Skip to content

Releases: kychee-com/run402

v1.51.0

29 Apr 14:23
fc98366

Choose a tag to compare

Run402 v1.51.0 is a lockstep release for the MCP server, CLI, SDK, and Functions helper package.

Highlights:

  • Added a canonical Run402 error envelope across SDK, CLI, and MCP surfaces so agents can branch on structured codes, retryability, phase, resource, and remediation fields instead of parsing prose.
  • Improved deploy error translation and CLI stderr output so gateway details are preserved while keeping existing user-facing behavior intact.
  • Added deploy operation list/events coverage across SDK, MCP, and CLI surfaces.
  • Refreshed the MCP/OpenClaw docs and CLI reference around error envelopes, deploy semantics, auth-as-SDLC manifest patterns, and the current v1.48+ surface.
  • Cleaned up release infrastructure with npm workspaces and broader CI test coverage for SDK and Functions sources.

Published packages:

  • run402-mcp@1.51.0
  • run402@1.51.0
  • @run402/sdk@1.51.0
  • @run402/functions@1.51.0

v1.50.1 - Deploy primitive hardening

29 Apr 08:38
b1f0ac7

Choose a tag to compare

Patch release hardening the v1.34+ unified deploy primitive after triaging 25 open issues.

Bug fixes (16 issues across 12 PRs)

SDK error translation

  • #145NetworkError (DNS / connection-reset / offline) now translates to Run402DeployError with code: NETWORK_ERROR retryable: true instead of the misleading generic INTERNAL_ERROR retryable: false.
  • #127Run402DeployError now preserves operationId and planId from the gateway error body (e.g. MIGRATION_CHECKSUM_MISMATCH), so agents can route directly to r.deploy.resume() without parsing err.body.
  • #128validateSpec errors now populate phase: \"validate\", resource, and a structured fix: { action, path } so consumers can switch on context instead of regexing the message.
  • #123 / #131 / #132 / #144r.deploy.resume() now validates input (rejects empty strings and non-op_-prefixed ids before issuing any request) and wraps gateway errors via translateDeployError. The MCP deploy_resume tool consequently surfaces a structured "Resume Failed" panel instead of "Unknown error (HTTP 404)" with a misleading hint.

Deploy events + retry

  • #124 / #134content.upload.skipped events now actually fire when the gateway reports a CAS object as already-present. Previously dedup'd deploys looked silent.
  • #135commit.phase events now emit done (or failed) for the previous phase before the next phase's started, plus a closing done on terminal ready. Progress UIs can now distinguish in-flight from finished phases.
  • #138r.deploy.start().events() async iterator no longer hangs when iteration starts after the deploy has already reached ready. Both successful and failed result-promise paths now wake a pending iterator.
  • #140CONTENT_UPLOAD_FAILED retryable: true errors now retry with exponential backoff (1s, 2s; max 3 attempts). A single transient network blip on a presigned PUT no longer fails the entire deploy.

Polymorphic byte sources

  • #126r.blobs.put(projectId, key, source) now accepts bare strings and Uint8Array polymorphically (matches ContentSource everywhere else in the SDK). The { content } / { bytes } wrappers continue to work.
  • #136 — MCP deploy tool's site.replace / functions.files / functions.source now accept bare-string entries in addition to the legacy { data, encoding?, contentType? } object — closes the cryptic "Unsupported byte source for X" trap.
  • #125r.apps.bundleDeploy legacy compat shim now validates opts.rls shape and throws a structured Run402DeployError(INVALID_SPEC) instead of crashing with TypeError: Cannot read properties of undefined (reading 'map').

Result populating

  • #130r.sites.deployDir({ ... }).url now correctly populates from urls.project (was returning \"\" because the v2 deploy primitive doesn't emit urls.site). Downstream UX (clipboard / status pages / CLI prints) recovers the live URL.

Skill-side

  • bugs: paginate gh issue list to avoid silent 30-result truncation.

Moved to private repo

4 issues with server-side root causes were moved to run402-private (gateway must reject empty deploys / short-circuit is_noop commits / return release-id+urls on resume of terminal ops; doc must reflect the wire-level 5 MB plan body cap).

Skipped (enhancement-only, not bug-typed)

5 issues remain open as feature requests: CLI release-URL print, custom-fetch x402 docs, 0-byte file edge cases, SDK list/events methods, manifest.json file pattern in SKILL.md.

🤖 Generated with Claude Code

v1.50.0 — Unified deploy primitive

29 Apr 07:40
cb9e6f4

Choose a tag to compare

Highlights

r.deploy.apply(ReleaseSpec) — one canonical SDK call for full-stack deploys. All bytes ride through CAS via presigned PUTs; no inline-base64, no 50 MB body cliff. Replace vs patch semantics per resource (site.replace, site.patch.put, site.patch.delete, same for functions, secrets, subdomains). Server-side commit state machine: validate → stage → migrate-gate → migrate → schema-settle → activate → ready. Migration registry keyed by (id, checksum) makes re-deploys noop-safe and same id + different sql a hard error.

import { run402, files } from "@run402/sdk/node";
const r = run402();
await r.deploy.apply({
  project: "prj_...",
  database: { migrations: [{ id: "001_init", sql: "CREATE TABLE ..." }] },
  secrets: { set: { OPENAI_API_KEY: { value: "sk-..." } } },
  functions: { replace: { api: { source: "export default async (req) => ..." } } },
  site: { replace: files({ "index.html": "<h1>hi</h1>" }) },
  subdomains: { set: ["my-app"] },
});

What's new

  • New Deploy SDK namespace with three layers: apply (one-shot), start (resumable op + event stream), plan/upload/commit (low-level for CLI/debugging). Plus resume, status, getRelease, diff.
  • files() helper (isomorphic) and fileSetFromDir() (Node-only, lazy, streams from disk).
  • Polymorphic byte sources: string, Uint8Array, Blob, web ReadableStream, FsFileSource. Hashes are computed locally; nothing inline on the wire.
  • Run402DeployError envelope with code, phase, resource, retryable, fix?, logs?. Maps the gateway's structured deploy errors (MIGRATION_CHECKSUM_MISMATCH, SUBDOMAIN_MULTI_NOT_SUPPORTED, SCHEMA_SETTLE_TIMEOUT, etc.) to typed exceptions.
  • Manifest-ref escape hatch: when the normalized spec exceeds 5 MB, the SDK uploads the manifest as a CAS object and references it. No body-size cliff anywhere in v2.
  • Server-authoritative manifest digest: idempotency keys on the gateway-computed digest, not a brittle SDK-side canonicalize match.
  • New MCP tools: deploy and deploy_resume. The legacy bundle_deploy / deploy_site / deploy_site_dir tools continue to work and route through the same SDK shim.
  • New CLI subcommands: run402 deploy apply --manifest <path> and run402 deploy resume <operation_id>. The legacy run402 deploy --manifest form keeps working unchanged.

Backward compatibility

Existing callers see no API break:

  • apps.bundleDeploy(projectId, opts) — translates to v2 internally; same input shape, same response shape.
  • sites.deployDir({ project, dir }) — wraps r.deploy.apply with fileSetFromDir; emits both unified DeployEvent shapes and the legacy { phase, ... } shapes for v1.32-era event consumers.
  • blobs.put — public API unchanged; routes through the same CAS substrate internally.

inherit: true on bundleDeploy is silently ignored with a one-time deprecation warning. Use r.deploy.apply({ site: { patch: { ... } } }) for partial updates instead.

Known issues

  • private#85: r.deploy.apply reaches status: ready and creates a release row, but the project's claimed subdomain's deployment_id is not yet updated by the activate phase — end users hitting the subdomain URL keep seeing the previous deployment. Fix is in progress on the gateway side. The legacy apps.bundleDeploy / sites.deployDir flows are unaffected (they hit the v1 path that updates subdomains correctly).

Documentation

  • cli/llms-cli.txt leads with the unified deploy section.
  • CLAUDE.md has a new "Unified Deploy (v1.34+)" section covering the three SDK layers, byte-source normalization, replace/patch semantics, server-authoritative digest, and shim mappings.

All four packages bumped to 1.50.0

v1.49.0

28 Apr 16:04
8c40115

Choose a tag to compare

Functions: real --deps, runtime version, and resolved deps

Companion to the gateway's drop-functions-layer-and-fix-deps change. The gateway now bundles @run402/functions plus user-supplied --deps into the function zip at deploy time — --deps is a real install/bundle step, not a reserved no-op.

What's new

  • --deps is live. Bare names (lodash) resolve to latest at deploy time; pinned (lodash@4.17.21) and range (date-fns@^3.6.0) specs are honored verbatim. @run402/functions (auto-bundled) and the legacy run402-functions name are rejected. Limits: 30 entries, 200 chars per spec. Native binary modules (sharp, canvas, native bcrypt) are rejected.
  • runtime_version on the function record — the bundled @run402/functions version (e.g. "1.49.0"). Surface as "Functions runtime version", never bare "runtime" (which already names the Node runtime).
  • deps_resolved — map of each --deps name to the actually-installed concrete version. Direct deps only; not a lockfile.
  • warnings — optional top-level string[] on the deploy response (sibling to the function record) for non-fatal notes such as bundle-size advisories.
  • Functions deployed before the gateway change have runtime_version and deps_resolved set to null.

Changes by package

  • run402-mcp, run402 CLI, @run402/sdk, @run402/functions released in lockstep at v1.49.0.
  • MCP deploy_function / list_functions / update_function descriptions and formatters updated to describe and surface the new fields.
  • SDK FunctionDeployResult gains warnings?: string[]; FunctionDeployOptions.deps doc rewritten with the real semantics.
  • CLI --deps help and SKILL.md / cli/llms-cli.txt updated; "not yet supported in deployed code" hedging removed.

npm

v1.48.0 — @run402/functions joins lockstep release

28 Apr 10:38
791f327

Choose a tag to compare

Highlights

  • @run402/functions is now an open-source npm package in this repo — the in-function helper library (db, adminDb, getUser, email, ai) that user-deployed serverless functions import. Previously it lived in the private gateway repo as run402-functions; that name is now npm deprecated in favor of @run402/functions.
  • All four packages now release in lockstep at the same version. The four published packages (run402-mcp, run402 CLI, @run402/sdk, @run402/functions) all ship together at v1.48.0. The /publish skill supports per-package selection for off-cycle patches; default is lockstep.

What's new for users

  • import { db, adminDb, getUser, email, ai } from '@run402/functions' in deployed serverless functions. npm install @run402/functions in your local project for full TypeScript autocomplete.
  • The legacy db.from(...) / db.sql(...) admin shim is removed — it silently bypassed RLS, which was a footgun. Use db(req).from(...) for caller-context queries (RLS applies) or adminDb().from(...) / adminDb().sql(...) for explicit BYPASSRLS.
  • auth.ts now uses a static import jwt from "jsonwebtoken" — bundle-safe for any downstream tooling that needs to inline @run402/functions.

Function record additions (forward-compat)

list_functions (MCP), the SDK's functions.list(), and the CLI's functions list --json now return two new optional fields on each function record:

  • runtime_version: string | null — the version of @run402/functions bundled into the function (set in a follow-up gateway release; null for now)
  • deps_resolved: Record<string, string> | null — resolved direct user dependency versions (also set in the follow-up)

Adding these to the type now means the gateway-side bundling change can populate them without a coordinated client release.

Breaking changes

  • import { db } from 'run402-functions' no longer resolves in deployed functions — switch to import { db } from '@run402/functions'. The npm package run402-functions is npm deprecated but still installable for legacy local consumers; new code should use the scoped name.
  • db.from(...) and db.sql(...) (object access, no (req) call) now error at type-check + runtime. The deprecation warning is gone — pick db(req).from(...) or adminDb() explicitly.

What's coming next

A follow-up release on the gateway side will:

  • Bundle @run402/functions and any --deps user packages into each function zip at deploy time via esbuild (real --deps install — currently it's a no-op)
  • Drop the Lambda layer entirely
  • Populate runtime_version and deps_resolved on every new deploy

The companion change is tracked as drop-functions-layer-and-fix-deps in the private repo.

Packages

v1.47.0 — Sunset legacy storage surfaces (clean slate)

28 Apr 09:32
d3c6778

Choose a tag to compare

Breaking changes — clean-slate removal of legacy storage surfaces

The private gateway repo big-bang-sunset the legacy storage shim on 2026-04-28 (kychee-com/run402#120). The four legacy MCP tools (upload_file, download_file, list_files, delete_file) and CLI run402 storage subcommand were registered but pointed at routes that 404 since the gateway's v1.33 cutover. Pre-revenue position is clean slate — every reference deleted, no migration tables, no "supersedes" mentions, no redirector stubs.

What's gone

  • MCP tools removed: upload_file, download_file, list_files, delete_file — handlers, registrations, tests, descriptions
  • CLI subcommand removed: run402 storage — module, dispatcher case, HELP entry, openclaw shim
  • Docs scrubbed: SKILL.md, README.md, README.zh-CN.md, openclaw/SKILL.md, cli/llms-cli.txt — tool-table rows, deprecated sections, "Supersedes" callouts all dropped; upload_file examples swapped for blob_put. The legacy /storage/v1/object/* REST docs in openclaw/SKILL.md were rewritten to document the new direct-to-S3 flow (init → PUT → complete).
  • Tests updated: sync.test.ts SURFACE rows + SDK_BY_CAPABILITY placeholders removed; legacy endpoints moved to IGNORED_ENDPOINTS so the test stays green even while the upstream private-repo site/llms.txt may still list them. Storage-targeting cases removed from cli-integration.test.ts and cli-e2e.test.mjs.

Use this instead

The five blob_* tools (blob_put, blob_get, blob_ls, blob_rm, blob_sign) and the run402 blob CLI subcommand cover every previous use case via the new content-addressed flow:

POST /storage/v1/uploads          → { upload_id, parts: [{ url, ... }] }
PUT  <presigned S3 URL>           (direct to S3, no gateway in byte path)
POST /storage/v1/uploads/:id/complete
                                  → AssetRef with cdn_url, cdn_immutable_url, content_sha256, sri, etag

Public blobs are CDN-served at https://pr-<public_id>.run402.com/_blob/<key> (auto-subdomain) or any claimed subdomain / mapped custom domain.

OpenSpec

  • openspec/changes/sunset-legacy-storage-surfaces/ (proposal + design + tasks + spec delta)
  • REMOVE delta applied to openspec/specs/incremental-deploy/spec.md for the dead "Upload file shows public URL" requirement

Stats

  • 30 files changed, 264 insertions(+), 675 deletions(-)
  • Net code reduction: ~411 LOC removed
  • Tests: 422 unit + 271 e2e passing, 0 fail

v1.46.0

27 Apr 20:37
3ce3f99

Choose a tag to compare

Highlights

Paste-and-go AssetRef with HTML tag emitters

blobs.put() now returns an AssetRef with built-in tag emitters — drop them straight into generated HTML and get content-hashed CDN URLs with SRI integrity for free.

const asset = await sdk.blobs.put({ projectId, body, key: 'app.js' });
const tag = asset.scriptTag();
// <script src="https://pr-...run402.com/app.js" integrity="sha256-..." crossorigin="anonymous" defer></script>

Three emitters: scriptTag(), linkTag(), imgTag(). Each returns a ready-to-use HTML tag with src/href on a content-hashed auto-subdomain URL guaranteed to work through the v1.33 CDN, plus integrity + crossorigin so browsers verify the bytes.

Best-practice defaults baked in

The agent's typical flow is now one line and gets every modern web best practice for free:

  • blobs.put() defaults immutable: true (enables cdnUrl, SRI, tag emitters)
  • scriptTag() defaults defer: true (non-render-blocking)
  • imgTag() defaults loading=\"lazy\" + decoding=\"async\" (off-main-thread decode, browser-controlled lazy load)

Opt-out paths ({ immutable: false }, { defer: false }, etc.) are explicit and documented.

CDN diagnostics for mutable URLs

New tools to verify a mutable blob URL is serving the latest bytes after re-upload:

  • MCP: diagnose_public_url, wait_for_cdn_freshness
  • CLI: run402 blob diagnose <url> (exit 0 on SHA match, 1 otherwise — shell-loop friendly), run402 cdn wait-fresh <url> --sha <hex> [--timeout <secs>]
  • SDK: blobs.diagnoseUrl(), blobs.waitFresh()

For immutable URLs (the new default), the content-hash makes them automatically read-after-write correct — these tools are for the mutable-URL path.

Sites deploy: --quiet flag + progress events

`run402 sites deploy` now documents `--quiet` mode and the progress event stream for programmatic deploys.

Install

```
npm install -g run402@1.46.0
npm install @run402/sdk@1.46.0
npx -y run402-mcp@1.46.0
```

Packages

v1.45.0 — deployDir progress events

27 Apr 14:51
143f408

Choose a tag to compare

Your agents can now stream live progress while a static site deploys instead of staring at a frozen prompt for a minute. Useful for long deploys that hit the v1.32 plan/commit transport's Stage-2 copy poll (up to 10 minutes for big sites).

What's new

SDKNodeSites.deployDir accepts an optional onEvent callback:

import { run402, type DeployEvent } from "@run402/sdk/node";

await run402().sites.deployDir({
  project: "prj_abc",
  dir: "./dist",
  onEvent: (e: DeployEvent) => {
    if (e.phase === "upload") console.log(\`uploaded \${e.done}/\${e.total} (\${e.file})\`);
  },
});

DeployEvent is a discriminated union over four phases:

  • { phase: "plan", manifest_size } — once, after `POST /deploy/v1/plan`
  • { phase: "upload", file, sha256, done, total } — per uploaded file (only `missing` ones; CAS-deduped files don't fire)
  • { phase: "commit" } — once, before `POST /deploy/v1/commit`
  • { phase: "poll", status, elapsed_ms } — per Stage-2 copy poll tick

Callback errors are swallowed so a buggy listener can't abort a deploy.

CLIrun402 sites deploy-dir and run402 sites deploy now stream events as JSON-line objects on stderr by default. Stdout still gets only the final `{ status: "ok", deployment_id, url }` envelope, so a piping agent can split streams cleanly:

run402 sites deploy-dir ./dist --project prj_abc > result.json 2> events.log
# events.log:
#   {\"phase\":\"plan\",\"manifest_size\":42}
#   {\"phase\":\"upload\",\"file\":\"index.html\",\"sha256\":\"...\",\"done\":1,\"total\":12}
#   ...

Pass --quiet to suppress event emission.

MCP — `deploy_site_dir` returns the buffered events as a fenced JSON code block in a second content entry on success. On failure, the partial event log is still appended so the agent can see how far the deploy got before erroring out.

Bookkeeping (no behavior change)

  • Promoted the `deploy-dir` capability to canonical at `openspec/specs/deploy-dir/` (rewritten to reflect what shipped at v1.44.0 — plan/commit transport, content-addressed manifest, no `inherit` forwarding).
  • Removed two stale requirements from `openspec/specs/incremental-deploy/spec.md` (`deploy_site` `inherit` and CLI `--inherit`) — both 410 Gone post-v1.32.

Compatibility

Fully backwards-compatible. Callers that don't pass `onEvent` observe identical behavior to v1.44.0; CLI without `--quiet` is the only visible change (extra stderr lines, which were previously silent). No server changes.

v1.44.0 - Plan/commit deploy transport

26 Apr 19:24
edfae95

Choose a tag to compare

What's new

Static-site deploys now use the v1.32 plan/commit transport. Re-deploying an unchanged tree issues no S3 PUTs; only the bytes the gateway doesn't already have are uploaded.

SDK (@run402/sdk)

  • sites.deployDir rewired to /deploy/v1/plan + /deploy/v1/commit. The SDK walks the directory, computes per-file SHA-256 + size + content_type, builds a manifest, and only uploads bytes the gateway is missing (single-PUT or multipart, with x-amz-checksum-sha256).
  • RFC 8785 (JCS) canonicalize matching the gateway's services/deploy-plans.ts:canonicalizeJson byte-for-byte. A digest mismatch would break plan idempotency, so the SDK ships a cross-repo digest fixture as a guard.
  • satisfied_by_plan: true entries are treated like present: true — no upload.
  • status: "copying" triggers a poll loop on /deployments/v1/:id (1s -> 30s backoff, 10 min cap) until ready or failed.
  • status: "failed" from commit surfaces as an ApiError; callers can re-invoke deployDir to resume.
  • One-shot URL refresh when a presigned S3 URL returns 403: re-call /deploy/v1/plan and retry the upload.
  • Removed: sites.deploy(files) inline-bytes overload (no shim) — callers migrate to sites.deployDir. SiteFile is preserved for apps.bundleDeploy (separate /deploy/v1 endpoint, unaffected).

MCP server (run402-mcp)

  • deploy_site now stages inline files to a temp dir and composes deployDir. Caller-facing schema unchanged minus the now-defunct inherit field.
  • deploy_site_dir reports bytes_total / bytes_uploaded from the commit response. inherit removed from schema.

CLI (run402)

  • sites deploy --manifest and sites deploy-dir <path> both go through the new transport.
  • --inherit is removed; passing it prints \"inherit is removed in v1.32; the SDK now uploads only changed files automatically.\" and exits 1.

Coordination

This release lands in the same window as the gateway v1.32 image. Without it, callers on older SDKs hit the now-410-Gone inline /deployments/v1 POST.


npm:

v1.43.0 — deployDir helper + optional wallet on projects.list

24 Apr 10:10
cfc969e

Choose a tag to compare

Two developer-experience improvements for agents and Node SDK callers.

Highlights

sites.deployDir({ dir }) — directory in, URL out

A new helper on the Node SDK (@run402/sdk/node), MCP server, and CLI that takes a local directory path and does the walking, binary detection, base64 encoding, and manifest assembly for you. Agents no longer reimplement dir-walk boilerplate before every deploy.

  • SDK: r402.sites.deployDir({ project, dir, inherit?, target? })
  • MCP tool: deploy_site_dir (shares the Zod schema shape with deploy_site)
  • CLI: run402 sites deploy-dir <path> --project <id>

The walk skips .git/, node_modules/, .DS_Store at every depth, auto-detects UTF-8 vs. binary content, POSIX-normalizes paths, rejects symlinks, and errors out on empty directories before issuing a request. Inherits the existing /deployments/v1 inline-payload ceiling (~100 MB) — blob-backed large files come on a future rung.

projects.list(wallet?) — wallet now optional in Node

Following the precedent set by allowance.faucet(address?), projects.list() on the Node SDK now falls back to credentials.readAllowance()?.address when the wallet argument is omitted. Collapses the common "what are my projects?" call to a single line. Explicit-wallet callers (including sandbox providers that lack readAllowance) are unchanged.

Clear error messages point to both escape hatches when the fallback can't resolve: pass an explicit wallet, or use @run402/sdk/node with a configured allowance. Closes #113.

Package versions

All three must stay in sync per the monorepo's publish contract.

Compatibility

Both changes are additive. Existing sites.deploy(files) calls, existing deploy_site tool invocations, and existing projects.list(wallet) calls all continue to work unchanged.