Releases: kychee-com/run402
v1.51.0
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
Patch release hardening the v1.34+ unified deploy primitive after triaging 25 open issues.
Bug fixes (16 issues across 12 PRs)
SDK error translation
- #145 —
NetworkError(DNS / connection-reset / offline) now translates toRun402DeployErrorwithcode: NETWORK_ERROR retryable: trueinstead of the misleading genericINTERNAL_ERROR retryable: false. - #127 —
Run402DeployErrornow preservesoperationIdandplanIdfrom the gateway error body (e.g.MIGRATION_CHECKSUM_MISMATCH), so agents can route directly tor.deploy.resume()without parsingerr.body. - #128 —
validateSpecerrors now populatephase: \"validate\",resource, and a structuredfix: { action, path }so consumers can switch on context instead of regexing the message. - #123 / #131 / #132 / #144 —
r.deploy.resume()now validates input (rejects empty strings and non-op_-prefixed ids before issuing any request) and wraps gateway errors viatranslateDeployError. The MCPdeploy_resumetool consequently surfaces a structured "Resume Failed" panel instead of "Unknown error (HTTP 404)" with a misleading hint.
Deploy events + retry
- #124 / #134 —
content.upload.skippedevents now actually fire when the gateway reports a CAS object as already-present. Previously dedup'd deploys looked silent. - #135 —
commit.phaseevents now emitdone(orfailed) for the previous phase before the next phase'sstarted, plus a closingdoneon terminalready. Progress UIs can now distinguish in-flight from finished phases. - #138 —
r.deploy.start().events()async iterator no longer hangs when iteration starts after the deploy has already reachedready. Both successful and failed result-promise paths now wake a pending iterator. - #140 —
CONTENT_UPLOAD_FAILED retryable: trueerrors 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
- #126 —
r.blobs.put(projectId, key, source)now accepts bare strings andUint8Arraypolymorphically (matchesContentSourceeverywhere else in the SDK). The{ content }/{ bytes }wrappers continue to work. - #136 — MCP
deploytool'ssite.replace/functions.files/functions.sourcenow accept bare-string entries in addition to the legacy{ data, encoding?, contentType? }object — closes the cryptic "Unsupported byte source for X" trap. - #125 —
r.apps.bundleDeploylegacy compat shim now validatesopts.rlsshape and throws a structuredRun402DeployError(INVALID_SPEC)instead of crashing withTypeError: Cannot read properties of undefined (reading 'map').
Result populating
- #130 —
r.sites.deployDir({ ... }).urlnow correctly populates fromurls.project(was returning\"\"because the v2 deploy primitive doesn't emiturls.site). Downstream UX (clipboard / status pages / CLI prints) recovers the live URL.
Skill-side
- bugs: paginate
gh issue listto 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
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
DeploySDK namespace with three layers:apply(one-shot),start(resumable op + event stream),plan/upload/commit(low-level for CLI/debugging). Plusresume,status,getRelease,diff. files()helper (isomorphic) andfileSetFromDir()(Node-only, lazy, streams from disk).- Polymorphic byte sources:
string,Uint8Array,Blob, webReadableStream,FsFileSource. Hashes are computed locally; nothing inline on the wire. Run402DeployErrorenvelope withcode,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:
deployanddeploy_resume. The legacybundle_deploy/deploy_site/deploy_site_dirtools continue to work and route through the same SDK shim. - New CLI subcommands:
run402 deploy apply --manifest <path>andrun402 deploy resume <operation_id>. The legacyrun402 deploy --manifestform 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 })— wrapsr.deploy.applywithfileSetFromDir; emits both unifiedDeployEventshapes 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.applyreachesstatus: readyand creates a release row, but the project's claimed subdomain'sdeployment_idis 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 legacyapps.bundleDeploy/sites.deployDirflows are unaffected (they hit the v1 path that updates subdomains correctly).
Documentation
cli/llms-cli.txtleads 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
- run402-mcp 1.50.0 — https://www.npmjs.com/package/run402-mcp
- run402 (CLI) 1.50.0 — https://www.npmjs.com/package/run402
- @run402/sdk 1.50.0 — https://www.npmjs.com/package/@run402/sdk
- @run402/functions 1.50.0 — https://www.npmjs.com/package/@run402/functions
v1.49.0
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
--depsis 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 legacyrun402-functionsname are rejected. Limits: 30 entries, 200 chars per spec. Native binary modules (sharp, canvas, native bcrypt) are rejected.runtime_versionon the function record — the bundled@run402/functionsversion (e.g."1.49.0"). Surface as "Functions runtime version", never bare "runtime" (which already names the Node runtime).deps_resolved— map of each--depsname to the actually-installed concrete version. Direct deps only; not a lockfile.warnings— optional top-levelstring[]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_versionanddeps_resolvedset tonull.
Changes by package
run402-mcp,run402CLI,@run402/sdk,@run402/functionsreleased in lockstep at v1.49.0.- MCP
deploy_function/list_functions/update_functiondescriptions and formatters updated to describe and surface the new fields. - SDK
FunctionDeployResultgainswarnings?: string[];FunctionDeployOptions.depsdoc rewritten with the real semantics. - CLI
--depshelp 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
Highlights
@run402/functionsis 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 asrun402-functions; that name is nownpm deprecated in favor of@run402/functions.- All four packages now release in lockstep at the same version. The four published packages (
run402-mcp,run402CLI,@run402/sdk,@run402/functions) all ship together atv1.48.0. The/publishskill 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/functionsin 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. Usedb(req).from(...)for caller-context queries (RLS applies) oradminDb().from(...)/adminDb().sql(...)for explicit BYPASSRLS. auth.tsnow uses a staticimport 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/functionsbundled 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 toimport { db } from '@run402/functions'. The npm packagerun402-functionsisnpm deprecated but still installable for legacy local consumers; new code should use the scoped name.db.from(...)anddb.sql(...)(object access, no(req)call) now error at type-check + runtime. The deprecation warning is gone — pickdb(req).from(...)oradminDb()explicitly.
What's coming next
A follow-up release on the gateway side will:
- Bundle
@run402/functionsand any--depsuser packages into each function zip at deploy time via esbuild (real--depsinstall — currently it's a no-op) - Drop the Lambda layer entirely
- Populate
runtime_versionanddeps_resolvedon 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)
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_fileexamples swapped forblob_put. The legacy/storage/v1/object/*REST docs inopenclaw/SKILL.mdwere rewritten to document the new direct-to-S3 flow (init → PUT → complete). - Tests updated:
sync.test.tsSURFACE rows +SDK_BY_CAPABILITYplaceholders removed; legacy endpoints moved toIGNORED_ENDPOINTSso the test stays green even while the upstream private-reposite/llms.txtmay still list them. Storage-targeting cases removed fromcli-integration.test.tsandcli-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.mdfor 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
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()defaultsimmutable: true(enables cdnUrl, SRI, tag emitters)scriptTag()defaultsdefer: true(non-render-blocking)imgTag()defaultsloading=\"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
- run402-mcp@1.46.0 — https://www.npmjs.com/package/run402-mcp
- run402@1.46.0 — https://www.npmjs.com/package/run402
- @run402/sdk@1.46.0 — https://www.npmjs.com/package/@run402/sdk
v1.45.0 — deployDir progress events
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
SDK — NodeSites.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.
CLI — run402 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
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.deployDirrewired 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, withx-amz-checksum-sha256).- RFC 8785 (JCS) canonicalize matching the gateway's
services/deploy-plans.ts:canonicalizeJsonbyte-for-byte. A digest mismatch would break plan idempotency, so the SDK ships a cross-repo digest fixture as a guard. satisfied_by_plan: trueentries are treated likepresent: true— no upload.status: "copying"triggers a poll loop on/deployments/v1/:id(1s -> 30s backoff, 10 min cap) untilreadyorfailed.status: "failed"from commit surfaces as anApiError; callers can re-invokedeployDirto resume.- One-shot URL refresh when a presigned S3 URL returns 403: re-call
/deploy/v1/planand retry the upload. - Removed:
sites.deploy(files)inline-bytes overload (no shim) — callers migrate tosites.deployDir.SiteFileis preserved forapps.bundleDeploy(separate/deploy/v1endpoint, unaffected).
MCP server (run402-mcp)
deploy_sitenow stages inline files to a temp dir and composesdeployDir. Caller-facing schema unchanged minus the now-defunctinheritfield.deploy_site_dirreportsbytes_total/bytes_uploadedfrom the commit response.inheritremoved from schema.
CLI (run402)
sites deploy --manifestandsites deploy-dir <path>both go through the new transport.--inheritis 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
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 withdeploy_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
- `run402-mcp@1.43.0` — https://www.npmjs.com/package/run402-mcp
- `run402@1.43.0` — https://www.npmjs.com/package/run402
- `@run402/sdk@1.43.0` — https://www.npmjs.com/package/@run402/sdk
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.