Skip to content

Execution & sandboxes ​

Coding runs use task-directed coding checks independently of environment preparation. Explicit legacy startup-smoke policies retain the first coding baseline compatibility admission and durable receipt contract. Tools never touch the bot host: every bash/read_file/write_file runs through an Executor. Production uses per-thread Cloudflare Sandboxes behind the proxy Worker; a thread's follow-ups reuse its workspace.

  • Code: src/core/dispatch/runLoop.ts (closed execution failure context), src/execution/typedExecutionDiagnostic.ts (closed diagnostic validation), src/core/checkExecution.ts + src/core/checkExecutionTypes.ts (caught execution observations), src/core/harness/container.ts (trusted operation context), src/core/runEventLines.ts + src/core/runEvents.ts (saved observation retention), src/artifacts/types.ts (platform-neutral artifact metadata), deploy/cloudflare-resident/credentialInspection.ts (typed count-only Worker boundary), src/artifacts/ (the artifact store: store.ts seam with the R2 and in-memory implementations, keys.ts, contentType.ts, config.ts, buildStore.ts, agreement.ts), src/core/dispatch/readyBinding.ts (acknowledged pilot binding before continuation), src/core/dispatch/staging.ts (inbound staging: the pull and exclude commands, the turn's line, copy-then-pull, the run's workspace paths), src/core/dispatch/threadAssets.ts (the thread's files: the one catalogue read over the runs' artifact events, and the where-clause the prompt and recall share), deploy/cloudflare/artifactsCopy.ts (the Worker's copy route) + deploy/cloudflare/ensure-bucket.mjs (the bucket step of the bot's npm run deploy), src/core/commands/credentials.ts, src/core/credentialInspection.ts, src/execution/ (workspaceAdvance, executor, factory, cloudflareSandbox, sandboxCredentials, sandboxErrors, sandboxEnv, sandboxIdle, sandboxStart, e2b, resident, githubApp, githubRepoProbe, sandboxLifecycle), src/agents/registry.ts (MACHINE_CLASSES, AgentDef.machine), deploy/cloudflare-sandbox/worker.ts + wrangler.jsonc + Dockerfile + docker-wrapper.sh + runtime-supervisor.mjs, src/deploy/artifactsBucket.ts (the artifacts bucket's lifecycle rules and privacy verdict over the operator's Cloudflare token), src/core/commands/artifacts.ts (artifacts lifecycle, artifacts check), src/execution/seedPlan.ts (item 25: the seed handle's shape, the fix-up script, the missing-backup classification) src/execution/legacySandboxCredentials.ts (cold sandbox reuse cleanup), src/execution/coldPublication.ts + coldPublicationBoundary.ts (fresh controller identity, typed pack and fixed Git plan),
  • Docs: Execution and trust, AGENTS.md invariants 5, 6
  • Tests: src/core/harness/container.test.ts, src/core/dispatch/runLoop.test.ts, src/core/typedExecutionDiagnostic.test.ts, src/artifacts/*.test.ts (the store seam, keys, types, config, the builder, the bucket agreement), deploy/cloudflare/artifactsCopy.test.ts + deploy/cloudflare/ensure-bucket.test.mjs (the copy route and the bucket step), src/core/dispatch/staging.test.ts (inbound staging), src/core/dispatch/threadAssets.test.ts (the thread's files: the catalogue read), src/channels/slack/attachments.test.ts (the by-reference classification), src/execution/factory.test.ts (per-agent provisioning + resident selection + the machine classes), src/execution/githubRepoProbe.test.ts (the repo-cold repository vet), src/core/dispatch/resolve.test.ts (which vet a class gets), src/core/dispatch/authorize.test.ts (the repo-cold refusals), src/execution/resident.test.ts, src/execution/binaryRead.test.ts + src/execution/readBytesRemote.test.ts (the binary read: contract, local, both remote clients), src/artifacts/store.test.ts + src/artifacts/keys.test.ts + src/artifacts/contentType.test.ts + src/artifacts/config.test.ts + src/artifacts/buildStore.test.ts (the artifact store), src/execution/shellQuote.test.ts, src/execution/githubApp.test.ts, src/execution/githubIdentity.test.ts (the identity the process acts as), src/execution/bashTimeout.test.ts, src/execution/executor.test.ts, src/execution/workspaceAdvance.test.ts (native workspace movement), src/execution/e2b.test.ts, src/execution/cloudflareSandbox.test.ts, src/execution/sandboxCredentials.test.ts (the executor-side credential-file refresher), src/execution/sandboxErrors.test.ts, src/execution/sandboxEnv.test.ts (the body-only env reader the Worker uses), src/execution/coldPublication.test.ts + coldPublicationBoundary.test.ts (typed cold effect and controller separation), src/execution/sandboxLifecycle.test.ts (the exec keepalive + static guards that the sandbox Worker wires it, renders every failure through thrownText, reads the env map through envFromRequest from the body alone while the executor sends only the body, names setsid -f in its timeout hint, and that wrangler.jsonc rolls out in one wave), src/deploy/wranglerTemplate.test.ts (the sandbox template's instance type), src/deploy/imageNode.test.ts (static: every image names one exact Node tag whose major is .nvmrc's, and the execution images copy it over the base's and assert it at build time), src/deploy/sandboxRuntimeSupervisor.test.ts (the sandbox image's PID 1: the supervisor's shape and its loop driven through an injected process factory), src/deploy/sandboxDocker.test.ts (static: the sandbox image installs the Docker engine and the lazy-start wrapper, and the wrapper's shape), src/deploy/imageToolchain.test.ts (static: both execution images carry one toolchain — python3/make/g++, ffmpeg, Playwright's headless Chromium at one pin — and prove each piece at build time), src/agents/registry.test.ts::the workspace prompts name the image toolchain (every prompt with a workspace names it); the deeper sandbox behaviors are infrastructure-dominated — the live checks below are their proof. src/deploy/artifactsBucket.test.ts, src/core/commands/artifacts.test.ts (the bucket's rules, privacy and calls; the two operator commands)., src/execution/sandboxIdle.test.ts (item 22: the idle verdict, the ledger, and the guard against a fake host — arming, counting, the stuck cap, the bounded destroy and the platform kill), src/execution/sandboxStart.test.ts (item 23: the start gate against a fake host — one warm-up, the starting answer, the pass-through, the failure handed on, the restart after a stop), src/execution/seedPlan.test.ts (item 25: the handle's parse, the fix-up script, the constants, the classification, and a scan over the Worker, the image, the template and the secrets manifest for the wiring), src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor seed src/execution/legacySandboxCredentials.test.ts (cleanup command and failure),

Behavior ​

The fixed harness log reader may re-read the same path and byte range once after an answered infrastructure failure whose origin the Resident executor set for an unnamed, stateless 5xx answer with closed service-failure metadata. This observes the original log; it never resends a model request, tool call, FIFO input or alive probe. It keeps the original cancellation and operation deadline. A second failure propagates. Fixed retrying, recovered and failed log outcomes include a bounded, redacted run identifier and expose the observation path without command, path, payload, credential or error text. Recovery requires a successful, untruncated structured result. Missing origin, authentication and policy refusals, named or state-bearing answers, unknown metadata, other failure reasons, command exits and sandbox replies with unqualified or stale diagnostics retain their existing behavior.

A failed run can retain a closed caller and operation context from the trusted execution seam in its existing failure summary. The context contains no command, path, payload, credentials or error text. It identifies the operation that failed; it does not prove whether that operation executed and grants no recovery or retry authority. Context validation omits malformed context or extra fields without changing the original failure.

Controller-owned workspace commands retain their stage, exit status, truncation flag and bounded, redacted failure output. A failed or truncated command cannot prove a checkout or authorize the next movement step.

  1. Per-thread isolation: one workspace/sandbox per threadKey; follow-ups reconnect. Losing a sandbox degrades gracefully — repos re-clone, nothing else is lost. A thread's container is eligible for teardown after 5 minutes of idle (SANDBOX_SLEEP_AFTER, src/execution/sandboxLifecycle.ts; the SDK default is 10 on the 0.12 and 0.13 lines, was 20 on 0.3.x): a follow-up inside that window lands on the same workspace. Beyond it, a proven unseeded container is destroyed and a follow-up gets a fresh container; a seeded or uncertain writer stays in its existing workspace unless the Worker proves independent quiescence and a complete owner-bound checkpoint (sandbox-preservation.md). Idle means idle: a running request does not count toward the five-minute idle window (item 22). The Worker's own deadline, not the SDK's, triggers the teardown decision, not unconditional destruction of a detached pi orphaned by a bot death.

  2. Command timeout is shell-level and structured: every /exec runs under coreutils timeout -k 10 <secs> inside the sandbox — 280s by default, raised per call by the bash tool's timeoutMs (item 12) up to the shared 20-min ceiling; a deadline kill is a genuine exit 124 (137 normalized to 124) with an actionable stderr line naming the limit, the timeoutMs knob and, for a job that must outlive the command, setsid -f with the wrapper's stdio redirected (DETACH_HINT, src/execution/sandboxLifecycle.ts; never nohup: the command's process group is reaped when timeout returns, so a plain background job dies with it — item 17's reason, and the recipe the explore preset carries, agent-explore.md item 5; the redirects are item 24's). Two SDK backstops sit above it and never win the race: the per-process timeout the Durable Object launches each command with (the shell limit plus a 40 s margin, SDK_BACKSTOP_MARGIN_MS; the Durable Object's own wait for the output to END is the command's deadline plus 20 s (OUTPUT_AFTER_EXIT_MS, item 24), after which it asks the runtime: a process that exited is answered with its code, one still running is killed and answered as the timeout), and the container's COMMAND_TIMEOUT_MS (1240s, Dockerfile — 40s above the ceiling; changing it needs a container rebuild). The SDK's own timeout surfaces only a timedOut flag, which is folded into the same exit 124 story. Invariant — one in-flight exec never outlives the container's activity timeout: the 0.13 SDK renews the activity timeout every second while a command's stream is open (its control connection's busy poll, BUSY_POLL_INTERVAL_MS), so sleepAfter (5 min) is idle time only and the shell-level timeout is the one deadline a command can hit. History: the 0.0.x @cloudflare/containers base renewed the clock once per proxied fetch, BEFORE the fetch, and its alarm loop SIGTERMed the container the moment the clock read expired — a command whose budget equalled sleepAfter died at the budget exactly and the next command found /workspace empty; the Worker carried its own once-a-minute keepalive from then until the 0.13 line made the SDK's own tracking the mechanism.

  3. Heartbeat streaming: /exec responses send headers immediately, a whitespace heartbeat every 15s, then one JSON document — no hop ever sees an idle connection (idle drops lost results in transit). Failures arrive in-body in a dual shape (error + exitCode: 127/stderr) so old/new executors both render them; in-body errors are never blindly retried. The failure text is never empty: both Worker catches (the /exec stream and the /read//write handler) render the thrown value through thrownText (sandboxErrors.ts) — the SDK's own message when it has one, else sandbox exec failed with no message from the SDK (<name>[, code <code>] | no error name); the container may still be running a previous image while a Worker/image rollout is in progress — retry in a minute; the classifiers (isFleetBusyError, isRecycleError, recycledMidCommandMessage) keep reading the raw shape. On the other side, a success body has no error key at all, so the executor treats a PRESENT-but-empty error as the Worker's failure shape with its text missing — ExecInfraError, item 9 — never as a command exit. The failure this closes: a SandboxError built from a body with no message has message === ""; a shape.message ?? String(err) fallback keeps it (?? fires only on null/undefined), a truthy check lets {error: ""} through as a plain exit 127, the health tracker counts a success, and the model reports its shell "down". The document also carries durationMs, the command's wall time in the sandbox (tracing.md item 19); the bot does not read it yet.

  4. One command is one supervised process, and the Worker retries nothing of its own. On the 0.13 SDK line there is no persistent shell session to lose: SwitchboardSandbox.runCommand starts each command as its own process (createExtensionProcessSandbox(this).exec(argv, { env, timeout }), argv = timeout -k 10 <secs> bash -c 'mkdir -p /workspace && cd /workspace && <command>') and collects it (output()), inside the Durable Object, and the fetch handler calls that method over RPC. The SDK's runWaking starts a stopped container and waits for its control port before the process is spawned, so a cold or sleeping container is a slower first command, never an error the Worker has to re-send; a container that is still being placed is the fleet-busy answer (item 14). History: on 0.3.x a container restart orphaned the SDK's cached session and the Worker reset it and retried once; 0.12.x persisted the session id and recreated it itself, and still answered Container is starting during a boot, which the Worker re-sent after 3 s. Both fences are gone with the sessions. A failure whose command MAY have run — the runtime replaced under the call — is never re-run; item 9 names it a recycle instead.

  5. GitHub identity and the run door: every workspace backend honors the effective profile's none | read | write identity or refuses before attach. blank carries only none. A read or write workspace requires the GitHub App and a live run bearer; a missing door fails by name for resident, cold sandbox, E2B and local CLI. Model commands receive GH_HOST and GH_ENTERPRISE_TOKEN=<revocable run bearer>, never an App installation token as GH_TOKEN, in a credential file, or from a helper. The trusted GitHub door on PUBLIC_GIT_BASE_URL accepts the run bearer for Git smart HTTP and gh API reads, then mints App tokens on its own side. Read calls require the dispatch-authorized repository and use a repository-pinned token with the explicit read-only App permission subset. Git receive-pack uses a token pinned to the bound repository with only contents:write, workflows:write (for pushes changing .github/workflows) and metadata:read. The GitHub mint response must report repository_selection: selected, the sole bound repository and matching effective permissions before either door token is cached; the verifier tolerates only GitHub's omission of implicit metadata:read. It parses the command prefix before forwarding, refuses default-branch, deletion, foreign repository/ref and malformed pushes, and uses the repository authorized by dispatch, saves a first branch reservation durably, and permits updates only after Git confirms that branch was created. The binding survives a run resume; the bearer stops working when the run ends. An opaque static GH_TOKEN cannot stand in for the App at the door. The local CLI serves gh through a private Unix socket and Git through loopback; hosted runs use a second custom domain so dashboard Access on the main /api/* path does not intercept gh. The resident Worker scrubs prior thread-readable App credential files on attach and requires the door bearer for writable commands; its root-owned mirror mint remains inside the Worker, limited to contents:read, pull_requests:read and metadata:read for clone/fetch and pull request fate lookup, and checks the returned scope before caching. gitIdentityEnvs still supplies the requester author and bot committer pair for write runs, and the images' off-GitHub fallback pair remains. The sandbox Worker receives the run bearer only in the request body's per-command env, with no x-env-* header or command-text export. E2B creates sandboxes without credential env and retires legacy persisted sandbox IDs before any reconnect; only entries marked as created without env may reconnect. The old sandbox credential-file refresher remains a tested utility but the factory no longer wires an App-token source into a model workspace. Decision 0048 records the original cold-only proposal; this implementation extends the boundary to residents and the local CLI after the resident credential exposure. The live acceptance row below remains a separate deployment proof. 5a. A repository-bound Git door does not capture public toolchain clones. Git installs no global GitHub URL rewrite: insteadOf matches prefixes, so even a rule for the bound .git name also captures a public repository whose name extends it. Provisioned checkouts use the exact bound door origin; a cold agent may clone that repository with gh repo clone <owner/name> or git clone "$GIT_DOOR_REMOTE". A direct private github.com clone URL is not authenticated by the run bearer. Other public GitHub clones stay direct. The default Git credential helper is cleared, and the run-bearer helper is scoped to the door origin, so a public clone cannot receive the bearer. The resident’s inline helper follows the same host scope.

  6. A sandbox Worker/image rollout never looks like a dead or silent sandbox. A bot deploy has a ~10–15 min deaf window (container image rollout); Socket Mode events during it are lost, not redelivered. A sandbox-Worker deploy rolls containers too, and a Worker and its image deploy as two artifacts: until the rollout finishes, the NEW Worker code can be handed a container instance still running the PREVIOUS image (Cloudflare's own words). In-flight thread sandboxes on old instances are SIGTERMed in any rollout mode (the grace period is 0) — recovered by (4), disk lost, named by item 9. What is closed and named is the window a NEW thread could fall into: (a) deploy/cloudflare-sandbox/wrangler.template.jsonc sets rollout_step_percentage: 100, so old-image instances are replaced in ONE wave and the window shrinks from minutes (the platform default [10, 100]) to seconds; (b) the 0.13 SDK's control protocol is versioned, so a Worker on one line talking to a container on another fails its handshake by name rather than running commands against a server that misreads them — RuntimeControlProtocolError against a versioned peer, and against a 0.12 server, which predates the handshake, the missing call itself ('utils.getRuntimeMetadata' is not a function, the text the first 0.13 deploy's live probe answered with during its three-minute roll before going live) — automatic teardown uses the idle guard; authenticated cancellation may destroy only its exact dedicated run slot of its own accord (a destroy from a Durable Object hook is unbounded, coalesced callers hang until eviction, and a fresh placement during a gradual wave can land on the old image again); (c) a command that reaches a skewed instance and fails without a message is named by thrownText (item 3) with the rollout hint. History: the 0.12.x Worker read the container's /api/version in onStart and logged sandbox.version-skew at warn; the 0.13 line exposes no such read from the Durable Object, and the handshake carries the fact instead. The shape this closes: a release deploys the sandbox Worker and its image together, a thread created seconds after the upload lands on a previous-image container, and every command fails with an empty message rendered as a silent exit 127 until a rollout wave happens to replace the instance.

  7. Per-agent provisioning: executor selection is context-aware — makeExecutor(opts, { threadKey, agent, profile, repo?, ref? }) sees the resolved agent, the run's effective profile (src/config/profile.ts: the machine class provisioned and the identity minted are read from it and never from the agent's own fields, so nothing a boundary capped can leak back in) and the inferred repo/ref for resident environments, and returns { executor, note? } (the note is the named fallback reason the dispatcher shows on the status card). Each agent declares the machine class its runs are provisioned on (AgentDef.machine, one of MACHINE_CLASSES in src/agents/registry.ts — item 18 has the four), and the factory branches on the class alone: an agent on none (general, research) gets a null executor — no sandbox or workspace is created or reconnected, and no sandbox credential is required — and a tool call reaching it surfaces as a legible tool error naming the class (a wiring bug: workspace tools on a machine-less class), never a crash. An agent on repo-resident (coding, review, ship) provisions the configured backend: the target repository's resident when it is serviceable (item 8), else the per-thread backend with the checkout and a named note. Repository resolution reads the same class: only a class that carries a checkout resolves or gates a repository (resident-repos.md item 29).

  8. Resident backend: with execution.resident {baseUrl, tokenEnv} configured AND a resolved target repo (ctx.repo — populated by the repo resolver), the factory probes the resident's /status (short timeout; transport failures negative-cached 30s) and selects ResidentExecutor only on warm; any not-warm state falls back to the per-thread backend above with a named note (resident <state> (<reason>) — using fresh sandbox — a degraded resident is loud). not-onboarded (404) also falls back per-thread, but carries a note too — repo not onboarded as a resident — running in a cold per-thread sandbox; onboard it (\repo onboard <owner/name>`) for a warm, deps-ready environment` — so the user can tell coding ran cold instead of on a warm resident (never a silent surprise). Full contract and criteria in resident-repos.md.

  9. Infra-failure classification: Cold checkout preparation starts no command for an already stopped run and preserves a thrown typed aborted error on fresh selection and recorded recovery, so setup ends stopped, never setup_failed. An unrelated preparation failure beside a pending stop remains a setup failure; the signal alone never reclassifies that failure. An Executor returns a normal nonzero command exit as ordinary output (exit <n>: …, never a throw), but throws ExecInfraError — carrying a typed reason (ExecInfraReason), named where the executor builds the failure and never inferred from its prose: transport-lost (the request failed on its transport), deadline-passed (the call's or the send's deadline passed with no answer), empty-failure (the Worker's failure shape with its text missing), worker-unavailable (an HTTP 5xx: the isolate rolling under a deploy, the resident mid-restore or its mirror mutex held), answered (the Worker named a failure in its body on a status that is not a 5xx — the SDK's words forwarded, a named refusal — whose meaning is in the words), refused (a refusal no wait clears, judged at once by the type whatever its words: a bare 4xx, a worktree still gone after the one re-attach, the deploy-storm streak guard, the strike after the wake wait — its words still the container's, the wait already spent — and the resident down or the resource unregistered behind a 5xx), aborted (the run's own stop aborted the request — the stop's signal decides before the error's name; nothing waits); the first four a wait can clear (EXEC_INFRA_WAITABLE, infraMayClear), which is what the harness's one more command waits on (harness.md item 6). The resident client types an answer by the fields the resident puts on it, never by its words or its status class alone (residentAnswerReason, on /exec, /read and /write alike). A 5xx that carries the resident's lifecycle pair — state and stateReason, the Worker's getStatus() beside the answer, on the hydrate path's not-serviceable answers (restore in progress, a hydration failing mid-restore), on mirror-busy while a refresh holds the mirror, on disk-pressure and on attach's image-stale — is worker-unavailable when that pair says the resident is coming back and refused when it does not, by the ONE decision the wake path makes on the engine view (isWakeable, src/execution/residentWake.ts: restoring, a serviceable state, a degraded reason the engine retries; not down, not onboarding — the rebuild after a down transition —, not a repo failure), so one fact never gets two verdicts by route. The answer's own reason — mirror-busy, disk-pressure, image-stale, the not-serviceable detail, runtime-replaced, control-reset — is never read as the lifecycle reason: a busy mirror on a degraded (github-unreachable) resident is a wait, not a repo failure. The fields ride every route alike: the JSON routes' body, and the /exec stream's one document on both of its paths — a failure the Durable Object named (execFailureDocument: the pair, the answer's own word, its status) and a pending result that REJECTED, the stub and not the method, answered as the Durable Object answers the same fact when it catches it inside (threadRejectionErr, by route — the mapping /read and /write give a rejected stub too): the DO reset by a code update is the DO's own word, control-reset on its 409, so a write's unknown outcome is resolved by the client's control-reset rule and never waited on as the resident unavailable; a runtime replacement is answered as the methods answer it, the DO's gate applied as far as it reaches — on /exec the word is said where the SDK's own moved sentence vouches for it (sdkVouchesRuntimeMoved, the DO's rule applied to the text that survives the stub boundary) and withheld where only the DO's knowledge of a restore under way could (the unknown branch's bare 409 with the SDK's words, as replacedExecAnswer gives it; the seam's one more command decides), on /read and /write it is said either way (runtimeReplacedErr, unconditional there on purpose, so the client's one re-attach-and-retry fires); anything else is the 500 with its transient, decided once the typed predicates are settled, through the same document — and the client reads a streamed answer's status from the body on /exec, /attach, /await-restore and /op by one rule (answeredStatus: only an HTTP 200 carrying an error and a status is re-statused), so a streamed 503 is the 503 it is, a streamed rejection is the catch-all's 500 it is, and neither is a deterministic answer over HTTP 200; stateReason is remote text and is sanitized like reason (resident-repos.md item 62). A Worker predating stateReason put the lifecycle reason in reason on its not-serviceable answers, so where stateReason is absent the client reads reason as the lifecycle reason unless it is one of the answer's own words that can reach that reading (mirror-busy, disk-pressure, image-stale; unregistered is refused before it, runtime-replaced and control-reset are 409s with no state) — the skew window keeps its wait through a degraded-but-serviceable resident; only that Worker's mirror-busy on a degraded resident, carrying no lifecycle reason at all, refuses. reason: "unregistered" (no registry record or repo facts to serve from, said at both preamble sites) is refused. A pre-admission 503 during a checked VM recycle or a refresh-only pool conflict carries transient: true: no command has started, so the existing bounded wait may clear it without replaying a side effect. A 5xx with a body but no state is otherwise the resident's deterministic answer — read-failed, attach-failed at <step>, op-failed at <step> — and answered, so its words decide at the seam and the failure stands at once instead of being re-run for five minutes, unless it is the 500 for a throw no route named — one builder, catchAllErr (deploy/cloudflare-resident/threadErr.ts: the shapes and the rules, pure over the throw and the route with the SDK's predicates handed in, run under plain Node with the SDK's real sentences; the Worker's scan pins only the wiring), at whichever catch met the throw: the fetch handler's catch-all, the streamed /attach and /await-restore rejections, the thread data plane's last resort, a route's own catch around its body (attach-failed; op-failed with no step, at the op's catch and the /op stream's rejection) — and the Worker typed that throw transient: true (isTransientPlatformThrow: a control reset, a runtime replacement, or the transient no word names — isUnnamedPlatformTransient: the runtime unreachable — by the SDK's connect abort's AbortError name or its exact sentence (RUNTIME_UNREACHABLE_WORDING, anchored to the whole message, so a TimeoutError's The operation was aborted due to timeout, the text a route's own AbortSignal.timeout on a slow GitHub raises, never matches; a wrapper is read by its cause), or by the resident's own renamed word runtime-unreachable: (isRuntimeUnreachableReason), which is what crosses the stub once run() has renamed the abort —, the pinned SDK's own platform-transient predicate (isPlatformTransientError: the code-update reset, a lost connection, the storage-startup reset, the typed retryable flag — the SDK's signal, not a string table of ours), and the sentences it does not name, each anchored to its sentence so an embedding never matches: the platform's bare internal error (the whole message, and the TOP-LEVEL throw alone, PLATFORM_INTERNAL_ERROR_WORDING — git's fatal: internal error is not it, and a Worker's own failed outbound fetch(), which carries exactly that message, wrapped by the route that made the GitHub or mirror subrequest as its cause, is that route's deterministic failure, never a blip a re-probe clears; the SDK types it only where workerd set its retryable flag, which nothing says it always does), the memory-limit reset (exceeded its memory limit and was reset), the storage-timeout reset (storage operation exceeded timeout which caused object to be reset — a route's own storage operation failed is not it), and the Durable Object overloaded, which the SDK excludes from its own tight retry and this client re-probes after the harness's bounded wait), which a re-probe clears (worker-unavailable); a bare 5xx with nothing is worker-unavailable; a body on any other status is answered; a bare 4xx is refused. Who reads transient: /exec, /read and /write through residentAnswerReason; /attach reads it alone, by one rule at every site (isTransientRefusal, the provenance rule probeStatus types /status by: a 5xx carrying the field, or a 5xx carrying no Worker document at all — the edge's own page — and never a flag on another status; so an edge 5xx on /attach enters the wake wait as the blip it is, never a plain error the selection falls cold on a minute after its probe waited, nor one the harness cannot wait on): a transient 500 on attach() — the run's first attach in the factory (a fresh run's, or a resumed run's re-attach to its recorded worktree) and every mid-run re-attach alike — enters the wake wait itself (a /status probe, then a re-attach as soon as the engine says it serves — at once when the first probe already says so, with no pause and no second probe (the engine was never told of an exit, so its serving view is current; after a container's exit the view lags, and the first re-attach follows the first pause, never a full /attach into a container still starting) —, the probing bounded by the caller's budget under the wake ceiling: FIRST_ATTACH_WAIT_MS (src/core/budgets.ts, a minute) for a dispatch's first attach — one budget across the factory's retry by default after a needs-ref — the retry gets what the first attach's WAIT left (ResidentNeedsRefError.wokeAfterMs), never what the first request's own latency took, a stale mirror fetched before the 409 being the attach's time and not the wake's — the card naming both waits' total; for a mid-run re-attach's PROBING what is left of the operation's ONE wake budget, which the wait for a rolling container and the recovery attach's wait draw on from the same clock — a clock that starts at the first wait, never at the call, so the call's own latency before the refusal is the command's and never the wake's — so an operation never probes twice its budget; the attach REQUESTS inside that wait are not the operation's to bound: a recovery attach, direct or as the wait's re-attach, runs under the attach's own default whatever the operation's budget, and the run's deadline and lease bound the whole; the wake budget bounds the PROBING alone, and ONE bound holds for every attach request, whoever opened it — the first request of an attach() and each re-attach inside its wake wait alike, a first attach's, a resume's, a head move's, a recovery attach's and the rolling wake's re-attach that recreates the worktree from the mirror: the attach's own default (attachOnce's: the exec default, BASH_TIMEOUT_MS, five minutes — never the twenty-minute exec ceiling), clipped to the RUN's remaining wall clock less the write-up reserve where the executor carries the run's clock (ResidentExecutorOptions.remainingMs, built into the executor per dispatch from ExecutorContext.remainingMs — the run control's lease clock, RunControl.remainingMs, which the RUN LOOP starts (startLease) — on the harness's own lease event, whichever harness published it (pi and OpenCode both do, harness-pi.md item 15), and for a resume from the remainder the record kept, since a resume publishes no second lease — and the dispatch, the relaunch and a review round hand on; attachBoundWithinRun, src/execution/bashTimeout.ts), so every attach the executor opens for the run's life — under the harness's container operations, the tools, the post-steps — is clipped, never one caller's alone; inside the reserve, or with less than an attach's floor past it (ATTACH_REQUEST_MIN_MS, half a minute: a bound too short to clone would be cut mid-clone and struck as a rollout — the command's one-second floor is the model's to choose, an attach is opened on the run's behalf), no request is opened at all: the typed refused (ResidentLeaseSpentError), naming the run's clock, since an opened request would run the attach to its end in the resident for a run that is ending and its deadline would read as waitable — and the run's end is read from it where it is decided: a relaunch's re-attach asks for nothing (WorkspaceReattachLeaseSpentError, the decision lease_spent) — read at its entry, again after its probe's wait, off the executor's ResidentLeaseSpentError when the lease ran out under the attach's wake wait, and again before any refusal the attach's own failure would become — a wake budget struck out, a re-attach request cut short — so the lease's end is never handed to workspace_lost; the probe's wait and the wake budget are both clipped to the lease less the reserve (leaseClippedBudget), so no wait spends the run into the reserve and hands the lease's end to a refusal — and the run ends on its budget: the time_budget_exhausted note carrying the bound's own sentence — attachBoundWithinRun's note, the one source, worded by the bound that refused: inside the reserve, or only N s past it under the attach floor — and no write-up ran, the budget's plain "without finishing" answer, the status completed, and none of the tail against the replaced container (no workspace observation, no push-before-abort salvage, no description turn, no work-left-behind note, no PR post-step; on a review, no head settle, no verdict turn, no review post: tailSkipped, the one predicate a hard stop skips them all by); never workspace_lost and never a new run from its request with a fresh lease; on a harness loop's own commands it cannot arise, since the loop ends before the reserve, and on the wind-down's or a post-step's command it is that command's own failure, never the container's verdict (identity answers no name on a refused); a bound the wake budget, the wake ceiling, an operation's call bound and the command's own timeoutMs never clip, since an attach may clone and install deps and a request cut short strikes a resident still attaching — a rollout strike the tracker counts — and provisions the run cold beside it; an executor built with no run (staging at dispatch, the CLI) and an attach before the lease starts (the dispatch's first attach) get the default alone; an operation's wall clock is its command's, and neither a recovery attach nor the wake's re-attach is the command) — and in that wait an unreachable Worker is the same blip: the Durable Object that reset or lost its connection under the attach loses /status too, so an unanswered probe after a transient refusal — isUnansweredProbe, the one rule the wait's decision, its strike and the selection probe's wait read the view by, typed by its provenance and never by the status class alone: the transport failing, the Worker answering /status with a 5xx it typed transient, or a 5xx carrying no Worker document at all (a non-JSON body — the edge's own error page, where the Worker never ran to type it); a 4xx there, an operator token no longer accepted, and a 5xx the Worker answered without typing it — its document carrying a string error and no transient, a throw in the status route — are the answers they always were and definite, refused — is waited through under the budget, never the definite "nothing says the container is coming back" a container's exit is judged by, and a wait spent with the Worker unreachable throughout ends in worker-unavailable, never refused — and binds when the resident answers, the binding carrying wokeAfterMs so the card names the wait (· after waiting Ns for the resident, as item 27's restore wait is named) and a hiccup a re-probe clears never sends a run cold; the run's hard stop rides in (the dispatcher hands its control's hardSignal to the attach stage as stopSignal) — into the first attach request itself as well as the wait's probes and re-attaches, into a resumed run's re-attach and a mid-run relaunch's (RelaunchContext.stopSignal, the run loop's hardSignal; the factory rethrows the stop by its typed shape, never as the re-attach refusal that would dispatch the stopped request again, reattachWorkspace answers stopped, and the relaunch ends the run as a hard-stopped run — the stop's answer, stopped_hard, nothing rotated, nothing relaunched, nothing restarted; a resumed run's row closes with the stop's status, never interrupted; a refusal beside a pending stop stays the refusal) and into a review round's mid-run head move (moveTo, carrying the round's hardSignal in MoveOptions) — so a stop at any point ends it at once with the stop's typed error and the factory never provisions a stopped run cold — and the dispatch reads that error as the stop it is (attachWorkspace answers stopped on the executor's typed aborted error — isRunStopError, the one shape a stop is read by, never the signal's state beside some other failure: a provisioning failure that coincided with a pending stop stays setup_failed with its error replied — and the settlement counts that stop as the loop's, so a follow-up queued during the wait is dropped with the ⛔ note, never run fresh on the stopped thread), closing the card stopped before the run started (⏹) with the neutral ⏹ Stopped by user. reply and ending the request stopped, never setup_failed; past the budget it is the wake's strike, worker-unavailable, which the factory folds into its cold-fallback reason and a longer clock may still wait on; a deterministic 500 stays the attach's own legible error, judged at once with no probe; the selection probe before the attach (resident-repos.md item 24's warm gate) — a fresh run's, a resumed run's (reattachWorkspace) and a mid-run relaunch's alike (harness-pi.md item 16) — meets the same blip a moment earlier and is waited through the same way when the Worker typed its 5xx transient — the fetch handler's catch-all 500 carries the field, probeStatus puts it on the unreachable view — re-probed at once and then every WAKE_POLL_MS under the first attach's budget on the wake path's ONE loop (waitOnStatus, which awaitWake runs on too — one pause, one stop check, one budget rule, each caller's probe under its own deadline: the wake's WAKE_PROBE_TIMEOUT_MS, the selection's configured probeTimeoutMs (8 s by default, resident-repos.md item 25) for its first probe and every re-probe alike; the factory's probeThroughBlip is the one function both the fresh selection and the resumed re-attach call), the first re-probe paying no pause as the wake wait's first view pays none, the run's stop riding into the first probe and every re-probe alike (a stop that aborts the first probe is the stop's typed error, never a cold fallback on the view it produced, and arms nothing), every re-probe through probeResident so a transport failure met inside the wait opens the outage breaker as the first probe's would — never the run's own stop aborting a probe, which the stop's signal decides before the error's name and which opens no window —, the wait drawn from that budget and named on the card with the attach's own — an attach that fails after it names it too; a probe still transient past it falls cold naming the wait (a resumed run's is refused naming it); and a blip never opens the probe's outage breaker while it is waited — the breaker stays the case of the platform not answering: a TRANSPORT failure, judged at once as before, and a wait SPENT on the edge's own page (more of the wait's views document-less than the Worker's typed blip — what the wait was spent on, never the last view alone: an edge wait that ends on one typed view is still an edge wait, and one edge page after a minute of typed blips is not), which arms the window at the wait's end so an edge outage costs one wait, not one per concurrent dispatch, while a wait spent on the Worker's own typed blip arms nothing; /op's streamed failure document carries its status in the body as /exec's does (streamOp sheds nothing), and ResidentOperations judges it by the same rule — isTransientRefusal over the status answeredStatus reads off the document, never the field on a 200 or on a named refusal — putting transient: true on the error outcome as the one signal (the message left the resident's words), and a real HTTP 5xx on /op with no Worker document at all — the edge's page — is read by the same rule and carries the signal too; the repo command's reader words it (— the resident was unavailable for a moment; re-run the command), so a platform blip is told apart from a failure in the op. The strike after the wake wait (residentWakeStrike, residentWakeBudgetStrike, resident-repos.md item 65) carries the wait's origin (WakeOrigin: a container's exit, or a refusal the Worker typed transient, where no container exited) as its classification code — container-exited, the one the tracker counts, or transient-refusal — and in its sentence (waited "to wake", or "to come back"), and the one decision on the last engine view (wakeStrikeReason): a budget that ran out while the resident was still coming back is worker-unavailable — the client's budget is the command's own, the harness's restore-window bound is a longer clock, and one restore gets one wait whatever route the resident answered by (harness.md item 6) — while a definite view (down, onboarding, a Worker that did not answer /status) is refused; a re-attach that failed as infra inside the wait ran under that bound, and its transport verdict still says nothing about the resident: the strike reads the same engine view; the run's own stop is the exception — its signal rides into that re-attach and into every /status probe of the wait as into every send, and every stop point throws the stop's one typed shape, ExecInfraError with reason aborted classified as the transport: the re-attach's as the call built it, the pause's and the probe's as wakeStopped builds it — never a plain error one caller unwinds and another counts, never a strike on the unreachable view the stop itself produced. The request-failed sentence is one builder for every client (requestFailedSentence(subject, host, route, err, recheck): the two executors through requestFailedMessage(worker, route, err), the resident admin client, src/core/residentAdmin.ts, with its own nouns and a re-check that names repo list — nothing special-cases a string), and both executors' request-failure sites pass the run's signal, so a stop is aborted and never the transport lost — when the exec transport itself fails — the sandbox unreachable (network), an HTTP/worker error, the sandbox's in-body "Command execution failed" / exitCode-127 signal, a PRESENT-but-empty in-body error ("" — the Worker's failure shape with its text missing, item 3; a success body carries no error key, and a bare exit 127 with no output and no error key stays an ordinary command result — foo 2>/dev/null is a legitimate silent 127), or a worktree still unavailable after a re-attach. When that in-body failure says the container went away under the command — a typed 0.12.x error (SessionTerminatedError, OperationInterruptedError, matched by NAME because the Durable Object RPC boundary keeps name/message and drops the prototype; isRecycleError) at any elapsed time, or a recycle-shaped text (0.12.x: Session '…' shell exited (exit code: n), The sandbox container stopped while the operation was pending., and The sandbox was destroyed while the operation was pending. — the disconnect a destroy() under a pending call produces; 0.3.x: Command execution failed, Session terminated, Session '…' not found) more than a minute into the attempt (per attempt — a retry gets its own clock) — the sandbox Worker rewords it as sandbox recycled mid-command after <n>s — the container's runtime was replaced or restarted under the command, which did not finish; check /workspace before continuing: it is empty if the container was replaced (re-clone), intact if only its runtime restarted (<original>) (recycledMidCommandMessage) — still exit 127, never a faked exit 124: it IS an infra failure and the command did not finish, so the model looks instead of shortening a command that was never the problem. Inside the Durable Object the decision is typed (isRuntimeReplacement: StaleProcessHandleError, RuntimeIdentityInactiveError, an OperationInterruptedError whose reason is a replacement — runtime_replaced, container_stopped, sandbox_destroyed, sandbox_lifetime_changed —, an RPCTransportError whose kind is a loss, or the SDK's replacement wording anywhere in the cause chain, the same list the resident Worker uses), so the wording is certain; the fetch handler's catch keeps the by-name and by-text fallback for anything the Durable Object threw unnamed. A runtime that did not answer is named (runtime-unreachable, src/execution/sandboxErrors.ts): every command and file operation reaches the container through the SDK's control connection to its port; when nothing answers there — the server exited and item 21's supervisor is starting it again, or the runtime is wedged — the SDK's connect aborts after 30 s with the bare The operation was aborted (isRuntimeUnreachableSignal, the same signal the resident Worker classifies, src/execution/residentRefresh.ts). The Durable Object catches it at the spawn and on the file operations (never after a typed replacement, which is item 9's recycle) and builds SandboxRuntimeUnreachableError, whose message starts with the token and carries the container id (the Durable Object's id, the same word the containers log dataset carries), the SDK pin, the platform's running flag and the SDK's text as the cause: runCommand answers /exec in-body with reason: "runtime-unreachable" in the item-3 dual shape; the file methods throw it, and the fetch handler matches it by name or token after the RPC boundary (isRuntimeUnreachableError) and answers /read and /write as HTTP 503 with the same reason. The executor never re-sends /exec and re-sends a 503 on its ordinary transport retry (3 s, 6 s, 12 s); both end as ExecInfraError carrying the text, so the card names the cause and the container. Both remote executors do this (CloudflareSandboxExecutor, ResidentExecutor); ExecInfraError extends Error, so message/instanceof Error callers are unchanged. This is the seam every caller reads a dead sandbox from: the pi harness's container operations and the relayed tools take an ExecInfraError as the failure of that one operation, named (harness-pi.md item 5 — the native loop's two-strikes fail-fast, ExecHealthTracker, went with the loop), so a wedged sandbox is told apart from a command the agent should keep handling. Two things are deliberately NOT infra: a resident's answer that the container under the thread is gone — the named runtime-replaced (a deploy swapped the runtime under one command — resident-repos.md item 43) or the preflight's worktree-missing (the container disk was recycled — item 27) — is, on /exec, the typed ExecSandboxRestartedError at once, the same word the item 65 wake ends in, so the pi harness ends the run for a restart from its request (harness-pi.md item 16); on the idempotent routes a runtime-replaced is re-attached and re-issued once, and only a second one there with no successful op between becomes ExecInfraError, so a single deploy can never read as a dead sandbox. And a full sandbox fleet (fleet-busy, item 14) is capacity: the executor waits for a slot and, when the wait is spent, throws ExecCapacityError — a separate class the tracker neither counts nor resets on — so a fleet at max_instances can never read as a dead sandbox. Every ExecInfraError the resident executor throws is classified for the spans around it (tracing.md item 2): a failed request transport (timeout when the deadline fired), a non-200 http with the status as the code, a worktree still gone after re-attach infra/attach, a repeated runtime replacement infra/runtime-replaced, an in-body exec error infra; a GitHub API failure is http with its status.

  10. Container toolchains: the sandbox image (deploy/cloudflare-sandbox/Dockerfile) carries git + gh + Node + npm + pnpm + bun (installed globally with npm; image and SDK move together); the resident image (deploy/cloudflare-resident/Dockerfile) carries git + Node + npm + pnpm + yarn (classic; a Berry repo bootstraps through its committed yarnPath release) + bun — every package manager the onboard-time detectCommands (resident-repos.md item 52) can name, so a detected install command always exists where it runs. Node is the repository's own line in every image, never the base's: the cloudflare/sandbox bases ship Node 22 (the 0.12.9 and the 0.13.0-next lines alike; the 0.3.x base shipped 20, which could not load 15 suites), while the repository builds and tests on Node 24 (.nvmrc, CI's setup-node, the install script) and its published CLI requires >=24 — so a suite run inside an image on the base's Node reads a different verify than CI does: npm ci warns on the CLI package's engines, and a test can pass on one major and fail on the other. Each execution image therefore copies Node from a stage of its own at an exact node:<version>-slim tag, pinned to linux/amd64 (the one platform the base publishes, so the copied binary runs in it whatever the build host is) — the base's npm tree removed first, then the binary, npm's tree and the headers at the base's own paths, so its npm/npx symlinks keep resolving — before any global install, and asserts that exact version at build time, so a base or stage bump that moves Node fails the build, not a run; the bot image builds FROM the same tag, and src/deploy/imageNode.test.ts holds the three tags equal and their major equal to .nvmrc's. The base's container server is a bun-compiled binary and does not run on that Node, so the swap changes only what a run's commands see. pnpm was added to both so coding/build runs in pnpm repos no longer hit pnpm: command not found. Every toolchain version is pinned exactly and asserted at build time, and src/deploy/imagePins.ts + imagePins.test.ts fail the suite if a floating tag returns (resident-repos.md item 53): both images installed pnpm@latest, so the pnpm major tracked the last image build rather than a commit — a routine rebuild moved it 10 → 11, pnpm 11 stopped reading package.json's pnpm field, and a repo keeping its overrides/patchedDependencies/onlyBuiltDependencies there fails --frozen-lockfile. pnpm sits on the 10 line on purpose (10.x reads that field; pnpm ≥10 self-manages packageManager, so the pin is a floor a repo can raise for itself — which also means the version baked in is not necessarily the one that runs, and a repo with a packageManager field fetches its own at install time). bun is pinned for the opposite reason: it does not self-manage packageManager, so the baked version is the one that runs, and the bases' own bun (1.3.x, a real binary at /usr/local/bin/bun) rejects a bun.lock at lockfileVersion 3 — bun 1.4's stamp for scoped overrides — as Unknown lockfile version, so every install in such a repo failed; both images remove the base's binary and install bun at one exact pin, at or above what an onboarded repo pins (resident-repos.md item 53). Both images carry one toolchain for what a run builds and looks at (src/deploy/imageToolchain.test.ts holds the two Dockerfiles to one shape): python3, make and g++ — node-gyp's needs, so a native module (node-pty) rebuilds from source in a cold npm install instead of dying on "no Python"; ffmpeg (Ubuntu's, with libx264), so an agent pulls frames out of a video to look at it (ffmpeg -i in.mp4 -vf fps=1 frame_%03d.png) and encodes video from frames or a screen recording; and a headless Chromium through Playwright at an exact pin — screenshots, PDFs and recordVideo, from playwright screenshot / playwright pdf / require('playwright'). The browser is Playwright's own build, never apt's: on Ubuntu 22.04 the chromium package is a snap stub that does not run in a container. Only the headless shell is installed (--only-shell; there is no display) with its system libraries (--with-deps) and the fonts pages render text with (fonts-liberation, fonts-noto-color-emoji); PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright is set BEFORE the install so the browser lands outside root's home, and the tree is opened a+rX, because the resident runs every agent command as an unprivileged workerN; NODE_PATH=/usr/local/lib/node_modules makes require('playwright') resolve from any working directory; Chromium runs with --no-sandbox (Playwright's default chromiumSandbox: false), which is what a root process, or one without user namespaces, needs. Every piece is proven by the layer itself, so the BUILD fails, not a run (CI builds every image on every pull request): the compilers answer --version, ffmpeg encodes a one-second testsrc clip to h264 and decodes a non-empty frame back out of it, the playwright CLI is on PATH at exactly the pin, and a real headless screenshot of a data: page lands as a non-empty png — in the resident image a second time as worker1, after the user pool exists, so an unprivileged thread user is proven able to drive the browser. The proof artefacts, the apt lists, apt's .deb archive (the base has no docker-clean hook; 400 MB stayed behind without apt-get clean) and npm's cache are dropped in the same layer. The prompt of every preset with a workspace (coding, review, explore, and the resident variants) names the toolchain in one sentence — the model reaches only for what it has been told is there; the cold prompts offer Docker, the resident prompts say there is none. Every image also carries pi (@earendil-works/pi-coding-agent) at one exact pin, proven at build the same way — the coding harness a run's container can start in place of the native loop (harness-pi.md item 3), and the bot image's for the presets without a workspace, whose pi is a child of the bot (harness-pi.md item 12). Every image also carries OpenCode (@opencode/cli) at one exact pin beside pi, the wrapper's install script allowed by name and opencode --version proven at build — in the resident again as worker1 — the second harness's process (harness.md item 12), dark until a deployment names it. Changing any image requires a container rebuild + redeploy to take effect.

  11. Hard-stop cancellation: Executor.exec(command, { signal }) carries the run's hard-stop AbortSignal. LocalExecutor kills the child process (the abort surfaces as an exit … line, never a throw); ResidentExecutor and CloudflareSandboxExecutor join it with their per-call deadline (execDeadline) so the bot-side request drops at once — dropping this request does not prove the remote process stopped. The authorized Hard Stop control separately fences an exact registered resident or dedicated run sandbox. A native read-only review with original admission target receipts closes both isolated namespaces before backend selection; it reports completion only after both acknowledge. Older admissions keep their existing target verification requirements. Resident cancellation rejects later original-owner requests, waits for admitted native work, kills only the independently verified owned runtime's UID, independently observes no remaining UID processes, and retains files and UID ownership. If the original runtime is already replaced, native-only observations may instead confirm a stable different boot and no UID processes under the exact owner and high-water fence, only when every outstanding native submission is independently confined to that dead epoch and has no remaining launch authority. Unbound legacy or lost SDK submissions remain unverified. That separate runtime-retired result preserves bytes and UID ownership. If an exact registration proves a new kernel of the same original owner, UID and workspace, shutdown must stop and independently observe that current runtime before crediting old-kernel retirement; a foreign successor is never signalled. Dedicated sandbox cancellation destroys only the prepared run slot. Unknown native or terminal results remain stopping; unsupported runtimes keep their ordinary stop and release path. E2BExecutor checks the signal before dispatch and rejects a stopped native advance after command completion (its command API cannot cancel an in-flight command) — safe because the runner races every tool await against the signal and stops waiting regardless — and gains release(mode): "always" (a read-only run or a hard stop) kills the micro-VM, the only way to end an uncancellable command; "if-idle" keeps it for the thread (the idle timeout reclaims it). The dispatcher requests release with "always" after any hard stop; resident eviction still requires its preservation guard (resident-repos.md item 16a). A cancellation acknowledgment is no permission to remove retained files or reuse a spent UID. Every remote exec has a bot-side deadline of budget + EXEC_CALL_MARGIN_MS — ResidentExecutor and CloudflareSandboxExecutor run every send (headers AND the streamed body — /exec answers HTTP 200 at once and heartbeats until the outcome, so the body read is where a dead sandbox's wait sits) under execDeadline(budget + 30 s, signal), the budget being the command's clamped timeoutMs for /exec and BASH_TIMEOUT_MS for a file op; a Worker that never answers is an infra failure — ExecInfraError naming both numbers (sandbox worker /exec gave no answer within 330s (command budget 300s + 30s margin) — the sandbox may be gone; the command may still be running in it), which fail-fast (item 9) counts — never an indefinite wait. The deadline is per send: a fleet-busy wait (item 14) does not eat into it. execDeadline is a plain timer, not AbortSignal.timeout (Node runs that one on an internal timer no test can drive). Without it a run can wait indefinitely on one /exec: the sandbox container gone (There is no container instance…), the Worker still heartbeating, a client that passes only the hard-stop signal and reads the body with no deadline, and a runner that checks its wall clock only between steps — nothing fires until an operator hard-stops it.

  12. Per-call bash timeout (timeoutMs): the bash tool's input schema carries an optional integer timeoutMs, Claude Code-style — the tool description names the default and the max, which is how the model learns to ask for more time (a long probe dies at a flat 5-min cap, and npm test/npm run build brush it). Policy lives in ONE module, src/execution/bashTimeout.ts, bundled into both deploy Workers: default BASH_TIMEOUT_MS (5 min, unchanged), ceiling BASH_TIMEOUT_MAX_MS (20 min), floor 1s. Clipped to the run: the ceiling alone does not stop one command from eating a run (20 min is 80% of the review agent's 25-min budget — one first command can take all of it and leave ~4 min), so the harness hands the relayed tools the run's remaining wall clock (ToolContext.remainingMs) and a tool that waits keeps RUN_DEADLINE_RESERVE_MS (60 s) back from it (await_runs, attach_file's render — agent-conductor.md item 8); the shell is pi's own tool now, run in the container under pi's per-call timeout (seconds, none by default), judged at the harness's gate by that explicit timeout — one reaching past the loop's end is refused before the command runs (harness-pi.md item 7) — and the loop's end cuts whatever runs without one (harness-pi.md item 15); the native bash tool's timeoutMs argument and its clip to the remaining wall clock went with the native tool table. The documented clamp rule (clampBashTimeout): a finite number is truncated and clamped into [1s, 20 min] — so 0/negative run at the 1s floor and a 25-min ask runs at 20 min — and anything else (absent, NaN, a string) falls back to the 5-min default. Every executor honors it: LocalExecutor maps it onto execFile's timeout (maxBuffer kept); E2BExecutor onto the SDK's commands.run({timeoutMs}); ResidentExecutor puts it in the /exec body only when the caller asked for one (an older resident sees the body it always did — attach's readonly/sha convention) and stretches its HTTP wait to timeoutMs + EXEC_CALL_MARGIN_MS (30s) so the resident's own streamed exit-124 answer wins the race against the client's transport deadline; CloudflareSandboxExecutor does the same body-only-when-asked, and the sandbox Worker sizes its coreutils timeout from it. Servers clamp with the same bounds and never trust the client's number (resident handleExec, sandbox Worker /exec — both via the shared clampBashTimeout). A deadline kill is exit 124 with a line naming the limit that fired and the timeoutMs knob (max included), so the model self-corrects instead of reading a generic abort. The resident's deterministic /op path keeps the flat 5-min budget — it has no caller knob. Back-compat & deploy ordering: no timeoutMs → byte-for-byte today's behavior; a resident Worker predating this change either ignores the field (very old — command still capped at its 5-min budget, answered legibly as exit 124 while the client clamp bounds the wait) or 400-rejects a value above 5 min (the immediate predecessor's parsePositiveInt), so the resident deploys before the bot, as always. Same for the sandbox Worker + its Dockerfile backstop (container rebuild).

  13. Every Worker reports the commit it was built from, and nothing is hand-bumped (without it a deploy's receipt has to be assembled from wrangler versions list plus a container image digest, because a Worker's /healthz cannot say which build is at the edge). The bot always could: npm run deploy writes build.json from the tree's HEAD (deploy/cloudflare/write-build.mjs) — and the release's publish-image job writes it from the release commit before building the bot's image, so a container from the published image (release-and-deploy.md items 21, 25) is stamped too — the image COPYs it, /healthz serves build: {commit, builtAt}, and deploy all's live gate compares it to the commit it deployed (slack-channel.md item 8). The three Worker SCRIPTS now do the same, and the resident's hand-edited const BUILD_MARKER = "perf53" is gone: its comment said "bump on every deploy-worthy change", it was bumped three times in total (gc51 → head52 → perf53) and then went unbumped across five deploys — so it could neither prove a deploy nor expire a stale test override, which is exactly what resident-repos.md item 49(c) (ignored:"stale-build <marker>") depends on. Mechanism: deploy/bin/build-stamp.mjs replaces bare wrangler deploy in each Worker-script npm run deploy, derives {commit, builtAt} from the tree (a -dirty suffix when it is not clean, because wrangler bundles the TREE and not the commit), and passes them as wrangler deploy --define SWITCHBOARD_BUILD_COMMIT/SWITCHBOARD_BUILT_AT; src/deploy/buildStamp.ts is the only reader (injectedBuildStamp), gating both reads behind typeof because an un-stamped bundle's identifiers do not exist at all — that path answers commit: "unknown" instead of throwing inside /healthz. --define and not --var: a CLI --var may replace the vars a Worker's wrangler.jsonc declares, and the resident's STATE_WORKER_URL is load-bearing (the watchdog records firings through it). A generated FILE cannot serve a Worker script either — committed, a deploy would dirty the tree (which deploy all refuses); gitignored, tsc and a fresh clone break on the missing import. Surfaces: resident GET /healthz → {ok, build} (was {ok, u}); memory GET /healthz → {ok, build, features}; sandbox gains GET /healthz → {ok, build} behind the same SANDBOX_TOKEN bearer as every other route — a build stamp does not justify a new unauthenticated surface on the Worker that proxies command execution.

  14. A full fleet is capacity, not a dead sandbox (fleet-busy): the per-thread sandbox Worker runs at most max_instances container instances (deploy/cloudflare-sandbox/wrangler.template.jsonc, 250 — billed per AWAKE instance; the source template retains this ceiling across deployments until a deliberate capacity change); the thread past that ceiling gets no instance, so @cloudflare/sandbox 0.3.x cannot create a session and throws the bare Failed to create session: 503 (the platform's own wording is "no container instance that can be provided…", and since the 0.13 line also "Maximum number of running container instances exceeded. Try again later, or try configuring a higher value for max_instances" — the phrase the 0.13 SDK's own warm pool matches on — and the platform's start-rate limit, "You are requesting too many containers per second", met by a burst of fresh threads (twenty-four seeded sandboxes started within twenty seconds had a third of their starts refused with it), which is capacity for a moment exactly like a full fleet; the 0.12 and 0.13 lines throw a typed ContainerUnavailableError, code CONTAINER_UNAVAILABLE, keeping the platform's wording as its message — the Worker takes the type by name first, isFleetBusyError, and the text second, every phrasing above). Nothing ran and nothing is broken. The contract: (a) the Worker recognizes those messages with the shared isFleetBusy (src/execution/sandboxErrors.ts, node-free, bundled into the Worker) and NAMES the condition — /exec finishes its streamed body with {error: "fleet-busy: <explanation> (<sdk message>)", reason: "fleet-busy", stdout: "", stderr: <same>, exitCode: 127} (the item-3 dual shape kept for older executors), /read and /write answer HTTP 503 {error, reason: "fleet-busy"} — the same named-reason precedent as the resident's mirror-busy; re-sending is safe by construction because session creation fails before any command or file op starts. A container still starting is the sibling token sandbox-starting (item 23), waited on by the same executor loop under its own budget. (b) The executor (CloudflareSandboxExecutor.call) matches the machine token only (reason === "fleet-busy", in-body or on a 503 — an OLDER Worker's bare message stays an ordinary in-body error, so a bot deployed ahead of its Worker changes nothing) and re-sends the IDENTICAL request — same route, body, headers, the envs resolved once per call — after 10 s, 20 s, then 30 s, until the total wait reaches min(the command's own timeoutMs — the 5-min default for a file op —, FLEET_BUSY_WAIT_MAX_MS = 5 min) (during pre-model setup, item 33, the original admission deadline applies); a hard stop ends the wait at once with the typed ExecInfraError/aborted stop, never a capacity refusal. Every OTHER in-body error is still ExecInfraError after exactly one send. (c) When the wait is spent it throws ExecCapacityError (executor.ts; extends Error, NOT ExecInfraError) whose message names the wait: sandbox fleet busy: no per-thread sandbox was available after waiting 300s; this request did not run. (d) On pi the container is provisioned at the workspace attach, before pi starts, so a full fleet is met there and never inside a turn; a relayed tool that meets it is that one call's failure, named. The fleet_busy note kind (parsed by runEventLines, a medium infra_failure finding in the friction analyzer) was the native loop's — it returned the tool result as ⏳ Sandbox fleet busy — <message>. Retry the command in a minute or finish with what you have. and went on — and stays a record fact from before the loop's deletion. The shape this closed: a burst of cold runs exhausts max_instances, one run gets two identical Failed to create session: 503 errors seconds apart, the breaker reads them as a wedged sandbox, and the run aborts with a "dead sandbox" diagnosis for a fleet that was merely full. Deploy order: the sandbox Worker before the bot — an old Worker's plain in-body error still reads as infra (today's behaviour, unchanged).

  15. Every executor operation is an exec.* span (tracing.md item 17). The relay hands each relayed tool call a TracingExecutor (src/execution/tracingExecutor.ts, a Decorator over the run's executor; harness-pi.md item 7): exec → exec.exec, readFile → exec.read_file, writeFile → exec.write_file, and exec.release / exec.move_to only when the wrapped executor has them — each a log-only span under the tool call's span carrying the backend and the per-call timeoutMs, never the command, the path or the output; a throw ends the span error and propagates unchanged. ExecutorSelection.backend names where the run's commands execute — local (the local executor, and the null executor an agent without a repo gets), e2b, sandbox (the per-thread Cloudflare sandbox), resident — so the spans of a resident-backed run and a cold run tell apart. The wrapper hands each exec.* span to the inner executor as opts.span (ExecTraceOptions on every Executor method), so a resident's or a sandbox's HTTP call is that span's http.client child (tracing.md item 21).

  16. The cold sandbox is the platform's largest predefined instance type: deploy/cloudflare-sandbox/wrangler.template.jsonc sets instance_type: "standard-4" — 4 vCPU / 12 GiB / 20 GB; a custom type can be no larger. A cold run clones, installs and checks a whole repository in one container with nothing warm, so the size is decided by the largest single command a cold thread must be able to run, not by the typical one: a large monorepo's typecheck alone needs more than 8 GiB (its own CI notes put the root program near 8 GB and each sub-program near 4 GB), and the previous standard-3 (2 vCPU / 8 GiB / 16 GB; a live probe read nproc 2, 8189 MiB, 15 GB on /) could not run it at all. The cost is per AWAKE instance — a sandbox sleeps after 5 idle minutes (item 1) — so the step is paid per active run-hour: 12 GiB against 8 of memory, and vCPU only while the cores are busy; the fleet ceiling (item 14) keeps ~15× headroom on the account's containers limits (6 TiB memory / 1500 vCPU against 25 × 12 GiB and 25 × 4 vCPU — the vCPU line is the tighter one). The resident reaches the same 4 / 12 GiB / 20 GB from the other direction (sixteen threads sharing one container, resident-repos.md item 55). Both execution images set NODE_OPTIONS=--max-old-space-size=8192, so one Node process may take 8 GiB of the 12 for its heap: V8's own default is a quarter of physical memory (~3 GiB here), and the resident image once pinned 1,536 MB — an eighth, meant as a fair share among its sixteen seats — under which this repository's own two tsc passes die with "heap out of memory" (they pass at 3,072 MB with ~2 GB of RSS each). A per-process heap limit never shared memory between processes; the cgroup does. The cap is a runaway guard sized for the largest legitimate command, with room, and the same in both images.

  17. Docker in the cold sandbox: the sandbox image installs Debian's docker.io engine and iptables in its apt layer, and docker on PATH is a wrapper (deploy/cloudflare-sandbox/docker-wrapper.sh, installed as /usr/local/bin/docker ahead of /usr/bin/docker) that starts the engine on first use: when /var/run/docker.sock is absent or docker info fails it enables net.ipv4.ip_forward (the default bridge needs it for containers to reach the network), starts dockerd in its own session (setsid -f, log at /var/log/dockerd.log), waits up to 40 s for docker info to answer — failing with a message that names the log — and then execs the real client with the caller's arguments. Every later docker call in the same warm container finds the engine running and passes straight through. Why in the image: a daemon installed at run time is lost with the container after its idle sleep (item 1) and costs minutes per run. Why its own session: every /exec runs under timeout -k 10 … bash -c (item 2) and its process group is reaped when the command returns, so a daemon started with nohup … & dies with the command that started it; a setsid daemon survives to the next one. Why it is safe to ship: the sandbox already runs as root with full capabilities inside its own Firecracker microVM, so a container a run starts lives inside that run's VM and shares its blast radius — the engine adds no privilege the run did not already have, and no privileged flag, capability or Worker config changes with it. What it unlocks: nektos/act in its default per-job-container mode, and repositories whose tests need containers (testcontainers, compose, image builds), which a cold sandbox could not run before.

  18. Machine classes (record 0026): AgentDef.machine is one of none | blank | repo-cold | repo-resident, and the executor factory, repository resolution and the repository gates read the class and nothing else about the agent (machineNeedsRepo is the one fact the resolver and the gates take from it). none: the null executor (item 7). blank: the per-thread backend with an empty workspace — no repository is resolved or gated, whatever the context carries — and no credential (nothing says a blank run acts as anyone); the resident registry and Worker are never touched. repo-cold: the per-thread backend with the checkout and the run's credential — scoped like the sandbox token, by the profile's identity (item 5; a none identity vets anonymously) — even when the repository has a serviceable resident. A bare owner/name is vetted against GitHub with that same credential (githubRepoProbe: one GET /repos/{owner}/{name}; 200 binds, 404 refuses, any other status, a transport failure or a credential that cannot be minted is unreachable — could not verify, never a bind and never a refusal; without a credential the vet is anonymous, so a private repository reads as 404, as it would to the run), a bare in <name> stays prose (the registry listing is never read), and the resident registry and Worker are never consulted, so an outage there can neither refuse nor delay the run. Its refusals name GitHub, never onboarding: a 404 closes the card repo not visible and replies that the repository is outside the Switchboard GitHub App installation or misnamed (repo_not_visible, with or without a resident fleet); an unanswered vet is repo_unverified with GitHub as the silent party; a visible repository meets the per-repo access gate like every class. repo-resident: items 7–8 and resident-repos.md items 24 and 29 — the onboarded resident when serviceable, else the cold fallback with its note, the registry vet included. Cold is the class for repo-cold and a fallback for repo-resident: only the fallback carries a note. explore is the first preset on repo-cold (agent-explore.md item 3); no preset declares blank yet, so that class is proven with a synthetic agent. In the hosted bot, execution: local admits only none: any preset whose effective profile names a workspace machine is refused by preset name before attach with the one workspace-backend sentence; local workspace machines remain available only to a one-shot CLI ask.

  19. A binary read on the seam (src/execution/binaryRead.ts): Executor.readBytes?(path) hands back a workspace file whole, as bytes — the screenshot or PDF a run produced, for a tool that needs the bytes rather than readFile's text view. One cap for every implementation, MAX_READ_BYTES (10 MiB): a binary cannot be truncated the way text output is, so a larger file is refused by name, never trimmed. LocalExecutor stats then reads; the two remote clients ask the same /read route for encoding: "base64" and decode the answer (decodeBase64Read). Both Workers accept the body: no encoding is the text read every older client sends, anything but base64 a 400 by name; both Workers stat the file first (as the thread user on the resident) and judge the cap on that size before any read. The sandbox Worker then reads through the SDK's base64 encoding; the resident reads in chunks — READ_CHUNK_BYTES (1 MiB less one, a multiple of 3 so the pieces' base64 concatenates without padding) through tail -c +N | head -c L | base64 -w0 as the thread user — because one command's stdout crosses the sandbox SDK's process log stream, which cuts a stream past a retention limit its typings do not name (about 2.3 MB observed) and reports the cut only as a truncated flag on the output; a 12 MB file once came back as 1.7 MB and was handed on as complete. The DO's run() now surfaces that flag, a chunk it marks or whose length is not the expected base64 length fails the read as read-inconsistent (409, not a 5xx the client would retry for 30 s), and the sandbox Worker refuses the same way when the SDK's read is not stat's size. The answer is { encoding: "base64", content, size } or { encoding: "base64", tooLarge: true } — HTTP 200 either way, because the clients classify a non-2xx or an in-body error as a sick Worker and a large file is the model's mistake, not infrastructure. The client decodes and refuses bytes that are not size (read-inconsistent), and an answer without a size as one that predates the verified read. A Worker that predates binary reads ignores the field and answers text: the client names it as a rollout gap ("redeploy it") rather than decoding text as base64. The method is optional, and the decorators (ExecHealthTracker, TracingExecutor — exec.read_bytes) carry it exactly when the inner executor has it, so a tool that finds it absent tells the truth; the e2b backend has none.

  20. The artifact store (record 0033; src/artifacts/): where a run's files move by reference when the artifacts: section is configured — one private R2 bucket the bot signs URLs for and never carries a file through. The seam ArtifactStore { presignPut(key, contentType), presignGet(key), head(key), get(key, { range? }), copyFromUrl({ url, size, key }) } (get opens the object as a stream from a signed GET minted per call — what the run page's proxy route pipes, live-view.md item 26; the one place the bot's process touches an artifact's bytes, in transit; with a single byte range — parseByteRange admits bytes=start-end, bytes=start- and bytes=-suffix, nothing else — it answers the slice with part naming the offsets against the whole size, the whole object when the store ignores the range, or unsatisfiable with the size when the range starts past the end; the range header rides beside the signature, unsigned, as S3 clients send it and aws4fetch leaves it) has two implementations: R2ArtifactStore over R2's S3 API (<accountId>.r2.cloudflarestorage.com/<bucket>, region auto, unsigned payload, aws4fetch as the signer) and InMemoryArtifactStore for tests. Every presigned URL admits a request for PRESIGN_TTL_SECONDS (600) and names one key; a PUT URL signs the content type so the object's type is the one the tool derived; head answers size and type or null; copyFromUrl asks the bot's own Worker (POST /artifacts/copy, bearer ARTIFACTS_COPY_TOKEN, bounded by artifacts.inbound.copyTimeoutMs) and accepts only an answer naming the key and size it asked for. Keys are runs/<runId>/out/<seq>-<basename> for a produced file and threads/<threadKeySafe>/in/<messageTs>/<index>-<basename> for a received one, the leaf unique per file; safeBasename reduces a filename to [A-Za-z0-9._-] (runs collapsed, never empty, never a run of dots) so it is shell-, URL- and key-safe by construction; contentTypeFor derives a produced file's type from its extension once, application/octet-stream otherwise, and INLINE_IMAGE_TYPES names the four raster types the run page renders inline. The section (r2.accountId, r2.bucket, retentionDays, inbound.maxBytesPerMessage, inbound.copyTimeoutMs) is validated at load like every other, an unknown key refused by name; three optional bot secrets (ARTIFACTS_R2_ACCESS_KEY_ID, ARTIFACTS_R2_SECRET_ACCESS_KEY, ARTIFACTS_COPY_TOKEN) travel the three-file secret contract, and a configured store with any of them or PUBLIC_BASE_URL missing fails startup by name, never downgrades. /healthz carries artifacts: { bucket } when configured and no key otherwise; bucket privacy is the operator's artifacts check, not a runtime probe (R2's S3 endpoint refuses every unsigned request whether or not the bucket is public). Absent section: no store, and every surface behaves as before it existed. The outbound path (attach_file through a presigned PUT and the channel's upload ticket, agent-coding.md item 10) and the run event and proxy route (live-view.md item 26) are built on it. The inbound half's transport is the bot Worker's copy route (deploy/cloudflare/artifactsCopy.ts, POST /artifacts/copy { url, size, key }, what R2ArtifactStore.copyFromUrl calls with ARTIFACTS_COPY_TOKEN): the Worker holds the two things the copy needs and the bot never does — the ARTIFACTS R2 binding and the Slack bot token a url_private requires — so the bytes flow Slack → Worker → R2 and the bot only asks. In order, each refusal before the next step: the copy bearer, compared in constant time (401 — an unknown caller learns nothing, not even whether a bucket is bound); the request's shape before any fetch (url must be https: on files.slack.com, slack.com or a *.slack.com host — 400; size a positive integer within the 1 GiB ceiling — 400/413; key an inbound key as inboundKey builds them, under threads/, one character class, no dot segments — 400); no binding or no Slack token → 503; then Slack's answer before any put — the token rides in the Authorization header and redirects are not followed, so a 200 text/html (Slack's login page: the token does not reach the file) is refused as unauthorized (502), any status other than 200 is Slack's word (502), and a Content-Length absent or other than size is 409 naming both numbers. The body flows through a FixedLengthStream(size) into one put(key, stream, { httpMetadata: { contentType } }) with Slack's content type: a stream that ends short fails the pipe, the put rejects, the bucket keeps nothing and the route answers 502; success answers { key, size }. GET /artifacts/copy with the bearer answers { bucket } — the name the Worker binds — so the operator's check can hold it against the config's (bucketMismatch, src/artifacts/agreement.ts: the same name, or a problem naming both; a Worker binding none says so). The binding is rendered only when the deployment profile names a bucket (deploy/profile.json artifacts.bucket, R2's bucket-name rules, optional and strict): the bot template's {{#if artifacts}} block renders r2_buckets: [{ ARTIFACTS, <bucket> }] and the ARTIFACTS_BUCKET_NAME var together, and a profile without one binds nothing. npm run deploy in the bot Worker's directory creates every bucket the rendered config binds before wrangler deploy validates the binding (ensure-bucket.mjs: wrangler r2 bucket create, "already exists" is success, any other refusal is wrangler's and stops the deploy); the plan asks the credential for wrangler r2 bucket list (Workers R2 Storage: Edit) on the bot step only when the profile names a bucket. Inbound staging rides on that route. With a store configured the Slack adapter leaves the files NEITHER inline pass could carry — a video, a 6 MiB PNG (over the 5 MiB image cap), a PDF over the 10 MiB document cap, a zip — on Slack by reference (stagedFiles, IncomingMessage.staged: name, size, type, url_private, the message's ts; metadata only, the inline caps unmoved, a 1 MiB PNG still inline), each within Slack's 1 GiB per-file ceiling, at most ten per message and within artifacts.inbound.maxBytesPerMessage (2 GiB by default); the rest stay skipped with the reason in the message's note, and the secret-file denylist refuses staging exactly as it refuses inlining — a .pem is never copied anywhere. Without a store nothing is staged and the note reads as before. The reference rides every path a message takes: a follow-up steered into a live run (FollowUpInput.staged, the durable inbox row — a few hundred bytes on the base row, so it survives the restart even when the row drops its inline attachments), and a fresh turn (mergeFollowUps concatenates them). For a run whose agent has a workspace (machine other than none) the dispatcher starts the copies right after the run's row exists — copyFromUrl into threads/<threadKeySafe>/in/<messageTs>/<index>-<basename>, 1-based, so two files of one name take 1-a.png and 2-a.png — overlapping the workspace attach, publishes the artifact event (direction: "in", naming the message the file arrived with — messageId, the id the message's input event carries, live-view.md item 26) as each copy answers, then after the attach pulls each landed copy over the run's own executor as the thread user (src/core/dispatch/staging.ts: mkdir -p attachments && curl -fsS -o 'attachments/<index>-<basename>' '<presigned GET>', both operands single-quoted so clip"; echo pwned; ".mp4 is a filename; a resident first appends attachments/ to .git/info/exclude so its clean-check keeps reading clean) and appends one line to the request turn: Attached files are in ./attachments/: 1-clip.mp4 (312 MB, video/mp4), a failed copy or pull named with its reason (clip.mp4 could not be staged: …, curl's words, no retry). A steer into a live run is staged by the runner before the model reads the drained turn (RunOptions.stageFollowUps), the same line closing the follow-up prompt. A workspace-less agent copies nothing and its turn says this agent has no workspace for <name> (<size>, <type>); ask `agent:coding` to work with it. The thread's files are one catalogue — the runs' artifact events, in and out; the record is the catalogue and a file no run recorded is not reachable, by design — read one way (src/core/dispatch/threadAssets.ts readThreadAssets: every run of the thread, listed past one page by cursor, each record paged to its end since a steer's file lands wherever in the log the steer did; one entry per key, oldest run first, owned by the run that first named it; then the store asked by HEAD once per key whether it still holds the object — a list, a page or a HEAD that fails stops only what it was reading, with a logged line) and rendered three ways. (1) The re-pull: a new run in a thread that has run before reads the catalogue beside the message's own copies so both overlap the workspace attach, and pulls the in entries the store holds into attachments/ after the message's own files — they number after them on the same counter, are named from earlier in the thread in the line and recorded as this run's own in event so the run page serves them; a key the store no longer holds is named as expired and takes no index, one the store could not be asked about is named so; nothing is copied again and Slack is never asked; this run's own row is left out of the read (its files are the turn's). (2) The prompt: every run in a thread with files carries a FILES OF THIS THREAD block in the session block after memory (session-log.md item 10), one line per file — name, size, type, received or produced by which run, and where it is for this run: in the workspace at ./attachments/<index>-<basename> when this run pulled it (WorkspaceFiles, filled by every staging round), on its run's page for a produced file, no longer in the store when its retention passed. (3) recall: a hit whose turn names a file carries the file's key and the same where-clause, and recall {assets: true} lists the catalogue, read fresh, outright (session-log.md item 10). A steer into the live run stages only its own files, as before. A hosted ship seed is the one accepted-inline exception: Slack preserves the accepted file's by-reference twin, the coordinator fold promotes it to the coding child's staged list, and its original zero-based Slack slot lands at attachments/0-<name> while reserving the run's counter against reuse; zero-based slots are preserved only for that initial seed call, so every later steer allocates from the run counter even when its message-local attachment says slot 0. A resumed run stages nothing (its turn already carried the files). Both coding prompts say the files are already in ./attachments/ when the turn names them.

  21. The container outlives its runtime. The SDK's container server exits on any uncaught exception (process.exit(1) in its uncaughtException handler), and the base image runs it as tini's one child (ENTRYPOINT ["/usr/bin/tini", "--", "/container-server/sandbox"]; tini exits with its child) — so one such error ended the container: /workspace gone, a detached pi with it, and every later command a failure until the platform noticed. The crash class that showed this — a Bun 1.3.12 fs.watch race on the server's own session directory, on the 0.12.9 image — is gone on the 0.13 line, whose server is compiled with Bun 1.3.14; the defence stays for the next uncaught exception. The image's ENTRYPOINT is tini running deploy/cloudflare-sandbox/runtime-supervisor.mjs (dependency-free Node ESM on the Node the image ships; its loop takes its process factory, clock, sleep and signal source as arguments, so the tests drive it with a fake child and no wall-clock wait): tini stays PID 1 (it reaps orphans and forwards the platform's signals), the supervisor starts the server as a child and starts it again one second after it exits, so the disk, the detached processes and the platform's view of the container survive, and the next command starts a fresh process on the new server (item 4). A platform stop is SIGTERM, forwarded by tini to the supervisor and by the supervisor to the server, nothing starts again — a stop that lands during the pause between two starts, or before the new pid is known, still ends it — and the server's own status is the container's (the shell convention, 128 + the signal's number, when a signal ended it; 127 when it never started); a terminal's INT is forwarded as TERM too — the server has one shutdown path. Five exits in a row inside ten seconds of starting end the container with the last status instead of looping. Invariant: a runtime crash costs at most the command in flight — never the workspace, never a detached process, never the thread's container.

  22. The Durable Object judges idle and stuck deadlines, but unsafe teardown retains seeded work. On the 0.13 SDK line sleepAfter is not a deadline: at expiry the SDK asks its runtime whether any tracked process or terminal is still active and renews the timeout if so — or if the probe fails at all. A detached pi holds its command's stdio open, a bot deploy mid-run orphans that pi until the same thread's next run, and the container is awake for good: twenty-five sandboxes were found running 10–16 h after their last request against the 5-minute sleepAfter, with three runs in flight, and every new thread was answered with the item-14 refusal. The Worker therefore decides for itself (src/execution/sandboxIdle.ts, node-free, bundled into the Worker; IdleGuard, wired in SwitchboardSandbox): (a) every request the object serves — /exec, /read, /write — runs inside served(), which holds the request's start time in flight and records its finish, success or failure alike; the finish reaches storage at most every 10 s (PERSIST_EVERY_MS) and is the baseline for the object's next wake. (b) The verdict (idleVerdict, pure) proposes teardown when nothing is in flight and the last finish is SANDBOX_SLEEP_AFTER_MS (5 min, held equal to SANDBOX_SLEEP_AFTER by a test) or more ago, or when the oldest request in flight has been there INFLIGHT_STUCK_MS (the 20-minute /exec maximum plus 10 min) or more — requests are measured one by one, so the pi harness's 750 ms polls beside a 20-minute command never read as one stuck request; keep otherwise, and a clock that went backwards keeps. The guard asks the preservation fence before either teardown path: an attempted seed or unknown ownership without independently proven quiescence and an owner-bound complete checkpoint retains the container and checkout, even at the five-minute idle deadline or past the stuck threshold. A proven unseeded container may be destroyed; a missing marker alone is not that proof. (c) Two clocks ask for the verdict: a sweep the object schedules for itself through the SDK's schedule table (idleSweep, every IDLE_SWEEP_INTERVAL_MS = 60 s, re-armed after each run while a container is running or a request is in flight, armed again by the first request after a stop) and the SDK's own onActivityExpired, overridden to answer with the same verdict — its process probes never decide. (d) Only after the preservation fence permits it, a destroy is the SDK's clean destroy() (sessions closed, the container SIGKILLed at the end) bounded by DESTROY_GRACE_MS (60 s); when that has not finished, threw, or left ctx.container.running anything but false, the platform's own ctx.container.destroy() follows — a denied or failed fence never enters either destroy path. Overlapping deadlines destroy once. (e) On every wake of the object, including the one the SDK's alarm causes on a leaked container, wake() reads the stored baseline — or takes now when there is none — and arms the sweep, so an unseeded fleet leaked before this code arrived can stop within one window plus one sweep of the deploy; seeded or unknown legacy containers remain when the preservation fence cannot prove safety. Every actual stop is one JSON line in Workers Logs (sandbox.idle-stop with why: idle | stuck, idleMs, source: sweep | sdk-expiry, the thread; sandbox.idle-stop.forced when the platform kill was needed). Invariant: only a proven unseeded container is bounded to SANDBOX_SLEEP_AFTER plus one sweep interval; a seeded or uncertain checkout can outlive both that window and the 30-minute stuck threshold when no independent quiescence witness exists. Such retention consumes fleet capacity (item 14), rather than silently erasing unpublished work; an actual runtime crash can still lose the command in flight (item 21).

  23. A thread's first request never carries the container's start (sandbox-starting). The SDK's first exec on a container that is not running does the start inside the call — the platform's instance grant (its default wait 30 s), the image pull on a machine that has not seen the image, the microVM boot and the runtime's port (90 s) — so a slow start can outlive the executor's per-send deadline for a 60 s command (90 s, item 11). The deaths that led here — every run that fell to a fresh sandbox dying with "gave no answer within 90s", seven in one hour once the leaked fleet drained — turned out to be item 24's held output pipes, not the start (the Worker's logs showed the container answering commands in 230 ms ten seconds after the ask); the gate stays as the guard for a genuinely slow start, which the SDK's own allowances say can take two minutes. The contract: (a) the Worker's Durable Object runs every route through a start gate (src/execution/sandboxStart.ts, node-free, bundled; StartGate, wired in SwitchboardSandbox): when ctx.container.running is not true and no warm-up is in flight, it begins one — a single true through the SDK, which does the start, run inside the idle ledger so the guard (item 22) sees a request in flight for as long as the start takes — and answers at once; /exec finishes its streamed body with {error: "sandbox-starting: <explanation> (<phase>)", reason: "sandbox-starting", stdout: "", stderr: <same>, exitCode: 127} (the item-3 dual shape), /read and /write answer HTTP 503 {error, reason: "sandbox-starting"}; while the warm-up is in flight every request gets the same answer and no second warm-up begins; once the runtime answered, requests run. A warm-up that fails hands its error to the next request, once, where it is classified as any SDK error is — a full fleet is fleet-busy (item 14), a silent control port runtime-unreachable (item 9) — and the request after that starts a fresh warm-up if the container is still not running; a container the idle guard destroyed is started again the same way by the thread's next request. Every start is two JSON lines in Workers Logs, sandbox.starting and sandbox.started with durationMs (or sandbox.start-failed with the error), so the start time is a measured fact. (b) The executor (CloudflareSandboxExecutor.call) matches the machine token only (reason === "sandbox-starting", in-body or on a 503 — an older Worker changes nothing) and re-sends the IDENTICAL request after 5 s, 10 s, then 15 s (SANDBOX_START_BACKOFF_MS) until the total wait reaches SANDBOX_START_WAIT_MAX_MS (10 min — under a midday burst the platform granted containers 2.5 to 5 minutes late, and two threads that died at five minutes had theirs a minute away; the ladder is the token's, restarting at its first step when the answer's token changes, so a start that becomes a refused start is re-sent after the fleet's 10 s, not the 30 s the start's ladder had reached; the Worker asks the SDK for the instance grant with a 10 s limit, INSTANCE_GET_TIMEOUT_MS, so a refusal the platform took 63 s to voice under the SDK's 30 s default and retries reaches the gate in seconds) — whatever the command's own budget: the start is not the command's time, and a 60 s command survives a two-minute start it did not cause. The two tokens share one wait ledger; the plan (budget, backoff, exhausted message) is the last token's, so a start that turns into a full fleet is judged by the fleet's budget from then on with the time already waited counted. (c) When the wait is spent it throws ExecCapacityError — sandbox not ready — the thread's container did not finish starting within 600s; try again in a few minutes — never ExecInfraError: a slow start is not a dead sandbox. Invariant: the first command a thread runs on a fresh or restarted container is judged against its own budget from the moment the container is up, never against the start. Deploy order: the Worker before the bot, as for item 14 — an old Worker's first exec still carries the start, today's behaviour.

  24. A command's output ends when the command does; a detached child never holds the answer. On the 0.13 line an exec's answer is read from the process's log stream, which the runtime ends only when every holder of the command's stdout and stderr is gone. setsid -f sh -c '…' forks a wrapper out of the exec's process group but not out of its file descriptors, so a detached shell that inherited them kept the stream open for its own lifetime — the harness seam's pi start did exactly that, the start command never answered, and every run that fell to a fresh sandbox died at the executor's 90 s deadline while its container, and its pi, ran on (the same shape at 05:08Z that day and after each deploy; run 8aff9e50 is the receipt — the container answered its first commands in 230 ms and the start command was canceled 90 s later). Three parts: (a) the seam's start script (startScript, src/core/harness/container.ts) runs the wrapper as setsid -f sh -c '<inner>' </dev/null >/dev/null 2>&1 (DETACHED_STDIO); the process's own stdin is the FIFO and its stdout and stderr the run's files as before, so nothing the process needs changes, and the exec's descriptors close when the exec's shell exits. (b) The Worker waits for the output to end only until the command's deadline plus OUTPUT_AFTER_EXIT_MS (20 s, src/execution/sandboxLifecycle.ts; inside the executor's 30 s per-send margin so this answer always arrives first), then asks the runtime for the process's status: exited → the answer is its exit code, empty output and heldOutputNote on stderr — what happened, that the output is lost to this call and the job runs on, and how to detach (DETACH_HINT); running → the process is killed and answered as the shell-level timeout, as before. (c) The exit-124 hint (timeoutNote) spells the detach with the same redirects, DETACH_HINT = setsid -f sh -c '<command> > /tmp/job.log 2>&1' </dev/null >/dev/null 2>&1, one string shared by the seam (DETACHED_STDIO equals DETACH_REDIRECTS, held by a test) and the docs (agent-explore.md item 5). Invariant: the Worker answers every /exec by the command's deadline plus 20 s, whatever the command left running — a held pipe costs the call its output, never the run.

  25. A cold sandbox is seeded from the resident's snapshot before its first command (POST /seed). A resident holds a stamped snapshot of its repository in R2 — the checkout without its dependency view (resident-repos.md item 7) and one archive per lockfile key holding the view (item 61) — and its /status publishes the handle, snapshot.{checkoutBackupId, depsBackupId, ref, sha}. The bot forwards it as the body's seed (src/execution/seedPlan.ts: {slug, checkoutBackupId, depsBackupId?, ref, sha, fetchRef?, fetchSha?}, every field checked — a backup id is a UUID, a slug is owner/name, a ref a branch name, a sha forty hex — and a bad one refused 400 by field name), with the run's env as on every route. The Durable Object, inside the idle ledger and behind the start gate (items 22, 23), restores the checkout into /workspace/checkout and the deps entry into /workspace/.seed-deps presigned only — the container downloads the archive over a URL the object signs, so a gigabyte never crosses the isolate; a Worker without the four presigned values answers seed-unconfigured, and /healthz says backupTransfer — each restore judged by bytes arriving against one shared SEED_RESTORE_MAX_MS deadline (8 min) and extracted onto the disk the resident's way (unsquashfs from the downloaded archive; the image carries squashfs-tools), while the image also carries apt's ripgrep and proves rg at build time — seed restore changes disk contents, not image tools, so fresh and seeded sandboxes inherit the same binary without an agent download — then runs the fix-up as one failing-fast script under SEED_FIXUP_TIMEOUT_MS (3 min): the tree becomes root's, origin points at the Git door, the thread's fetchRef is fetched (Git authenticates through the run bearer's Git door and per-command env, never a GH_TOKEN or token in the script) and checked out at fetchSha when the fetch holds it else at the tip, then the extracted entry's root and outermost workspace node_modules directories replace untracked dependency views at their repository-relative paths, and the head is printed last as the answer's sha. Materialization occurs after the final checkout, preserves relative package/executable symlinks and tracked dependency fixtures, omits workspaces absent from that checkout, and refuses symlinked destination parents before removing any dependencies. The entry root must contain node_modules; the whole entry is never nested under the checkout's node_modules. The marker /workspace/.switchboard-seed names the handle the container was seeded from: the same handle again (a retry, a re-attach) validates the existing dependency view before answering cached and never restores over a live tree. Both fresh and cached views reject an incompatible wrapper, missing direct required packages, or unusable installed executable entries. Inspection reads the root manifest and package.json-declared workspaces, allows optional packages to be absent and workspace bins to be build outputs, and runs no package scripts or test commands. It does not prove the transitive graph, discover pnpm-workspace.yaml-only workspaces, or validate other dependency-directory formats. The same check runs for resident and seeded preparation, including saved seeded resumes. Every outcome is named in the streamed body (item 3), so the executor never reads a seed that did not happen as a dead sandbox: {seeded: true, cached, slug, ref, sha, from, steps: {restore, deps, fixup}, phases: {checkout: {download, extract}, deps: {download, extract} | null}, ms} — each restore split into the archive's download (judged by bytes) and its extraction onto the disk, logged as sandbox.seed-restore too, so a slow seed's owner is read off the answer, or {seeded: false, reason, detail, step} with reason one of seed-missing (the handle's objects are gone — a rotation or an offboard took them; the caller re-reads /status once and retries), seed-failed (a step failed; the run goes cold with the note), seed-unconfigured, or seed-incompatible (an existing cached dependency view could not be validated; preserves its files and marker, refuses admission without retry or cold fallback); a start or a full fleet met at the warm-up keeps its wait token (items 14, 23). A failed fresh seed sweeps what it wrote — mounts, a half tree, the marker — so the cold run that follows clones into an empty workspace. The client is CloudflareSandboxExecutor.seed, under SEED_BUDGET_MS; the bot's selection of a seeded sandbox is the next item's work. The sandbox Worker binds the RESIDENT's cache bucket ({{resident.script}}-cache) and names it in BACKUP_BUCKET_NAME, inside an {{#if resident}} block of its template — a profile without a resident renders no seed bindings — and the R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY secrets are the resident's own, provisioned to the sandbox too. Deploy: the Worker before the bot, as for items 14 and 23.

  26. A run the resident refuses lands in a seeded sandbox when the probe carried the handle (ExecutorSelection.seeded). In the factory's resident fallback — the probe answered a state that is not serviceable, or the attach failed — a probe body that carried seed (item 25) makes the per-thread sandbox call POST /seed before the run. Because this fallback stands behind the first attach, a drained fleet is waited for under the FALLBACK's own cost, never the deploy's (resident-repos.md item 69, issue 2101): the attach passes drainBoundMs = DRAIN_FALLBACK_WAIT_MS (3 min — a seeded sandbox stands up in about two), the typed draining error carries the drain's share, and the fallback selection's drainWaitMs publishes the run's drain_wait note. seedForThread (src/execution/seedPlan.ts) puts the thread's own ref and resolved head on the handle as fetchRef / fetchSha (a thread with no ref stays on the snapshot's branch). A seeded answer puts {slug, ref, sha, workspace: /workspace/checkout, cached, ms} on the selection, and the card's note reads <reason> — seeded sandbox · from resident snapshot · owner/name · ref@sha7 (seededSandboxNote), so a reader tells the three paths — resident, seeded, cold — apart from Slack alone; the resident discriminant stays unset. seed-missing re-reads /status once (seedRetryDecision) and retries with the newer handle when the resident published one; the same handle again, a second miss, seed-failed, seed-unconfigured, or a Worker with no /seed (an older release — the client's infra error) send the run cold with the refusal on the note: <reason> — using fresh sandbox (seed …). Nothing to seed from — an unreachable resident (no body), a repository not onboarded (404), an execution type that is not the cloudflare sandbox — is the cold path unchanged. The run's hard-stop signal reaches credential cleanup, /seed, capacity and transport retry waits, and the missing-seed refresh probe. A stop already pending starts no sandbox request; a stop during setup aborts the request and propagates as the typed stop, never a fresh-sandbox fallback. An ordinary seed failure still falls cold with its reason. The dispatcher composes the seeded prompt variant after executor resolution exactly as the resident's (resident-repos.md item 31): AgentDef.seededSystem (CODING_SYSTEM_SEEDED, REVIEW_SYSTEM_SEEDED — the repository already cloned at the checkout, no clone, no install, gh and Docker present unlike the resident's) plus a line naming the repository and the checkout; the REVIEW TARGET block's seeded branch names the checkout, forbids a second clone, pins the first command to the HEAD check there and diffs against the base already present. resident and seeded never both hold; resident wins.

  27. A restoring resident is a short wait, never a cold fall-through (/await-restore). A resident deploy restores every resident from its snapshot, and every dispatch that probed one mid-restore used to fall to the per-thread fleet at once — under load that burst exhausted max_instances and the runs died on capacity while a warm resident was seconds away (the leaked-slot half of the same incident was fixed separately). Event-driven, with no polling and no retry timer on either side: (a) the resident Worker gains POST /await-restore (operator scope, body {resource}; a not-onboarded resource is 404) whose Durable Object registers a waiter on an in-memory ledger (RestoreWaiters, src/execution/restoreWaiters.ts) BEFORE it reads the state — the read awaits storage, and a transition landing in that gap would otherwise publish to a ledger the request is not on yet and strand it — then answers a non-restoring state at once, withdrawing the waiter, and otherwise holds the ONE request until setResidentState transitions out of restoring — the transition itself publishes the restore event to every held waiter with the state landed on. The answer streams like /attach (heartbeat whitespace then one JSON document over HTTP 200) so a restore lasting minutes cannot lose the connection; a DO restart drops the held RPCs with the ledger and the bot's deadline names the fallback. (b) The bot's factory: a /status probe that finds the resident restoring opens that one held request (ResidentExecutor.awaitRestore, bounded by AWAIT_RESTORE_TIMEOUT_MS = 10 min — the outer safety net, not a retry timer) instead of falling cold; when the answer is a serviceable state it attaches as any serviceable probe does and the card names the wait (· after waiting for the resident's restore); a non-serviceable landing, an attach that fails after the wait, a failed wait, or an older Worker's 404 (a bundle that predates the route) takes item 26's fallback — a sandbox seeded from the probe's handle when it carried one, a fresh one otherwise — with the wait named on the note, never silently. The run's stop rides into the held request (awaitRestore's signal): a run stopped during the hold is the stop's typed shape (aborted), decided by the signal before the error's name, and is never provisioned cold. (c) The resumed-run re-attach holds the same request (issue 1364 part 1; run-history.md item 54): a re-attach's probe that finds the resident restoring opens the held request too — a resumed run's worktree lives on that resident or nowhere, so a refusal at once would close the run and restart it from its request, losing the tree, the transcript and a coordinator child's round to a rehydrate that ends on its own — bounded by the same ceiling clipped to the run's lease less its write-up reserve (restoreHoldBudget), the lease read again after the hold so its end under the hold is the run's end on its budget (item 9), never a refusal; a serviceable landing re-attaches the run's own worktree with the wait named on the note, and every landing short of that — a non-serviceable state, an older Worker's 404, a failed wait — is the typed re-attach refusal naming the wait: a re-attach never falls to a sandbox, whichever way the hold ends. Deploy order: the resident Worker before the bot, as always — an old Worker's 404 is the named cold fallback on the fresh path, today's behaviour.

  28. A container that did not accept the connection is a wait, never a dead sandbox (runtime-busy). The container's runtime accepts one control connection per SDK call, and every /exec, /read and /write opens one. When the platform's fetch to the container's port is not accepted inside the platform's own allowance — a few seconds, well under the SDK's 30 s connect timeout — it throws a plain Error: Container is taking too long to accept the connection; the application could be overwhelmed with load. Nothing ran: the SDK was still connecting, before any process was started. The platform's words name a cause it never measured, and the token names the refusal, never its cause: live, the harness's one-second poll of its transcript log met it three minutes into a whole repository's verify saturating every core of the item-16 instance, AND on an idle container — a review thread whose every command was a git clone, two git diffs, a grep and a sed, each completing in under 31 ms, the runtime accepting a connection 0.9 s before the refused one and 0.8 s after it, the fleet at 7 of 25 and no deploy rolling. Both times the failure reached the bot as an ordinary in-body error (answered, item 9), the harness read it as the run's failure, killed the process and tore the workspace down — and the container accepted the kill a second later. The contract: (a) the Worker names the refusal only where no process was started — the spawn (spawnFailure) and the gate's warm-up — with the token runtime-busy in the item-3 dual shape (runtimeBusyExecAnswer); the file routes throw the typed SandboxRuntimeBusyError and the fetch handler answers HTTP 503 with the token; the recognizer is the platform's wording alone (isRuntimeBusySignal, RUNTIME_BUSY_WORDING, src/execution/sandboxErrors.ts), since the platform gives no type; a failure of a running command's output is never named so — the process exists, and a re-send would run it twice. (b) The executor re-sends the identical request on the token exactly as on fleet-busy (item 14) and sandbox-starting (item 23), after 3 s, 5 s, then 10 s, inside the operation's own budget capped at RUNTIME_BUSY_WAIT_MAX_MS (5 min) — when a command of the thread's own holds the container, it is bounded like every command — and throws ExecCapacityError naming the refusal when the wait is spent, never ExecInfraError; neither the token's explanation nor that message asserts what held the container or tells the reader to wait for a command that may not be running. (c) An older Worker's bare platform text stays an ordinary in-body error after one send: the token decides, never the words. The instance size is not the fix: an idle container has met the refusal, so no size rules it out, and a poll must survive it whatever the size; the SDK's connect timeout is not the knob either, since the platform's allowance runs out first and the SDK exposes none.

  29. A model-call transport failure is held inside the run, never mistaken for its answer. A verified transient or rate-limited provider failure enters the provider hold. Each proxy request makes one upstream call, reports a retryable provider failure down and parks the run; Pi owns at most two retries after the original call, within one minute and the original loop or follow-up lease. Its 5s/15s bases have positive jitter, and Retry-After seconds or HTTP dates are minimum waits. Provider-up releases spend the same attempt budget and cannot shorten rate-limit or Retry-After advice. Billing and configuration failures end immediately without a provider-down recovery wait. A pi-local abort or stream EOF uses the same attempt/time bounds without claiming provider health or waiting for provider-up. Actual model success resets recovery. Restart reservations survive the first replacement-producer receipt, and the recovery deadline is checked after durable acknowledgement before a continuation. Spending the provider recovery budget raises ModelTransientFailureError and records failure: provider_transient; spending the local stream budget raises ModelStreamIncompleteError and records failure: model_stream_incomplete. A gateway HTML document, including one mislabeled 200, is sanitized and parked by the proxy without a hidden retry. Provider bodies remain diagnostics, never the card or runner report. OpenCode's typed recovery remains the explicit gap in harness.md.

  30. An existing pull request is writable only through an exact publication receipt. The unit durably records repository, pull request, head/base refs, expected full head SHA, owned publication ref and owner before a coding continuation can start. A normal reply may repair a legacy ended merge_ready row only when that same unit's completed coding record and posted approving review record jointly attest one full head; the original requester, open same-repository pull request number, head/base refs, live ref tip and sole runner owner must all freshly agree, then lastPush and the complete receipt are written together before reissue. Absent, partial, ambiguous, moved, foreign, closed, wrong-base, wrong-requester or changed-owner evidence starts nothing and writes nothing. Spawn rechecks sole ownership, and immediately before the harness opens the resolved request, attached workspace, durable binding and one fresh pull-request read must agree on every identity and head fact. Missing, partial, stale, moved, foreign, closed, deleted, unavailable or mismatched facts block publication. An admitted push names only the owned ref and carries an explicit full-SHA --force-with-lease, so movement after the fresh read fails atomically; a rejected push revokes the shared live harness fence before the continuing model can issue another push. Every mechanical salvage path uses the same fence, leaves committed work unpublished when blocked, keeps its checkout attached under the workspace retention policy while saying later retention is unverified, and creates neither an alternate branch nor a duplicate pull request.

  31. Operator credential inspection is bounded and count-only. credentials inspect <run> --backend resident|sandbox --repo owner/repo --ref branch --head sha requires the explicit credentials:exec grant for a user or service actor and visibility of the live run. It uses that live harness's executor, process id and kernel birth identity captured through the launch pipe before model code starts; older rows without that identity remain incomplete. The resident Worker accepts a typed inspection request and validates its count receipt before either RPC or HTTP transport; raw output, exceptions and timeout-output recovery never enter the diagnostic transport. It it cannot mint credentials, execute a supplied command, or authorize publication. Results contain only counts and booleans. Missing evidence, a changed registration/process/binding, an unsupported backend, unsafe file/helper state, malformed output or transport failure cannot complete the inspection. The fixed probe inspects command and harness environments, known credential files and Git helper configuration without invoking a helper. A model running as root cannot attest its interpreter: the cold executor refuses in the trusted bot before any remote call, returning an incomplete result. Unknown helper implementations, including the cold image's gh helper, remain incomplete. This is a bounded diagnostic, not proof of whole-container absence, helper execution, revocation or publication. Those live receipts remain separate.

  32. Cold publication runs outside the model-root sandbox; cold inspection remains incomplete. The authenticated sandbox Worker rejects its reserved controller identity on every model exec/read/write/seed and effect source route before obtaining a sandbox. Its controller name is freshly allocated by the Worker, not selected by a thread header or body. The source sandbox exports a capped untrusted Git pack without effect credentials. For an ordinary update, the exporter first tries new commits and their complete trees plus the typed old tip and its tree, excluding unrelated pre-old history. If that transfer exceeds its bound, the fresh controller fetches the publication branch through the same repository-bound Git Door, checks that its tip equals the typed old SHA, and omits blobs while retaining all base trees. The source then exports only objects added after that verified base. A new ref first tries its bounded complete source graph, even when the Door has no default HEAD or its default HEAD is unrelated. If that full graph exceeds the cap, it can use a shallow default HEAD from the same Door only when it is an ancestor of the new tip; a rebased update can also use this verified fallback. Only the trusted fetched pack may promise missing base blobs; the untrusted pack is never marked promisor. Object integrity is checked on import, then full strict fsck checks the combined graph with network protocols disabled during validation. Missing new objects, malformed objects, unrelated or oversized bases refuse without a model fallback. The same time, memory, object-count and byte bounds apply. It uses fixed image Git with no model filesystem, alternates, hooks or imported config. The exact Door old-head lease is still required before excluded ancestry may be treated as already present remotely. Only the runner-owned Executor.publishBranchResult carries the effect bearer to this one-use controller over a single-send typed route with no model env or credential refresh; the controller's Git push names the exact typed repository, ref, next commit and full old-head lease through the existing Git Door. The Door and durable settlement remain the publication authority and receipt. All output and failure text at the transport boundary are fixed, never Git or SDK output; a lost response or nonzero post-push result is unknown and never replayed. The Worker holds the effect and controller teardown under waitUntil on response loss; the controller's own idle sweep is the backstop if its Worker is interrupted. Fleet refusal or failed graph validation cannot fall back to a model-owned push. This controller cannot inspect the model VM's original process or birth: item 31's cold result stays incomplete. No live inspection, revocation, adoption or publication acceptance follows from these unit tests.

  33. Setup waits within the admitted run. During pre-model setup, a typed fleet-busy refusal may wait within the run's original admission deadline. Each retry sends the identical request after a proven pre-operation refusal; the executor does not restart selection, a resident claim, or a possibly accepted seed. After the model loop takes the run, ordinary per-command fleet caps apply. A spent setup wait carries typed fleet facts to the same run's terminal record.

  34. Review movement is native and confirmed. Local, cold sandbox and E2B executors advance a bound checkout only from a verified full prior HEAD with no tracked or nonignored untracked changes. They fetch the exact requested full commit, recheck prior HEAD and cleanliness, refuse unpublished commits, detach without force or ignored-file overwrite and confirm the exact new HEAD through successful, untruncated command results. A hard stop before dispatch or during an awaited command prevents confirmation. Missing Git state, failed commands or private changes refuse movement. Resident movement additionally requires the exact live owner, physical binding and fence (resident-repos.md item 73).

Deliberate PR-image publication ​

PR images adds an explicit PNG-only public-copy operation to the artifact store and attach_file. It does not make the R2 bucket public, change the ordinary inbound/outbound keys or relax the authenticated run-artifact reader. The Worker holds at most one 10 MiB screenshot for validation; the bot still transfers no artifact bytes. Public copies share the existing lifecycle, expire from publication time, and can be deleted independently. The live acceptance procedure is deployment-gated.

Validation criteria ​

CriterionProof
The original log path and byte range may be read once more after an answered Resident infrastructure error; other operations and failure reasons stay single-send, and stop/deadline/second failure remain binding[unit] src/core/harness/container.test.ts::ExecHarnessContainer — log observation recovery::*; src/execution/resident.test.ts::ResidentExecutor.exec::keeps the executor origin on an answered exec failure without claiming no execution
Failed runs retain closed operation attribution without changing the original error, accepting extra payload fields or claiming a command did not run[unit] src/core/typedExecutionDiagnostic.test.ts::closed typed execution refusal observations::retains closed operation attribution without changing an infrastructure failure or accepting extra payload fields; src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::saves the failed harness operation without claiming that its command did not run
The fixed controller log observation may wait on a fresh qualified runtime-busy or sandbox-starting refusal before execution. It reuses the same frozen request, environment, credentials and byte offset inside the original operation/run bound. Hard stop or fencing prevents another send. Model commands, writes, unqualified busy replies and unknown transport remain single-send; stored diagnostics grant no retry authority.[unit] src/core/harness/container.test.ts::ExecHarnessContainer — original operation deadline::observes the original log offset after a qualified…; ::does not repeat a log observation…; ::does not send another log observation…; ::bounds repeated log busy refusals by the original run deadline…
Typed single-send busy failures retain only a closed pre-execution reason and HTTP response class. Trusted caller operations distinguish a harness control/observation from check metadata or selected execution. Failure notes and tool responses may carry this observation; recording this diagnostic alone changes no class/message/stack, sends/deadlines, ending or persisted pending/UNKNOWN receipt. No raw command, environment, path, body, refusal or container value is recorded. Unknown/malformed transport supplies no such fact or execution/retry/ending authority.[unit] src/core/typedExecutionDiagnostic.test.ts::closed typed execution refusal observations::*; src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::saves a closed adapter and harness refusal on the original failure note without changing its ending
9: cold checkout preparation preserves typed setup stops on fresh selection and recovery, starts no command when already stopped, and keeps coincident unrelated failures distinct[unit] src/execution/factory.test.ts::makeExecutor resident selection::cold registration checkout::preserves a typed checkout stop on fresh/recovery setup (recorded=%s), ::a stop already pending starts no cold checkout command, ::a checkout failure beside a pending stop remains a setup failure
32: pre-effect cold publication refusals preserve a bounded closed diagnostic list from command status, measured stream bytes and base hash format; legacy refusal responses remain compatible, malformed diagnostics and lost effect responses remain unknown without replay; diagnostics grant no publication or recovery authority[unit] src/execution/coldPublication.test.ts::cold publication refusal diagnostics::*
32: when a coherent rewrite and the leased old tip are siblings and default HEAD advanced, the existing controller derives one common ancestor from bounded validated Git commit objects rooted at the freshly verified exact old ref; every shallow frontier must precede that ancestor, which is independently fetched from the bound repository before full final-graph validation and the unchanged old-head lease; missing paths, foreign roots, malformed or truncated objects and ambiguous ancestry refuse before push[unit] src/execution/coldPublication.test.ts::cold publication refusal diagnostics::bounds a shallow sibling rewrite after default HEAD advances…
34: native movement confirms a real checkout, preserves tracked/untracked/ignored private bytes and unpublished commits, and refuses hard stops before or during confirmation[unit] src/execution/workspaceAdvance.test.ts::executor workspace advance::*
33: a run's refused setup request waits within admission without changing its body, then returns to the command cap; a typed seed refusal does not start a fresh checkout[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor fleet-busy wait::waits within one run setup lease on the identical refused request, then returns to the command cap, src/execution/factory.test.ts::a typed fleet refusal during seed ends setup without starting a fresh checkout
25: cached dependency validation is read-only, refuses incompatible wrappers, missing declared packages and broken expected executable links, and permits dependency-only views and absent optional packages; an incompatible cache never enters destructive reseeding or the ordinary cold fallback[unit] src/execution/seedPlan.test.ts::pilot ready environment check::*; src/execution/factory.test.ts::makeExecutor seeded sandbox::an incompatible cached seed refuses without retry or cold fallback even without a pilot policy
25: a seeded checkout consumes the resident dependency entry as a repository-relative tree: the root executable resolves at checkout node_modules/.bin and each outermost workspace dependency directory replaces its corresponding checkout view while preserving tracked leaves, without nesting the entire entry under node_modules[unit] src/execution/seedPlan.test.ts::resident dependency entry consumed by a seeded checkout::materializes the producer's root executable and workspace dependencies beside tracked fixtures; src/execution/seedPlan.test.ts::dependency entry materialization::*
32: a small update to an existing large tree uses a verified exact old tip even when default HEAD diverged; only trusted base packs may promise omitted blobs, new objects remain fully checked, and push packing must not read omitted old blobs as delta bases or fetch them lazily; the original exact ref and old-head condition still fence the push[unit] src/execution/coldPublication.test.ts::isolated cold publication plan::publishes a small update to a large unchanged branch even when default HEAD diverged, src/execution/coldPublication.test.ts::isolated cold publication plan::rejects a new graph…
32: a cold publication refusal before the push returns one of four fixed 409 phases (base/graph unavailable, transfer/validation refused); only an exactly shaped, explicitly allowlisted 409 passes through the client as fixed text, never Worker-provided text. Malformed, unknown, 4xx other than 409, 5xx and lost answers stay UNKNOWN and are never replayed; the settlement records the fixed phase without treating it as accepted[unit] src/execution/coldPublication.test.ts::isolated cold publication plan::preserves only the fixed pre-effect 409 phase…, ::treats an untrusted…, ::the Worker sends only fixed pre-effect 409 phase codes, never SDK output; src/core/publicationSettlement.test.ts::publication settlement::records a fixed cold 409 refusal phase as rejected, never an accepted or pending effect
32: a new ref first exports its bounded complete graph against an empty repository or an unrelated default HEAD, validates and pushes with an empty-ref lease; when full history exceeds the cap, it uses an ancestor default HEAD as a verified partial base without changing that lease[unit] src/execution/coldPublication.test.ts::isolated cold publication plan::publishes a bounded complete initial ref against an empty repository with an empty-ref lease, src/execution/coldPublication.test.ts::isolated cold publication plan::publishes a bounded complete initial ref against an unrelated default HEAD with an empty-ref lease, src/execution/coldPublication.test.ts::isolated cold publication plan::fetches a repository-bound immutable base for initial cold publication, src/execution/coldPublication.test.ts::isolated cold publication plan::the Worker tries a complete initial-ref graph before looking for a trusted fallback
32: a Worker-allocated controller identity is disjoint from every model route before getSandbox, and the effect bearer never enters model env or shell; a bounded old-head graph excludes unrelated history while controller-side bounded Door fetch supplies an immutable base for oversized existing trees, initial refs or rebased updates, and validation of the closed graph refuses malformed, missing or oversized data before the push effect; generated commands retain Linux byte, memory and time bounds while local Git proofs run on macOS and Linux; lost answers are not replayed; cold inspection still refuses[unit] src/execution/coldPublicationBoundary.test.ts::cold publication controller identity::allocates an unselectable controller name under the actual named-Sandbox mapping, src/execution/coldPublication.test.ts::isolated cold publication plan::*, src/execution/credentialInspectionRemote.test.ts::remote credential inspection::refuses cold inspection before a root-controlled runtime can forge the receipt; [gap] a live release/canary proves the controller's fleet admission, cleanup, and exact Git Door effect on a real cold thread only after separate authorization.
31: a resumed pilot must receive an acknowledged owner-fenced ledger write of its rebound workspace binding, retaining the recorded sandbox owner and key, before its model or coordinator continuation, including mid-run relaunch; a fenced write or retry-pause handoff stops the former owner without a reply, while an unavailable write or handoff pauses a still-owned run; neither proceeds on a stale dependency source[unit] src/core/dispatch/readyBinding.test.ts::resumed pilot binding commit::*; src/core/dispatch/relaunch.test.ts::prepareRelaunch — the relaunch decided and prepared::pauses a pilot before bearer rotation when its rebound binding commit is *, ::commits the repaired dependency receipt before relaunching the original run; src/core/dispatch/runLoop.test.ts::the pi harness — the container replaced under a living bot: the relaunch ceiling::a refused pilot binding commit pauses before child resume or model continuation; src/core/dispatcher.test.ts::run ledger write-through (docs/reference/specs/run-history.md item 35)::a fenced resumed pilot binding commit leaves the reply and row to the new owner, ::a resumed pilot loses its owner during pause after binding storage fails without replying; src/core/runLedger/writeThrough.test.ts::finishing and finish::silences a retry pause refused by the ledger after another generation takes the row, ::keeps a retry pause with unavailable storage distinct from a lost owner
31: readiness can verify dependencies and required tools without choosing or inventing a coding test command; an explicit legacy first action still requires its command[unit] src/execution/seedPlan.test.ts::pilot ready environment check::checks preparation without requiring or inventing a test command; src/config.test.ts::pilot ready environment config::prepares dependencies and tools without choosing the coding agent's test command
31: an opt-in pilot write unit requires a resolved repository, branch, exact head and configured resident plus sandbox before provisioning; the trusted coding caller pins the current branch head and supplies dependency/tool requirements and an optional legacy test command; a warm resident needs a bound checkout, dependency snapshot and attached dependency view, including a matching stored dependency key on a reused tree; a fallback needs a matching snapshot/dependency seed, never a fresh cold checkout, and its cache marker includes the dependency archive identity; a seeded fallback admits only when the committed root lockfiles at the dependency snapshot head match the target checkout head, otherwise it gives a typed, actionable pre-model refusal to install dependencies for that head; the dependency directory and required tools are checked with fixed-output, model-free commands before admission; an optional declared test command is checked for syntax and executable availability without execution, and an explicit legacy first action requires that command; a missing prerequisite is a typed, actionable pre-model refusal, and a resumed resident unit uses its recorded attached ref when the new request omits a branch, requires the recorded worktree, user and branch to match the returned binding while allowing the container identity to change after a roll, and accepts a reused dependency view only with its stored key; a resumed seeded unit retains the snapshot source head and rechecks root lockfiles against it; a failed resumed readiness or reuse-only reattach check pauses the same ledger run with its recorded binding, without a replacement workspace or model turn; the same pause applies when a live pilot writer loses its container and the recorded checkout cannot be re-attached; the original preservation policy and ready requirement are stamped on a tracked ledger row before the first model turn, and an untracked pilot writer is refused before workspace attach; a resumed stamped run validates and uses its recorded requirement even if live config changed, while an unstamped legacy coding run retains ordinary recovery; any unpublished changes remain where the original worktree lives, and after environment repair the next process generation reclaims the same run and attaches reuse-only before continuing, and no resumed unit can bypass the gate until its recorded environment can be verified[unit] src/execution/factory.test.ts::makeExecutor pilot ready environment::*; src/execution/seedPlan.test.ts::pilot ready environment check::*; src/config.test.ts::pilot ready environment config::*; src/execution/githubPulls.test.ts::githubPulls::fetchBranchHeadSha (pilot writer's exact base binding)::*; src/core/dispatch/provision.test.ts::attachWorkspace — the workspace attach and the ask-once refusal::a configured pilot writer resolves the exact base head and supplies the declared readiness check before its first model turn, ::keeps a pilot writer out of the executor when its branch head cannot be verified, ::keeps a failed resumed pilot readiness check attached for the same workspace retry, ::holds a pilot run on its recorded binding when reuse-only reattach refuses, ::keeps a recorded coding workspace when pilot configuration changed before recovery, ::honors a hard stop before holding a pilot whose ready settings changed, src/core/dispatch/provision.test.ts::reattachWorkspace — the run's recorded workspace re-attached without the gate::pauses a stamped pilot writer when its mid-run reattach loses dependencies, ::uses the run's original ready requirement when live pilot config changes; src/core/dispatch/runLoop.test.ts::the pi harness — the container replaced under a living bot: the relaunch ceiling::pauses a coding run on mid-run reattach refusal without finishing or releasing its recorded worktree; src/core/dispatcher.test.ts::run ledger write-through (docs/reference/specs/run-history.md item 35)::a pilot writer paused by mid-run reattach refusal keeps its original ledger row and checkout, ::refuses an opted-in pilot writer before workspace attach when its run is not durably tracked; src/core/runLedger/writeThrough.test.ts::finishing and finish::pauses only one resumed run for readiness retry and preserves its workspace binding for the next generation; src/core/dispatcher.test.ts::run ledger write-through (docs/reference/specs/run-history.md item 35)::a resumed pilot readiness failure keeps its run and dirty workspace for the next generation, ::a resumed pilot with no recoverable workspace binding pauses its original run before provisioning; [gap] the delegation caller retains the original queued brief/unit and does not spend a model round on this refusal; [gap] a deployed pilot write from a real question reaches a ready worktree and completes its declared tests before a reviewed PR
30: exact existing-PR publication — a complete durable binding plus request, workspace, current owner and fresh remote agreement admits only the owned ref with an atomic full-SHA lease; missing, stale, mismatched, moved, foreign, closed, deleted, unavailable, ownership-changed and concurrent-movement states block the model or publication, preserve committed work as unpublished, and create no alternate ref or duplicate pull request[unit] src/core/coordinator/contract.test.ts::isCoordinatorUnit — one unit's row::an existing-PR publication binding requires complete repository, PR, ref, full-head and owner identity; src/core/existingPrPublication.test.ts::verifyExistingPrPublication — exact existing-PR publication binding::*; src/channels/adminCoordinator.test.ts::POST /admin/coordinator/spawn — the child as the parent record's requester (item 9)::an existing-PR coding spawn carries the durable publication binding only for its sole owner and fails closed after ownership changes, src/channels/adminCoordinator.test.ts::pr-check keeps the machine's adopted pull request authoritative before branch discovery (agent-ship items 9, 10 and 12)::persists the first complete same-repository PR read as future coding rounds' exact publication authority; src/core/dispatch/runLoop.test.ts::the pi harness — every preset's runs, in the run's container::the gate's push rules follow the thread: a pull-request thread's or a unit child's bound branch is the run's own — the one push target, its base protected; a plain thread's binding is the protected base, src/core/dispatch/runLoop.test.ts::runLoop — the model turn and everything that rides on it::a blocked existing-PR publication keeps the local checkpoint attached and renders truthful partial status without an alternate push, ::a concurrent branch move that rejects the atomic lease keeps the unpublished checkpoint attached, src/core/dispatch/runLoop.test.ts::the pi harness — every preset's runs, in the run's container::a rejected compaction checkpoint revokes the open harness's publication receipt before the continuing model can push, src/core/dispatch/runLoop.test.ts::a resume with the answer in hand (the \finish` plan)::a verification-blocked existing PR with a clean checkout performs no PR lookup, edit, or create; src/core/harness/pi/toolRules.test.ts::judgeToolCall — an existing-PR publication fence:😗; src/core/codingPrPostStep.test.ts::salvageBudgetPush — a ship coding child pushes what it has at the budget (push-before-abort, agent-ship.md item 8)::an existing-PR publication fence leaves committed work unpublished when blocked and uses the atomic lease when allowed; src/core/harness/windDown.test.ts::windDownAnswer — the finale answer reads what the ending established (harness-pi item 6)::a publication-blocked workspace names the local checkpoint without promising retention or continuation; src/core/dispatcher.test.ts::a unit-owned thread (record 0051's reply-as-event and gone-instance rules)::plain continuation delegates to exact original-unit recovery without replacing its owner`
5: resident, cold and local read/write workspaces refuse without a door; local model commands see only the run bearer and no GH_TOKEN or App token mint[unit] src/execution/factory.test.ts::makeExecutor per-agent provisioning::a local backend gives model commands the run bearer without an App token, ::refuses read and write workspace attach without a door, even when an App or PAT is available, ::a door-backed local run exposes only its revocable run bearer to model commands; src/execution/executor.test.ts::LocalExecutor exec env::*
5a: the bound checkout and GIT_DOOR_REMOTE carry the exact Git door URL, including cold PR review, seed restore and legacy re-attach; global GitHub URL rewrites cannot divert a public clone whose name extends the bound repository, and only the door has the run-bearer credential helper[unit] src/execution/factory.test.ts::makeExecutor per-agent provisioning::leaves GitHub clones direct even when their names extend the bound repository; src/agents/registry.test.ts::coding prompts: push then submit_pr_description (opening the PR is the system's job)::cold coding and explore clone the bound private repository through its exact door remote; src/execution/factory.test.ts::makeExecutor resident selection::warm probe → ResidentExecutor, attached on open, with the resident discriminant set; src/execution/factory.test.ts::makeExecutor resident selection::on a resume (ctx.reattach): re-attach, never re-provision::a seeded sandbox re-attach moves a legacy origin to the exact door URL before restoring publication facts; src/core/dispatch/authorize.test.ts::authorizeAttachedHead — every review workspace is at the PR head before the first model turn::an unseeded non-resident backend provisions the PR checkout at the resolved head before verifying it; a mismatch is refused and released before the model; src/core/dispatch/authorize.test.ts::authorizeAttachedHead — every review workspace is at the PR head before the first model turn::a cold PR review with no Git door refuses before creating a GitHub checkout; src/execution/seedPlan.test.ts::seedFixupScript::uses the exact bound Git door URL and rejects malformed destinations; src/execution/seedPlan.test.ts::seedFixupScript::runs as one failing-fast script: ownership, origin, the deps view moved in, the thread's ref fetched and checked out, the head printed last
18: hosted local execution refuses a workspace machine before attach by preset name; none still provisions nothing and the CLI local backend remains deliberate[unit] src/core/dispatch/provision.test.ts::attachWorkspace — the workspace attach and the ask-once refusal::a hosted process with local execution refuses a workspace preset by name in one sentence before attach, ::an agent that declares no repository attaches nothing…; src/execution/factory.test.ts::makeExecutor per-agent provisioning::a local backend gives model commands the run bearer without an App token
29: verified transient and rate-limited failures allow two retries within one minute and the run lease; a pi-local stream EOF retries locally and exhaustion records its own typed failure — a 502 mid-turn completes on retry; a park-capable run self-retries without waiting for another run; the plane can release a transient hold early within the attempt budget, without shortening rate-limit or Retry-After advice; spending the retry window raises the named typed ending without gateway HTML; overload, reset and a bare gateway document classify alike; a 200 HTML gateway page is parked and sanitized without a proxy retry[unit] src/core/harness/pi/harness.test.ts::runPiHarness — a run on pi from the first file to the answer::a 5xx then success: the transient failure is retried after the ladder's first backoff — pi is re-prompted and the retry's answer is the run's, ::transport failures stay held on backoff inside the lease; exhausting that retry budget ends by type without exposing a gateway page, while a non-transient error is never retried, ::a parked provider failure on a park-capable run holds the turn and retries on backoff without waiting for another run; a plane reissue can still release it early, ::the ProviderFailure seam classifies real transient answers without promoting embedded tokens, ::the harness's park decision is the typed cause set, not a second prose classifier; src/channels/modelProxy.test.ts::the provider level and the park (record 0064)::a gateway HTML page is a transport failure even when it claims 200: it is parked and never relayed as model output; src/core/harness/pi/harness.test.ts::runPiHarness — a run on pi from the first file to the answer::an incomplete local stream that spends the retry lease ends with its own typed failure, ::an incomplete OpenAI Responses stream retries locally and completes the original turn
18: blank → the per-thread sandbox with no repository and no credential, no resident probe though a resident is configured and a repository is in the context; on local execution, an empty per-thread workspace directory[unit] src/execution/factory.test.ts::makeExecutor machine classes::blank → a per-thread sandbox with NO repository and NO credential, no resident probe, even with a repo in the context, ::blank (local) → a per-thread workspace directory, empty
18: repo-cold → the per-thread sandbox with the checkout and the run bearer under its effective read/write identity, no note, no resident probe though one is configured; without a resolved repository, an empty workspace and no note[unit] src/execution/factory.test.ts::makeExecutor machine classes::repo-cold → a per-thread sandbox WITH the checkout and the run's credential; the resident is never probed though one is configured, ::repo-cold without a resolved repository → the per-thread sandbox with an empty workspace, no note
18: the null executor's tool error names the class and the classes that provision a workspace[unit] src/execution/factory.test.ts::makeExecutor machine classes::the null executor's tool error names the class and the classes that provision a workspace
19: the /read encoding contract — no encoding is the text read, base64 asks for bytes, anything else is refused by name; the resident reads text with cat and never bytes through one command; the base64 cap is exactly what a MAX_READ_BYTES file encodes to and the decoded byte count tells a file one byte over apart[unit] src/execution/binaryRead.test.ts::the /read encoding contract (item 19)::*
19: the bot decodes a base64 answer to the bytes only when they are exactly the size the Worker measured — a short answer is read-inconsistent with nothing handed on, an unsized one predates the verified read; a text answer (a Worker that predates binary reads) is named as a rollout gap and never decoded or counted as infra; a tooLarge answer is the cap's one message naming the file[unit] src/execution/binaryRead.test.ts::decodeBase64Read — the bot side of a Worker's answer (item 19)::*
19: the resident's chunked read — stat -c %s first and only a number parses; the chunk is a multiple of 3 below the SDK's observed stream cut so the pieces' base64 concatenates into the whole; the plan covers the size exactly with a short last chunk and an empty file has none; each chunk command seeks with tail, bounds with head, encodes unwrapped, and its expected base64 length is known before it runs[unit] src/execution/binaryRead.test.ts::the resident's chunked byte read (item 19)::*
19: both remote clients report a short answer as read-inconsistent (never infra) and carry a Worker's own 409 refusal with its words[unit] src/execution/readBytesRemote.test.ts::CloudflareSandboxExecutor.readBytes (item 19)::an answer whose bytes are not the size it names…, ::the Worker's own read-inconsistent refusal…, src/execution/readBytesRemote.test.ts::ResidentExecutor.readBytes (item 19)::bytes that are not the named size are read-inconsistent…
19: LocalExecutor.readBytes hands back the exact bytes a utf8 read would mangle, refuses a path escaping the workspace, and refuses an over-cap file by name with its size before reading it[unit] src/execution/binaryRead.test.ts::LocalExecutor.readBytes (item 19)::*
19: the tracing executor carries readBytes exactly when the inner executor has it — an exec.read_bytes span without the path[unit] src/execution/tracingExecutor.test.ts::TracingExecutor::*
19: both remote clients ask /read for encoding: "base64" (the resident with its resource and thread) and hand back the decoded bytes; a text answer and a tooLarge answer are plain errors, never ExecInfraError; the resident's missing file is the route's 404 classified like readFile's[unit] src/execution/readBytesRemote.test.ts::CloudflareSandboxExecutor.readBytes (item 19)::*, src/execution/readBytesRemote.test.ts::ResidentExecutor.readBytes (item 19)::*
20: an R2 PUT URL names one key on the bucket's S3 endpoint, admits requests for 600 s, signs the content type and carries no secret; a GET URL signs the host alone; the same inputs sign identically and another key does not; a key is percent-encoded per segment with the slashes kept[unit] src/artifacts/store.test.ts::R2ArtifactStore presigned URLs (item 20)::*
20: head answers size and type from a signed HEAD, null on 404, and throws on any other status or on a 2xx without a content-length (never reading a missing length as 0); the HEAD and the GET are signed with accept-encoding: identity, so the edge never compresses a text object's answer and drops its length (Node's fetch would otherwise advertise br/gzip/deflate/zstd, and a text/plain object of 48 bytes or more came back with no content-length)[unit] src/artifacts/store.test.ts::R2ArtifactStore.head (item 20)::*
20: copyFromUrl POSTs to the bot's own Worker with the copy bearer and a timeout and accepts only the size it asked for; a non-2xx or short answer is an error carrying the Worker's words[unit] src/artifacts/store.test.ts::R2ArtifactStore.copyFromUrl (item 20)::*
20: both implementations honour one contract (URLs name the key and the window; head reads what was stored and null for nothing; a single byte range reads the slice with part against the object's size, an open end and a suffix clamp, a start past the end is unsatisfiable); the in-memory copy stores exactly size bytes with the upstream type and refuses a short body[unit] src/artifacts/store.test.ts::InMemoryArtifactStore honours the store contract (item 20)::*, src/artifacts/store.test.ts::R2ArtifactStore (over a fetch double) honours the store contract (item 20)::*, src/artifacts/store.test.ts::InMemoryArtifactStore.copyFromUrl (item 20)::*
20: parseByteRange reads the three single-range forms and nothing else; the R2 get forwards a valid range on the wire unsigned, maps a 206 to part, a 200 to the whole object, a 416 to unsatisfiable with the size — from the 416's Content-Range when it names one, else from one HEAD (R2's InvalidRange answer carries no size), an object the HEAD no longer finds being null — and sends no range for a form it refuses[unit] src/artifacts/store.test.ts::parseByteRange (item 20)::*, src/artifacts/store.test.ts::R2ArtifactStore.get with a range (item 20)::*
20: safeBasename keeps the last segment and reduces every other character to _, collapsing runs, never empty and never a run of dots; outbound keys live under the run with a per-file sequence and inbound under the thread and message with the file's index[unit] src/artifacts/keys.test.ts::safeBasename (item 20)::*, src/artifacts/keys.test.ts::key builders (item 20)::*
20: the content type is derived from the extension, case-insensitively, application/octet-stream otherwise; the inline image set is exactly the four raster types[unit] src/artifacts/contentType.test.ts::contentTypeFor (item 20)::*
20: the artifacts: section parses with the documented keys and is absent by default; an unknown key at any level, a missing bucket or account, a bad bucket name and a non-integer knob are refused by name[unit] src/artifacts/config.test.ts::the artifacts config section (item 20)::*
20: no section → no store; the section with the three secrets and a public URL → the R2 store on the configured bucket; a missing secret or a missing PUBLIC_BASE_URL fails by name[unit] src/artifacts/buildStore.test.ts::buildArtifactStore (item 20)::*
20, the copy route before any fetch: a missing or wrong bearer is 401 with nothing fetched or put and GET with the bearer names the bound bucket (503 without a binding); a url off Slack's hosts or on http:, a non-positive or fractional size, a size over the 1 GiB ceiling (413), a key that is not an inbound key and a body that is not a JSON object are each refused by name with no fetch and no token sent; no binding or no Slack token is 503 after the bearer; a non-POST is 405[unit] deploy/cloudflare/artifactsCopy.test.ts::artifacts copy — the request, before any fetch::*
20, Slack's answer before any put: a 200 text/html (the login page) is refused as unauthorized, a non-200 is Slack's status, the token rides the Authorization header with redirects not followed; a Content-Length absent or other than the declared size is 409 naming both numbers; a fetch that throws is 502 with the reason; nothing is put in any of these[unit] deploy/cloudflare/artifactsCopy.test.ts::artifacts copy — Slack's answer, before any put::*
20, the wiring: the Worker hands the copy route the request it rebuilt for tracing, never the inbound one — a rebuilt Request takes the body stream with it, so the original reads as an empty body and the route answers 400 "not JSON" while the rebuilt one is parsed and reaches Slack[unit] deploy/cloudflare/artifactsCopy.test.ts::artifacts copy — the wiring hands the route the request that still has its body::*
20, the copy: one put under the key through the fixed-length pipe with Slack's content type and the answer { key, size }; an upstream body that ends short fails the pipe, the put rejects, the route is 502 and the bucket holds nothing[unit] deploy/cloudflare/artifactsCopy.test.ts::artifacts copy — the copy itself::*
20, the bucket step: every distinct bucket_name of the rendered config is read (none without r2_buckets); exit 0 is created, wrangler's "already exists" (10004) is success, any other refusal is wrangler's verbatim[unit] deploy/cloudflare/ensure-bucket.test.mjs::ensure-bucket — bucketNamesOf()::*, deploy/cloudflare/ensure-bucket.test.mjs::ensure-bucket — decide()::*
20, the binding: the profile's artifacts.bucket is optional and strict (R2 bucket-name rules, refused by field; the committed example names none); the template view carries it; the committed bot template renders r2_buckets and ARTIFACTS_BUCKET_NAME together exactly when the profile names a bucket, a bot-only profile with a bucket included; the plan asks the bot step for r2 bucket list only then[unit] src/deploy/profile.test.ts::…::\artifacts.bucket` is optional…, src/deploy/wranglerTemplate.test.ts::…::carries the Access application…, src/deploy/wranglerTemplate.test.ts::…::the committed bot template binds the artifacts bucket…, src/deploy/plan.test.ts::capabilityProblem::*`
20, the agreement: the config's bucket and the Worker's bound bucket agree, or the problem names both; a Worker binding none says so; the shim routes /artifacts/copy to itself[unit] src/artifacts/agreement.test.ts::bucketMismatch (item 20)::*, src/core/trace/workerTrace.test.ts::shimRoute::*
20, staging — classification: a video, a 6 MiB PNG and an over-cap PDF are staged with their metadata while a 1 MiB PNG the image pass carried is not; store off → nothing staged and the labels unchanged; a 1.2 GB file, an eleventh file and the overflow past the per-message budget are skipped with the reason; the secret denylist wins over staging; no URL/name/size is skipped by name[unit] src/channels/slack/attachments.test.ts::stagedFiles (by-reference classification)::*
20, staging — the commands and the line: the pull makes the directory and fetches to attachments/<index>-<basename> with both operands single-quoted (a hostile name is a filename); the resident's exclude appends attachments/ once; sizes read 312 KB/1.2 GB; the line names every landed file with size and type and every failed one with its reason; the workspace-less line points at agent:coding; copies land under the thread's inbound key with an in event each, pulls follow in order after the exclude on a resident and without it on a sandbox, two files of one name take 1- and 2-, a failed copy is never pulled, a failed pull carries curl's words and is not retried, nothing to stage runs no command; a coordinator-folded accepted seed keeps Slack slot 0 at attachments/0-<name>, while same-name slot-0 attachments from later folded messages and later staging rounds take collision-free run indexes; every path is appended to the coding prompt and records its own inbound artifact[unit] src/core/dispatch/staging.test.ts::staging — the commands::*, src/core/dispatch/staging.test.ts::staging — copy then pull::*, src/core/dispatcher.test.ts::inbound staging (record 0033)::a coordinator coding child stages a folded accepted PNG at its zero-based path, appends that path to the prompt and records it in the asset catalogue
20, staging — the reference rides every path: a follow-up carries staged and mergeFollowUps concatenates it into the fresh turn; the durable inbox row carries it on the base row, reads it back, keeps it when the inline attachments are dropped, and drops a malformed entry never fatally[unit] src/core/threadAdmission.test.ts::mergeFollowUps — unconsumed inputs become ONE fresh request::texts join in order; attachments and staged references concatenate…, src/core/runLedger/inboxMessage.test.ts::durable inbox — staged references (record 0033)::*
20, staging — the dispatcher: a coding run's copy starts after admission and before the workspace attach, the pull runs over the run's executor and the first user turn ends with the line; a general run copies nothing and its turn names the missing workspace; without a store no pull and no line[unit] src/core/dispatcher.test.ts::inbound staging (record 0033)::*
20, staging — a steer into the live run: copied and pulled over the run's executor before the steer is sent, whose text ends with the line; the record carries the in event[unit] src/core/dispatch/runLoop.test.ts::the pi harness — every preset's runs, in the run's container::a steered follow-up's staged file is copied and pulled before pi reads the steer…, …::without a store the pi steer is sent as before…
20, the thread's files — one catalogue read: the thread's runs are listed past one page by cursor and each record paged to its end (an event on a late page is found); the artifact events, in and out, yield one entry per key, oldest run first, owned by the run that first named it; the store is asked once per key and held says whether it still holds the object; a run list, a record page or a HEAD that fails stops only what it was reading, with a warning naming it, and the other files still count; a run list the store could not serve (live rows only, no throw) is the catalogue of those rows, warned, with no further page asked for; describeAsset says what a file is and where it is for this run in the words the prompt and recall share[unit] src/core/dispatch/threadAssets.test.ts::readThreadAssets — the thread's files from its runs' records, held or not::*
20, staging — the thread's earlier files on that read: an in entry the store holds takes the run's next index and this run's own in event, one the store no longer holds is named as expired with no index and no event, one the store could not be asked about is named so; nothing is copied and the store is not asked again; WorkspaceFiles resolves a key to the path THIS run staged it under across every round; a later run in the thread pulls its own file then the earlier one (1- then 2-), re-copies nothing, carries the earlier key on its own record, its turn names the expired one, and its prompt lists the thread's files with where each is — its own message's file left to the turn[unit] src/core/dispatch/staging.test.ts::staging — the thread's earlier files::*, src/core/dispatcher.test.ts::inbound staging (record 0033)::a later run in the thread pulls the files dropped on earlier messages…
20, dynamic recall — files published after the initial prompt reach recall { assets: true } only after their canonical producer dependencies are durably added to the consumer; a missing archive or revoked producer contributes neither file metadata nor inherited dependencies[unit] src/core/dispatcher.test.ts::inbound staging (record 0033)::dynamic asset recall persists readable producer dependencies before returning files and omits unproved or revoked producers
20, staging — both coding prompts say a dropped file is already in ./attachments/ when the turn names it[unit] src/agents/registry.test.ts::…::both coding prompts route files the person should see through attach_file…
20, live: a 1 GB Slack file copied through the deployed route — wall time, Slack's egress rate and the response on the unit's PR; a failure sends the record to its multipart fallback (the bot copying in 8 MiB parts)[agent] after the bot Worker deploys with the binding: mint a copy from a switchboard thread's url_private with the copy bearer and read { key, size } back; receipts on the receipts issue
20, live — a large file dropped on a thread reaches the run: a 300 MB video (and, at the ceiling, a 1 GiB file) shared on a new message with the ask lands as ./attachments/1-<basename> with the exact size and sha256 of the source, and ffmpeg reads the video; the bot's /healthz process.rssMb stays within tens of MB of its idle value throughout; the run's summary quotes the turn's line when a file is NOT there rather than inventing it[agent] generate the two files locally (ffmpeg -f lavfi -i testsrc2 …, head -c 1073741824 /dev/urandom), record their sha256, upload each with a Slack upload URL (curl --upload-file) and complete it onto a fresh message asking for path, size, sha256 (and a contact sheet for the video); sample /healthz every 20 s meanwhile; compare the run's numbers to the local ones; receipts on the receipts issue
20, live — a deployment without artifacts: is unchanged: /healthz carries no artifacts key, a dropped file over the inline caps gets the "could not be passed through" note as before, and attach_file takes the inline path[agent] on a deployment whose config has no artifacts: block (or after removing it and deploy restart): read /healthz, drop an over-cap file on a thread and attach a small screenshot from a coding run; expect the pre-store behaviour on all three; receipts on the receipts issue
20: /healthz carries artifacts: { bucket } when the store is configured and no key when not[unit] src/channels/health.test.ts::build identity on /healthz::healthPayload carries \artifacts: { bucket }` when the store is configured and no key when not`
20: the three secrets are declared on the bot in the manifest, on the Worker's Env and in its forward list[unit] src/core/secretsManifest.test.ts
20, the bucket's rules: a retention gives two lifecycle rules over every key — delete after the retention, abort an incomplete multipart upload after one day — described as a person reads them; the read-back matches only when both rules are there as written (a missing, disabled or differently aged rule is named, another id is left alone); a bucket is private exactly when its managed r2.dev domain is disabled and no custom domain is enabled, every open setting named otherwise[unit] src/deploy/artifactsBucket.test.ts::artifacts bucket — the rules::*, src/deploy/artifactsBucket.test.ts::artifacts bucket — privacy::*
20, the bucket's calls: the four Cloudflare calls ride the operator's CLOUDFLARE_API_TOKEN as the bearer and never the bot's token; a missing token is the call's problem naming the variable and the permission with nothing called; a 401/403 names the permission, another status keeps the API's words, success: false carries its errors, a thrown fetch its reason, a malformed answer is named[unit] src/deploy/artifactsBucket.test.ts::artifacts bucket — the host's calls::*
20, live: artifacts check on the deployment's bucket reports private: true, and artifacts lifecycle applies the configured retention and reads it back equal[agent] with CLOUDFLARE_API_TOKEN for the account in the shell: artifacts check → the private verdict naming the disabled managed domain; artifacts lifecycle --dry-run then artifacts lifecycle → applied 2 lifecycle rule(s) … and read them back equal; receipts on the receipts issue
19 live: after both Workers deploy, a coding run in a resident thread and one in a cold sandbox thread each playwright screenshot https://example.com shot.png then attach_file shot.png — the PNG renders inline in the Slack thread; a 12 MB file is refused with the cap's message, not a failed run[agent] @switchboard agent:coding in <resident repo>: screenshot https://example.com to shot.png, attach it, then head -c 12000000 /dev/urandom > big.bin and attach that too; report both tool results verbatim, and the same in a non-resident thread
18: the GitHub vet — GET /repos/<owner>/<name> with the run's credential as a bearer and the scope asked for; 200 binds, 404 refuses, every other status, a transport failure and an unmintable credential are unreachable; no credential → an anonymous request; a non-slug is refused before any request[unit] src/execution/githubRepoProbe.test.ts::githubRepoProbe::*
18: which vet a class gets, against the production resolver on a deployment with both a sandbox and a resident — repo-resident: one registry GET /status and no GitHub call; repo-cold: GET /repos on GitHub with the run's scope and never the registry, a 404 → rejectedRepo, silence → unverifiedRepo; blank: no vet and an empty context[unit] src/core/dispatch/resolve.test.ts::resolveTarget — the vet a machine class gets::*
18: a repo-cold run's refusals — GitHub's 404 refuses as repo_not_visible naming the repository and the installation, with or without residents, never onboarding; GitHub's silence refuses as repo_unverified naming GitHub; a visible repository meets the access gate like every class[unit] src/core/dispatch/authorize.test.ts::authorizeRepo — the repository gates, once the target has landed::a repo-cold agent's repository was vetted against GitHub::*
13: resolveBuildStamp — an injected commit + timestamp survive; a -dirty suffix is preserved; a missing/blank/non-string commit is unknown and no timestamp is invented; both are trimmed[unit] src/deploy/buildStamp.test.ts
13: injectedBuildStamp with no --define present returns {commit:"unknown"} rather than throwing on the undeclared identifiers[unit] src/deploy/buildStamp.test.ts
13: buildStamp/defineArgs — the stamp carries the commit and build time, marks a dirty tree, and emits defines whose values are JS string LITERALS; a value containing a quote is escaped, not injected as code; the identifiers match the reader's[unit] src/deploy/buildStamp.test.ts
13: buildId — the identity a stored artifact compares against is the commit PLUS the build time, so two builds of one dirty tree and a redeploy of the same clean commit are told apart, while sibling isolates of one deployed version agree; no timestamp → the commit alone[unit] src/deploy/buildStamp.test.ts
13: spawnOutcome — exit 0/1 pass through; a spawn error and a SIGNAL kill (which reports status: null) each become exit 1 with the reason NAMED, so an interrupted deploy is not read as a failed one[unit] src/deploy/buildStamp.test.ts
13: memory /healthz carries build beside features, unknown in an un-stamped test bundle[unit] deploy/cloudflare-memory/worker.test.ts + runs.test.ts (workerd)
13: the define reaches all three bundles — wrangler deploy --dry-run --outdir per Worker contains the HEAD sha and zero leftover SWITCHBOARD_* identifiers (esbuild folds typeof … === "string" to true), and the resident's dry run still binds env.STATE_WORKER_URL (the --var hazard avoided)[agent] In each Worker directory run wrangler deploy --dry-run --outdir <dir>; grep the bundle for git rev-parse HEAD and for leftover SWITCHBOARD_ identifiers; the resident's dry-run output must still list STATE_WORKER_URL under its bindings.
13 live: after deploy each Worker's /healthz reports the deployed commit — resident and memory unauthenticated, sandbox with the bearer[agent] After a deploy, curl each Worker's /healthz (the sandbox with its bearer) and compare build.commit to the deployed commit.
≤280s commands succeed; >280s return exit 124 + timeout stderr[agent] In Slack: @switchboard agent:review run \sleep 45 && echo OK` then `sleep 320 && echo NO`, report raw results. Expect OKthenexit 124: command timed out in the sandbox after 280s…`.
Long commands stream heartbeats (no fetch failed)[agent] Covered by the same run — without heartbeats the 320s case surfaces as fetch failed.
Session recovery after container replacement[agent] Run a command in a thread, wrangler deploy the sandbox worker (replaces containers), run a follow-up in the same thread — it must succeed (repo re-clones), not 500 Session not found.
Thread workspace reuse[agent] agent:coding create file /workspace/marker.txt, then follow-up cat marker.txt — must print the content (same sandbox), unless the sandbox expired (then a legible re-clone story, not a crash).
1, 2: the idle lifetime is five minutes in the Container class's own grammar[unit] src/execution/sandboxLifecycle.test.ts::the idle lifetime::is five minutes, in the Container class's own grammar
2, 4, 9: the sandbox Worker WIRES the seams (static guard; the Worker itself cannot run under vitest) — sleepAfter = SANDBOX_SLEEP_AFTER; a command is one supervised process started and collected inside the Durable Object (createExtensionProcessSandbox(this).exec(, proc.output() with no session fence and no keepalive of its own; the /exec failure path classifies a recycle by type inside the Durable Object and by name after the RPC boundary, a full fleet, and a silent control port; a file route's refusal leaves with the Durable Object's reason token (the stat under a binary read on a full fleet included — the executor waits on the token, never the text); automatic teardown uses the idle guard; authenticated cancellation may destroy only its exact dedicated run slot[unit] src/execution/sandboxLifecycle.test.ts::sandbox Worker wiring (static)::the Durable Object's sleepAfter is the shared constant, and a command is one supervised process started and collected inside the Durable Object, ::the /exec failure path names a mid-command recycle by type inside the Durable Object and by name after the RPC boundary, a full fleet, and a silent control port, ::a file route's refusal carries the Durable Object's reason token, src/execution/sandboxIdle.test.ts::IdleGuard::*, src/execution/sandboxLifecycleReader.test.ts::sandbox run cancellation::*, src/execution/sandboxLifecycle.test.ts::sandbox Worker wiring (static)::automatic lifecycle destruction and served operations remain wired through the idle guard
9, the typed reason: ExecInfraError names why at every construction in both remote executors — a literal from the closed list or a reason-of helper, read as syntax by the TypeScript parser so a parenthesis in a message or a nested call cannot cut the scan short — the waitable set is exactly the transport lost, the deadline passed, the empty failure shape and the Worker unavailable, the executors' real wordings (built by the same helpers they throw with: the one request failed (…) sentence for both Workers, the sandbox executor's no-answer and empty-failure shapes) carry those reasons, a request the run's stop aborted is aborted by the signal before the error's name, the resident client types an answer by the fields on it (a 5xx carrying a state waits exactly when isWakeable says the resident is coming back — the wake path's own decision, so onboarding refuses on both routes — reason: "unregistered" refuses, a 5xx with a body and no state is answered, a bare 5xx waits, a body on another status is the words, a bare 4xx is refused), its strike after the wake wait carries the last engine view's decision (a budget spent on a still-restoring resident is unavailable, a definite view refused; a re-attach that fails as infra inside the wait is typed by that same view, the run's own stop — whose signal rides into the re-attach — the call's own aborted error), the streamed routes' status is read from the body by one rule, a rejected data-plane call is answered as the Durable Object would by route (the reset word; the replacement's bare 409 on /exec, its word on /read and /write; else the typed 500, its transient the SDK's own predicate and the two remainder sentences) and typed by the client so, a transient attach-failed is unavailable and keeps the wake wait going, the stop rides into the wait's probes, and the resident names the two refusals on its answers as fields and every unnamed-throw 500 through one shape[unit] src/execution/execInfraReason.test.ts::*, src/execution/residentWake.test.ts::wakeDecision: the engine view says whether the container is coming back::isWakeable is the one decision on a state…, src/execution/resident.test.ts::ResidentExecutor waits for the wake (item 65: a container rollout is a minute, not a dead sandbox)::the wait respects the command's budget…, ::a container exit with a definite non-recovering engine view is infra at once, on every call, ::an unreachable /status is a strike too…, ::a re-attach that fails as infra inside the wait is a strike typed by the last engine view…, ::a hard stop while the re-attach inside the wait is in flight aborts that request too…, ::a hard stop while a /status probe inside the wait is in flight aborts that probe too…, ::a hard stop during the pause ends the wait at once with the stop's one typed shape…, ::a re-attach inside the wait whose 500 the Worker typed transient…, ::a /read whose Durable Object stub rejected with the SDK's stopped-container sentence…, ::a pending /exec whose Durable Object stub rejected with the SDK's own moved-runtime sentence…, ::attach: a 500 the Worker typed transient enters the wake wait…, ::attach: the wait is bounded by the wake budget…, ::an unreachable /status right after a transient refusal is the same platform blip, waited through…, ::probes that stay unreachable past the budget after a transient refusal end in the resident unavailable…, ::the run's stop rides into the first attach request itself…, ::one wake budget per operation…, ::the re-attach inside a first attach's wait runs under the attach's own timeout, which the wake ceiling never caps…, ::the rolling wake's re-attach — the one recovery that always recreates the worktree from the mirror — runs under the attach's own timeout…, ::one bound for every attach request an operation opens, whatever the command's budget…, ::a recovery attach after a worktree eviction has the attach default as its floor…, ::the wake clock starts at the first wait, never at the first call…, ::after a transient refusal only an unanswered probe is the same blip…, src/core/dispatcher.test.ts::*::a hard stop during the first attach's wait ends the request \stopped`…, ::a follow-up queued during the first attach's wait is dropped with the ⛔ note when the stop ends the attach…, ::a provisioning failure beside a pending stop is the failure, not the stop…, src/execution/resident.test.ts::ResidentExecutor.moveTo::carries the run's stop into the move's re-attach…, src/core/dispatcher.test.ts::review post-step::head moved during the run (item 12)::the worktree move carries the run's hard stop…, src/execution/factory.test.ts::makeExecutor resident selection::warm probe then a TRANSIENT attach failure…, ::a transient blip the first probe already shows cleared re-attaches at once…, ::a hard stop during the first attach's wake wait ends it at once…, ::a /status probe the Worker typed transient is waited through, not a cold fallback…, ::a probe still transient past the first attach's budget falls cold with the wait named…, ::a hard stop during the probe's wait ends it at once with the stop's typed error…, ::probe transport failure → named fallback + negative cache…, ::a transport failure met on a re-probe inside the blip wait ends the wait at once AND opens the outage breaker…, ::an attach that fails after the probe waited through a blip names that wait too…, ::the retry's budget is what the first attach's WAIT left, never what its own latency took…, ::on a resume (ctx.reattach): re-attach, never re-provision::a resumed run's /status probe the Worker typed transient is waited through, not a re-attach refusal…, ::a resumed run's probe still transient past the first attach's budget is the re-attach refusal, naming the wait…, ::a hard stop during a resumed run's probe wait ends it with the stop's typed error…, src/core/dispatch/provision.test.ts::reattachWorkspace — the run's recorded workspace re-attached without the gate::the run's own stop ending the re-attach's wait is `stopped`…, src/core/dispatch/relaunch.test.ts::prepareRelaunch — the relaunch decided and prepared::the run's hard stop rides into the re-attach…, src/core/dispatcher.test.ts::run ledger write-through (docs/reference/specs/run-history.md item 35)::a hard stop while the relaunch re-attaches the run's worktree ends the run stopped…, ::a resumed run hard-stopped while its re-attach waits through a transient refusal ends `stopped` and closes its row `stopped_hard`…, src/execution/tracingExecutor.test.ts::::wraps release and moveTo only when the inner executor has them…, src/execution/resident.test.ts::ResidentExecutor.probeStatus::carries the Worker's `transient` typing on a 5xx…, src/execution/factory.test.ts::makeExecutor resident selection::a failure beside a pending stop is that failure, not the stop…, ::the needs-ref retry by default draws on the first attach's one budget and the card names the total wait…, ::one budget across the needs-ref retry…, ::on a resume (ctx.reattach): re-attach, never re-provision::a resumed run's re-attach waits through a transient refusal under the first attach's budget…, ::a hard stop during a resumed run's re-attach wait ends it with the stop's typed error…, ::a re-attach refusal beside a pending stop is the refusal…, src/execution/resident.test.ts::ResidentOperations.run::a rejected op the Worker typed as the platform's transient…, deploy/cloudflare-resident/threadErr.test.ts::, ::a pending /exec whose Durable Object stub REJECTED…, ::a pending /exec whose Durable Object stub rejected with the DO's own code-update reset…, ::a refusal streamed by /exec over HTTP 200 is typed by the status and the lifecycle pair IN the document…, deploy/cloudflare-resident/notServiceable.test.ts::*`
9: a checked VM recycle refuses before admitting /exec and carries transient: true; the client waits for a serving resident, re-attaches and sends the same command once under the command's budget; an untyped 503 still stands as an answer, and no other transient exec failure is blindly retried[unit] deploy/cloudflare-resident/destroyFence.test.ts::resident VM destroy uncertainty::types the pre-admission recreate refusal as temporary, src/execution/resident.test.ts::ResidentExecutor waits for the wake (item 65: a container rollout is a minute, not a dead sandbox)::a pre-admission checked recycle waits for re-attach and re-sends the same command once, while an untyped refusal remains an answer
9: the recycled-mid-command wording fires only for a recycle-shaped failure more than a minute into the attempt, or at any time for a typed recycle error; an early failure of any text, and a late failure of any other text, is returned unchanged; both the 0.3.x and the 0.12.x recycle texts are recognized[unit] src/execution/sandboxLifecycle.test.ts::recycledMidCommandMessage::leaves an early failure with any message alone, ::leaves a recycle-shaped failure alone inside the first minute — that is a real startup failure, not a recycle, ::names the recycle when a recycle-shaped failure arrives after more than a minute, and tells the model to look at /workspace before assuming either outcome, ::never rewords an unrelated failure on timing alone — a late transport error keeps its own text, ::recognizes the 0.12.x recycle texts — a terminated session shell and a container stopped under a pending call, ::a typed recycle error (certain) is named at any elapsed time — the SDK is stating the runtime went away; src/execution/sandboxLifecycle.test.ts::isRecycleError::takes the SDK's typed errors by NAME across the 0.12 and 0.13 lines (the RPC boundary drops the prototype), ::falls back to the recycle-shaped texts, and rejects everything else
5: the run bearer rides in the exec env option and never in the command text — the Worker source carries env: envVars and no base64 export[unit] src/execution/sandboxLifecycle.test.ts::sandbox Worker wiring (static)::the credential goes through the exec env option, never through the command text; live: after the sandbox Worker deploys, a cold run's Command executed lines in the Worker logs carry no GH_TOKEN
5: the executor sends the resolved env as body.env on every route (/exec, /read, /write) and NO x-env-* header — the request carries only authorization, content-type, x-thread-key; an empty map is still env: {}; the fleet-busy re-send carries the same body, env included[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor env transport::sends the map in the body and NO x-env-* header on any route, ::an empty env is still the one body shape: \env: {}`, so the Worker reads one field on every route; src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor fleet-busy wait::re-sends the SAME route, body and headers after 10 s then 20 s and returns the eventual result`
5: envFromRequest — reads body.env alone (request headers are never read); body keys are upper-cased; names that are not shell identifiers and non-string values are dropped; an env that is not a plain object is ignored; a non-object body yields {} without throwing; no other body field is read as env[unit] src/execution/sandboxEnv.test.ts::envFromRequest::reads \env` from the body — an object of string values — and returns it as the env map, ::upper-cases body keys, so `gh_token` and `GH_TOKEN` are one variable, ::drops names that are not shell identifiers after upper-casing, ::drops non-string values — numbers, booleans, null, objects, arrays — and keeps the string ones, ::an `env` that is not a plain object (array, string, null, number, boolean) yields an empty map, ::a body that is not an object yields an empty map — never a throw, ::reads only `env` — the body's other fields are never env, and there is no header channel`
5: the sandbox Worker WIRES the body path (static) — envVars comes from envFromRequest( and the Worker source names no x-env- channel; the executor resolves the map once (const envs = await this.opts.resolveEnvs()), passes it as the body's env — under it a caller's own variables when an exec carries some (harness-pi.md item 4) — in the body, and its source names no x-env- either[unit] src/execution/sandboxLifecycle.test.ts::sandbox Worker wiring (static)::the Worker reads the env map through envFromRequest and names no x-env header channel, ::the executor sends the env in the body alone — no x-env-* header
5 live: after the bot and Worker deploy, a cold run sees GH_ENTERPRISE_TOKEN as its run bearer and no GH_TOKEN; gh auth status and a repo-bound read work, while Workers Logs show no x-env-* request header or App credential[agent] On an authorized real run, inspect the effective env without printing token values, run a read-only gh command, then inspect the corresponding Worker invocation headers and door refusal/forwarding receipts.
3: thrownText — the SDK's message verbatim (trimmed) when present; a message-less error names the error name and code and says a rollout may be in progress; whitespace-only counts as empty; no name at all still yields a non-empty text[unit] src/execution/sandboxErrors.test.ts::thrownText::returns the SDK's message verbatim (trimmed) when it has one, ::a message-less error names the error name and code and says a rollout may be in progress, ::a whitespace-only message counts as empty, ::no name at all still yields a non-empty text that says so
9: the destroy-time disconnect text (The sandbox was destroyed while the operation was pending.) is recycle-shaped — reworded after a minute, kept inside it, and recognized by isRecycleError[unit] src/execution/sandboxLifecycle.test.ts::recycledMidCommandMessage::recognizes the destroy-time disconnect text as a recycle; src/execution/sandboxLifecycle.test.ts::isRecycleError::falls back to the recycle-shaped texts, and rejects everything else
9: the runtime-unreachable text starts with the token and names the container id, the SDK pin, the platform's running flag (running / stopped / not reported) and the cause (or that there was none); the typed error carries the name and is recognized by name or by token after the RPC boundary, and is neither fleet-busy nor a recycle nor a stale session[unit] src/execution/sandboxErrors.test.ts::the runtime-unreachable message and error::*
9: the Worker's answer shapes for it — /read//write {error, reason: "runtime-unreachable"}, /exec the dual form (error + exit 127 + stderr) plus the reason[unit] src/execution/sandboxErrors.test.ts::the runtime-unreachable answer shapes the Worker sends::*
9: the executor turns the in-body /exec answer into ExecInfraError after exactly one send, the text naming the token and the container; a /read 503 is re-sent on the transport retry and the eventual answer returned; a runtime that never comes back is ExecInfraError naming the token once the transport retries are spent[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor runtime-unreachable::*
9 live: a cold thread whose server is killed under a command gets a card naming what happened, never the bare HTTP error! status: 500 — the recycle wording for the command the kill interrupted (the control connection's peer closed: a typed replacement), and runtime-unreachable with the container id if the port then stays silent for 30 s[agent] After the Worker and image deploy, in a NON-resident thread, run kill -9 $(ps -eo pid,comm | awk '$2=="sandbox"{print $1}') — the tool result reads sandbox recycled mid-command after Ns — the container's runtime was replaced or restarted under the command …; the next command (cat /workspace/marker from item 21's row) runs on the restarted server. A runtime-unreachable: … (container <64 hex>, sandbox SDK 0.13.0-next.751.1; …) result means the supervisor gave up or the port is wedged; its container id is the $containers.container.id of that thread's rows in the containers dataset.
3/9: the executor throws ExecInfraError after exactly one fetch on a PRESENT-but-empty in-body error; a body without an error key and exit 127 with empty output is the normal exit 127: result; exit 0 with no output is (no output)[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor in-body empty error::an empty-string error is ExecInfraError after exactly one fetch — the Worker's failure shape with its text missing, ::a body WITHOUT an error key and exit 127 with empty output is the normal \exit 127:` result — `foo 2>/dev/null` is legitimate, ::exit 0 with no error key and no output renders (no output)`
3/6: the sandbox Worker WIRES it (static; the Worker cannot run under vitest) — both catches call thrownText( and the .message ?? String(err) fallthrough is gone; automatic teardown uses the idle guard; exact runtime cancellation remains separate; wrangler.jsonc sets rollout_step_percentage to 100[unit] src/execution/sandboxLifecycle.test.ts::sandbox Worker wiring (static)::every failure text goes through thrownText — the empty-string fallthrough is gone, src/execution/sandboxIdle.test.ts::IdleGuard::*, src/execution/sandboxLifecycleReader.test.ts::sandbox run cancellation::*, src/execution/sandboxLifecycle.test.ts::sandbox Worker wiring (static)::automatic lifecycle destruction and served operations remain wired through the idle guard, src/execution/sandboxLifecycle.test.ts::sandbox Worker wiring (static)::wrangler.jsonc rolls the sandbox image out in one wave (rollout_step_percentage 100)
6 live: across the next sandbox Worker+image rollout, the Worker logs carry no empty-tailed sandbox.exec error … — lines; a thread that starts during the rollout either runs normally or sees the named sandbox exec failed with no message from the SDK (…rollout…) text, never a silent exit 127; any sandbox.version-skew container=… sdk=… line is the skew being seen[agent] wrangler tail <sandbox worker> through the deploy, start one cold review inside the first two minutes, read its run page
2 live: a command longer than sleepAfter completes in a per-thread sandbox and the workspace survives it[agent] After the sandbox Worker deploys, in a NON-resident thread: run \mkdir -p /workspace/probe && sleep 330 && echo OK` with timeoutMs 600000, then ls /workspace→OKthenprobe. Re-run after the 0.13 port, since the SDK's busy poll now holds the deadline where the Worker's own keepalive did: the failure signature is a recycle at 300 s (sandbox recycled mid-command after 300s) and probe` gone.
Sandbox image Node satisfies the repo's engines pin (≥22), so fallback reviews load the full suite[agent] After a sandbox-worker deploy, force the per-thread path (any repo not onboarded as a resident, or a pruned ref) and run: @switchboard agent:review run \node --version` in <owner/repo>, then `npx vitest run --reporter=basic 2>&1 | tail -5`. Expect v22.xand 0 unloadable suites (pre-fix: 15 suites failed to load withwebidl.util.markAsUncloneable is not a function). Build-time backstop: the Dockerfile asserts node --version` is v22 or the image build fails.
Shell quoting through the timeout wrapper[unit] src/execution/shellQuote.test.ts — tests the exact shellQuote module the worker ships (extracted to deploy/cloudflare-sandbox/shellQuote.ts); the timeout-binary cases run on CI's Linux and skip on macOS.
GH App token minting & caching[unit] src/execution/githubApp.test.ts — mocked fetch: JWT shape, cache hit, mint-failure surfacing, GH_TOKEN/null fallbacks and opted-in mint cancellation.
5: a cached token is reused only while it outlives the longest single command — the margin is ≥ BASH_TIMEOUT_MAX_MS + 5 min, a token inside it re-mints, one outside it is reused[unit] src/execution/githubApp.test.ts::the reuse margin covers the longest single command (BASH_TIMEOUT_MAX_MS) plus slack, ::re-mints when the cached token has less than the reuse margin left, ::reuses the cached token while it still has at least the reuse margin left
5: the commit identity env (record 0062) — a write identity with a bound requester gets the four variables, author the requester pair, committer the bot pair; unbound, both the bot pair; a run with no requester or no binding store reads no binding; read and none get none of the four; an unknown bot pair yields none; a binding read that throws falls back to the bot pair[unit] src/execution/factory.test.ts::gitIdentityEnvs — the commit identity for a run's workspace::*
5: the four ride every plane — the shared resolver puts them in the sandbox body's env and E2B's per-command envs beside the credential, makeExecutor wires the requester's binding into a write run's resolver, and the resident executor sends them in each /exec body's env, resolved per exec, a caller's variables under them, no x-env-* header, and no env key when there is nothing to say[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor credential freshness::the four commit identity variables reach the body's env beside the credential; src/execution/e2b.test.ts::E2BExecutor credential freshness::the four commit identity variables reach each command's envs beside the credential; src/execution/factory.test.ts::makeExecutor per-agent provisioning::a write run's sandbox env carries the four identity variables — author the bound requester pair, committer the bot pair; src/execution/residentExecEnv.test.ts::ResidentExecutor — the commit identity rides each /exec body's env::*
5: the agent trailer and the fallback address — one hook shared by every image byte for byte, core.hooksPath and an off-GitHub fallback user.email wired in every image, the hook appending the trailer from GIT_COMMITTER_* idempotently (a foreign co-author does not stop it), and a git commit under it yielding author = requester pair, committer = bot pair, trailer = bot pair — or the image fallback pair on a non-GitHub address with none set[unit] src/execution/prepareCommitMsgHook.test.ts::*
5: the run bearer is present in each command environment, while no App token is minted into the workspace[unit] src/execution/cloudflareSandbox.test.ts::resolves the sandbox env on EVERY call, so each command carries the credential current at its start, ::resolves nothing at construction — building the executor mints no credential; src/execution/e2b.test.ts::each command carries the envs resolved at its start; src/execution/factory.test.ts::makeExecutor per-agent provisioning::the sandbox env resolves the run bearer for each command without minting App tokens
5: ordinary caller env never overrides a run's resolved credential; resident and Cloudflare cold publication use their own typed runner-owned transports, never an ExecOptions bearer or model shell, while unsupported E2B exposes neither typed transport nor shell fallback[unit] src/execution/resident.test.ts::ResidentExecutor.exec::keeps model env from replacing the door bearer and sends publication only through the typed route, src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor credential file refresh::exposes a typed runner-owned publication transport but never one in the model sandbox, src/execution/coldPublication.test.ts::isolated cold publication plan::sends one typed request without model env, refresher, command or retry after response loss, src/execution/e2b.test.ts::E2BExecutor credential file refresh::does not expose a runner-owned publication transport in the model sandbox, src/execution/residentPublication.test.ts::resident runner-owned Git publication::*
5: a reused Cloudflare cold sandbox removes legacy App token files before seed or model work; E2B retires legacy VMs with creation-time App env before creating an env-free VM, reconnects only marked env-free VMs, and refuses failed retirement or cleanup[unit] src/execution/legacySandboxCredentials.test.ts::legacy cold sandbox credential cleanup::*; src/execution/cloudflareSandbox.test.ts::Cloudflare reused sandbox credential boundary::*; src/execution/e2b.test.ts::E2B reused sandbox credential boundary::*; src/execution/factory.test.ts::makeExecutor seeded sandbox::a refused resident with a snapshot…
5: the unwired legacy credential-file refresher — a token inside its margin (or nothing written yet) is refreshed before the exec: the write script lands the store file with the credential line on the body's env, resets the global helper list, and is not repeated while the token lives; a run without a credential writes nothing; a write is believed only once its exec lands; the refresh decision, the recognizers and the write script are the pure module's, shared by both sandbox backends[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor credential file refresh::a token inside its margin is refreshed before the exec…, ::a run-bearer push refusal is returned once without an App credential source; src/execution/e2b.test.ts::E2BExecutor credential file refresh::lands the store file at the e2b path before the first exec…, ::a run-bearer push refusal is returned once without an App credential source, ::a failed write exec ends the exec by name and is re-planned — never believed fresh; src/execution/sandboxCredentials.test.ts::SandboxCredentialRefresher::*, ::credentialWriteScript::*, ::the credential-refusal and push recognizers::*
5: a run-bearer Git push refusal is returned as command output once; the factory supplies no App credential source to either cold executor, so neither remints or retries on the model path[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor credential file refresh::a run-bearer push refusal is returned once without an App credential source; src/execution/e2b.test.ts::E2BExecutor credential file refresh::a run-bearer push refusal is returned once without an App credential source; src/execution/factory.test.ts::makeExecutor per-agent provisioning::read and write sandboxes get only the run bearer
12: the clamp rule — a finite number truncated and clamped into [1 s, 20 min], anything else the 5-min default — and every executor honoring timeoutMs (the local executor kills at it and names the knob)[unit] src/execution/bashTimeout.test.ts::clampBashTimeout::*, src/execution/executor.test.ts::LocalExecutor per-call timeout::*
5: least-privilege token by identity — the read scope mints exactly contents, pull_requests, issues, actions, checks, metadata, every value read; the trusted door's write mint requests only contents:write, workflows:write, metadata:read for its bound repository; its read and write scopes cache separately and reject an expanded mint response; the separate generic bot write scope retains its full grant for trusted post-steps; the trusted door mints read and repository-pinned write tokens while model workspaces receive only run bearers; the factory reads the profile's class and identity, never the preset's own fields[unit] src/execution/githubApp.test.ts::mints door write tokens for one repository with only Git push permissions and keeps scope caches separate, ::refuses a door mint whose returned scope exceeds the requested repository or permissions, without caching it, ::accepts a door mint response that omits implicit Metadata, ::refuses a door mint whose repository selection remains all; src/execution/githubMintScope.test.ts::GitHub installation token scope::*; src/execution/githubApp.test.ts::mints a READ-scoped token whose permissions are exactly contents, pull_requests, issues, actions, checks and metadata — all read, ::every permission the read scope requests is read — the request never carries write or admin, ::the default (write) scope requests NO permissions restriction — the full grant, ::read and write tokens cache in separate slots (one mint per scope); src/execution/factory.test.ts::makeExecutor per-agent provisioning::read and write sandboxes get only the run bearer, src/execution/factory.test.ts::makeExecutor machine classes::provisions from the profile, never from the preset: the class and identity the context's profile names win over the agent's own fields; src/execution/githubRepoProbe.test.ts::githubRepoProbe::a run whose identity mints nothing (no scope) vets anonymously too: the credential resolver is never asked; src/core/dispatch/resolve.test.ts::resolveTarget — the vet a machine class gets::repo-cold on a \read` identity vets with a read token; on `none` it vets anonymously and mints nothing`
5 live: a review run reads CI through the door with its run bearer and cannot mutate via REST, GraphQL or Git; a coding run pushes only its bound non-default ref[agent] After deployment, use existing authorized review and coding runs to verify reads, mutation refusals, repo/ref refusals and an accepted bound push without printing bearer values.
App installation covers the target repo[agent] agent:review run \gh repo view <owner/repo> --json name`` for each repo agents work on — must return JSON, not 404. Failure signature: "GitHub credentials don't have access" + 404.
7: agents on machine class none provision nothing, under every execution type, and a tool call reaching the null executor names the class[unit] src/execution/factory.test.ts::an agent declaring no repo gets a null executor with cloudflare configured (no sandbox call, no token needed), ::an agent declaring no repo gets a null executor with e2b configured (no API key needed), ::an agent declaring no repo creates no workspace directory in local mode; src/agents/registry.test.ts::machine class declarations: coding and review run on repo-resident; general and research on none
7: agents on repo-resident get the configured backend[unit] src/execution/factory.test.ts::a local backend gives model commands the run bearer without an App token, ::a repo-requiring agent gets the Cloudflare backend when configured, ::a repo-requiring agent with e2b configured but no API key still fails legibly; end-to-end: src/core/dispatcher.test.ts::a coding ask still selects the configured remote backend
Executor selection receives the resolved agent in its context[unit] src/core/dispatcher.test.ts::passes the resolved agent to executor selection
Resident selection: warm → resident; anything else → named per-thread fallback; outage circuit breaker[unit] src/execution/factory.test.ts::makeExecutor resident selection (all six scenarios); details in resident-repos.md
Infra failures throw ExecInfraError (distinct from a nonzero exit), so a caller tells a dead sandbox from a command the agent should keep handling[unit] src/execution/resident.test.ts::ResidentExecutor infra classification::*, src/execution/cloudflareSandbox.test.ts::*
A resident runtime-replaced on /exec (one deploy) is NOT infra — the typed restart the pi harness keys on[unit] src/execution/resident.test.ts::ResidentExecutor infra classification::a runtime-replaced on /exec (one deploy) is the typed restart, never infra
14: isFleetBusy recognizes the SDK 0.3.x Failed to create session: 503, the platform's "no container instance" wording, and CONTAINER_UNAVAILABLE; a stale session, "Command execution failed", a file-op failure, a 500, and arbitrary text are NOT fleet-busy[unit] src/execution/sandboxErrors.test.ts::isFleetBusy::recognizes the SDK 0.3.x client's unparsed 503 from createSession, ::recognizes the platform's raw no-instance message, ::recognizes the newer SDKs' CONTAINER_UNAVAILABLE code, ::is NOT a stale session, a wedged sandbox, or arbitrary text
14: isFleetBusyError takes the 0.12.x ContainerUnavailableError by name or code and falls back to the recognized texts; a recycle, a stale session, or an unrelated failure is not fleet-busy; thrownShape reads an Error, a plain object, and a string alike[unit] src/execution/sandboxErrors.test.ts::isFleetBusyError / thrownShape::takes the 0.12.x typed error by name, whatever its text, ::takes the error code when a client surfaces it, and falls back to the recognized texts, ::is NOT a recycle, a stale session, or an unrelated failure, ::thrownShape reads an Error, a plain object, and a string the same way
10: check:sandbox-pair gates on COMMITTED files only — the package.json pin must equal the Dockerfile tag and the lockfile's resolution of that pin (nested workspace entry first, else the root's hoisted one); an INSTALLED copy that disagrees with the lockfile (an install drifted ahead, as the resident's did) is a one-line advisory naming the workspace and both versions with the npm ci remedy, never a failure; a fresh clone with nothing installed says nothing[unit] src/sandboxPairCheck.test.ts::check-sandbox-pair lockfileSdkVersion::prefers the workspace's nested lockfile entry over the root's hoisted one, ::is null when the lockfile has no entry at all; src/sandboxPairCheck.test.ts::check-sandbox-pair pairMismatches::fails a lockfile that resolves the pin to a different version, or has no entry; src/sandboxPairCheck.test.ts::check-sandbox-pair installAdvisories::says nothing when the installed SDK equals the lockfile, or nothing is installed (a fresh clone), ::emits one line naming the workspace, both versions, and npm ci when the install drifts, ::fixture: a lockfile pinning one version with another installed is an advisory, not a failure; src/sandboxPairCheck.test.ts::the repository's sandbox-image Workers::pin @cloudflare/sandbox to exactly the image tag they build FROM, and the lockfile agrees
10: every image names Node by one exact node:<major>.<minor>.<patch>-slim tag, the three tags are equal and their major is .nvmrc's; each execution image takes it from a node stage pinned to linux/amd64, removes the base's npm tree, copies the binary, npm's tree and the headers at the base's paths before any global install, and asserts the exact version at build time[unit] src/deploy/imageNode.test.ts; [agent] npm run check:image (CI's image / build … legs) green at the PR head, and node --version inside each built image prints the tag's version
10: both execution images install one apt set (python3 make g++ ffmpeg fonts-liberation fonts-noto-color-emoji, no recommends) in the layer that installs playwright globally at one exact version, set PLAYWRIGHT_BROWSERS_PATH=/opt/ms-playwright before that install and NODE_PATH to the global tree, install chromium's headless shell with --with-deps --only-shell, open the browser tree a+rX, prove the compilers, the ffmpeg encode → decode round trip, the CLI at the pin and a real headless screenshot at build time, and drop the apt lists, apt's .deb archive, npm's cache and the proof artefacts in the same layer; the two images name the same apt set and the same pin; the resident runs the screenshot proof again as worker1 after the user pool[unit] src/deploy/imageToolchain.test.ts::deploy/cloudflare-sandbox/Dockerfile::* (11), src/deploy/imageToolchain.test.ts::deploy/cloudflare-resident/Dockerfile::* (11), src/deploy/imageToolchain.test.ts::the two execution images carry one toolchain::* (2), src/deploy/imageToolchain.test.ts::the resident image::runs the screenshot proof a second time as worker1, after the user pool exists — the build proves an unprivileged thread user can drive the browser
10: playwright is one exact global install per execution image, read by the pin parser like pnpm's, and the same version in both[unit] src/deploy/imagePins.test.ts::the execution images' playwright::is one exact global install per image, ::names the same version in both images
10: bun is one exact global install per execution image, read by the pin parser like pnpm's, the same version in both, the base's binary removed first and the pin proven on PATH at build[unit] src/deploy/imagePins.test.ts::the execution images' bun::*
10: pi is one exact global install in every image (the two execution images and the bot's), proven at build, and the same version in the three[unit] src/deploy/imagePiHarness.test.ts::the three images carry one pi::*, src/deploy/imagePiHarness.test.ts::Dockerfile::*
10: OpenCode is one exact global install in every image, its install script allowed by name and scoped to it, the version text proven at build, the same version in the three, and the resident proves it again as worker1[unit] src/deploy/imageOpenCodeHarness.test.ts::the three images carry one OpenCode::*, src/deploy/imageOpenCodeHarness.test.ts::Dockerfile::*, src/deploy/imageOpenCodeHarness.test.ts::the resident image proves OpenCode as a thread user::*
10: every prompt with a workspace (coding, review, explore, and the resident variants) names Node 24, python3 and g++, ffmpeg, and the headless Chromium through playwright screenshot / playwright pdf / require('playwright') with video; the cold prompts offer Docker and the resident prompts say there is none; the prompts without a workspace name none of it[unit] src/agents/registry.test.ts::the workspace prompts name the image toolchain::* (3)
10 live: after the next release, in a cold sandbox thread agent:explore run: playwright screenshot https://example.com /tmp/a.png && ffmpeg -f lavfi -i testsrc=duration=1:size=320x240:rate=10 /tmp/a.mp4 && ls -l /tmp/a.png /tmp/a.mp4 lists two non-empty files, and the same command in a coding thread on an onboarded repository (a resident, running as workerN) does too[agent] Run the command above in a NON-resident thread and read the run page: both files listed with a non-zero size, no command not found, no browser launch error. Then the same command through agent:coding on an onboarded repository, whose card says the resident attached.
14: the Worker's answer shapes — reason: "fleet-busy", an error keeping the SDK's message as the cause, and the /exec dual form (error + exit 127 + stderr)[unit] src/execution/sandboxErrors.test.ts::the fleet-busy answer shapes the Worker sends::names the reason and the explanation, and keeps the SDK's own message as the cause, ::the /exec shape carries the dual in-body failure form (error + exit 127 + stderr) like every other exec failure; the Worker wiring (streamExec catch → fleetBusyExecAnswer, outer catch → 503 fleetBusyAnswer) is [agent]: fill the fleet (max_instances threads busy) and run one more cold command — the run page must show ⏳ Sandbox fleet busy and the run must continue, not Sandbox exec transport failed 2 times in a row
14: the executor re-sends the IDENTICAL request (route, body, headers) after 10 s then 20 s and returns the eventual result; a /read answered 503 fleet-busy waits the same way[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor fleet-busy wait::re-sends the SAME route, body and headers after 10 s then 20 s and returns the eventual result, ::a /read answered HTTP 503 with reason fleet-busy is the same wait (the default 5-min budget applies)
14: the wait is bounded by the command's own budget and by the 5-min cap, and then throws ExecCapacityError — not ExecInfraError — naming the seconds waited[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor fleet-busy wait::gives up once the total wait reaches the command's own budget and throws ExecCapacityError, not ExecInfraError, ::never waits longer than FLEET_BUSY_WAIT_MAX_MS (5 min) even for a 20-minute command; constants: src/execution/sandboxErrors.test.ts::the executor's bounded wait::* (3)
14: a hard stop during the wait ends it at once with no further send[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor fleet-busy wait::a hard stop during the wait ends it at once with no further send
14: a run the fleet-busy ending kills is one queryable log line on each side, under the stable prefix sandbox.fleet-busy — the card's ending unchanged and the pre-model run record carrying the typed cause: the Worker logs sandbox.fleet-busy.refused (thread, Durable Object id, the platform's refusal, the route on a file op) at both sites that name the refusal and hands the answer its containerId; the executor's spent fleet wait carries the last answer's facts on ExecCapacityError.fleetBusy (refusal, wait, container — the fleet's ending alone, never a start's); the bot's ending site (the dispatcher's catch-all, the one place every failing run passes) logs sandbox.fleet-busy.run-ended once with the run id, the thread, the container, the refusal and the wait spent, and logs nothing for a run that ends any other way[unit] src/execution/sandboxErrors.test.ts::the fleet-busy ending log lines::* (7), ::the fleet-busy answer shapes the Worker sends::both shapes carry the Durable Object id when the Worker passes it, and omit the key when it does not; src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor fleet-busy wait::the spent wait carries the ending's facts — the Worker's refusal text, the wait and the Durable Object id — for the bot's ending log, ::an older Worker's busy answer without the container id still carries the refusal and the wait; src/execution/sandboxLifecycle.test.ts::sandbox Worker wiring (static)::a fleet-busy refusal is one queryable log line at both classification sites, and the answer carries the Durable Object id
14: the spent fleet wait has one typed classifier for the paired bot log and a pre-model run failure; malformed facts or another capacity error match neither. This adds no container reclamation or second telemetry ledger.[unit] src/execution/sandboxErrors.test.ts::the fleet-busy ending log lines::matches the error by name across an import boundary — a name-and-facts shape logs like the class itself, src/core/dispatcher.test.ts::run ledger write-through (docs/reference/specs/run-history.md item 35)::coordinator producer identity::records typed fleet capacity only when a review child failed before its model started
14: every OTHER in-body error — including an OLD Worker's bare Failed to create session: 503 — is still ExecInfraError after exactly ONE send (nothing else is replayed)[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor fleet-busy wait::every OTHER in-body error is still ExecInfraError after exactly one send — nothing else is replayed, ::an OLD Worker's bare Failed-to-create-session 503 (no reason) still reads as infra — one send
14: ExecCapacityError is its own class, never an ExecInfraError — a caller that reads infra failures as a dead sandbox never reads a full fleet as one[unit] src/execution/cloudflareSandbox.test.ts::*
14: fleet_busy notes survive the saved-stream parser and count as a medium infra_failure finding[unit] src/core/runEventLines.test.ts::accepts every declared run_note kind, fleet_busy included (docs/reference/specs/execution.md item 14); src/core/runFriction.test.ts::infra_failure: a fleet_busy note is an infra finding too (capacity is friction), at medium severity — the run went on
14 live: with the fleet full, one more cold run waits and then proceeds (a slot frees) or ends with ⏳ Sandbox fleet busy — … after waiting 300s and a normal write-up — never the dead-sandbox abort; max_instances 25 is live on the deployed Worker[agent] wrangler deployments status / the containers dashboard shows 25; force the condition by running max_instances+1 concurrent cold reviews, read the last run's page
11: the shared clamp rule — a 25-min ask runs at 20 min, 0/negative at the 1s floor, NaN/absent/non-numeric at the 5-min default[unit] src/execution/bashTimeout.test.ts::clampBashTimeout (every branch)
11: pi's bash is judged at the harness's gate by its explicit timeout alone — one reaching past the loop's end is refused before the command runs, one inside it and a call naming none run[unit] src/core/harness/pi/toolRules.test.ts::judgeToolCall — bash: an explicit timeout against the loop's end::*
11: the policy module names default (300000), floor (1000) and max (1200000) once, and the note names the limit that fired and the knob that raises it[unit] src/execution/bashTimeout.test.ts::bash timeout constants::*, src/execution/bashTimeout.test.ts::bashTimeoutNote::*
11: LocalExecutor honors the per-call budget (execFile timeout, maxBuffer kept) and a deadline kill is exit 124 naming the limit + the timeoutMs knob — never a generic abort, never the 124 wording for an ordinary nonzero exit[unit] src/execution/executor.test.ts::LocalExecutor per-call timeout
11: ResidentExecutor sends timeoutMs in the /exec body only when asked (older-resident body compat), clamped, kept across the one re-attach retry[unit] src/execution/resident.test.ts::ResidentExecutor per-call timeout
11: E2BExecutor maps the budget onto commands.run({timeoutMs}) and renders the SDK's TimeoutError as exit 124 naming the limit[unit] src/execution/e2b.test.ts
11: CloudflareSandboxExecutor sends timeoutMs in the /exec body only when asked, clamped[unit] src/execution/cloudflareSandbox.test.ts
11: every send to the sandbox Worker has a bot-side deadline of budget + 30 s covering the headers AND the streamed body — a fetch that never answers, and a body that heartbeats but never completes (a gone container under a still-heartbeating Worker), are both abandoned at budget + margin with ExecInfraError naming both numbers; no timeoutMs → 330 s; a 20-min (or a clamped 25-min) ask → 1230 s; /read and /write wait the 5-min file-op budget + margin[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor per-send deadline …::a fetch that never answers is abandoned at budget + margin…, ::a body that heartbeats but never completes…, ::no timeoutMs → the 5-minute default budget…, ::a 20-minute timeoutMs → 20 min + 30 s…, ::/read and /write wait the file-op budget…
11: the ResidentExecutor bounds the body read too — a heartbeating resident body that never completes is an ExecInfraError (resident worker /exec request failed) at budget + margin, which fail-fast counts, not a raw TimeoutError[unit] src/execution/resident.test.ts::ResidentExecutor per-send deadline …::a heartbeating body that never completes is an ExecInfraError at the command budget + margin, not a raw TimeoutError or a hang
11: the hard stop still aborts the send at once (reported as the stop, not as a silent Worker); an answer inside the deadline is unaffected and nothing fires after it; the deadline is per SEND — a fleet-busy wait before the send does not eat into it[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor per-send deadline …::the hard-stop signal aborts at once…, ::an answer inside the deadline is unaffected…, ::the deadline is per SEND…
11: execDeadline fires exactly at its timeout under fake timers (a plain timer, not AbortSignal.timeout) with a TimeoutError reason, and a joined hard stop aborts it at once with the stop's reason, the timer never re-firing[unit] src/execution/executor.test.ts::execDeadline::*
11 live: a cold (sandbox) run whose container is destroyed mid-command — wrangler containers stop/delete the thread's instance while a long sleep runs — gets sandbox worker /exec gave no answer within … as an ExecInfraError at budget + 30 s instead of hanging; the card reads running bash (Ns) meanwhile, and the run ends through fail-fast or the finale, never by an operator stop[agent] human-gated.
11: resident Worker clamps the body's timeoutMs server-side with the shared bounds (never trusts the client) and its exit-124 stderr names the limit + knob; /op keeps the flat 5-min budget[agent] (Worker wiring; the clamp itself is the shared unit-tested clampBashTimeout) On a warm resident: run \sleep 330 && echo LONG` with timeoutMs 360000→LONG; the same command without timeoutMs→exit 124at 300s with a stderr line namingtimeoutMsand1200000`.
11: a >5-min command completes end-to-end through a live resident (HTTP wait = budget + 30s margin, heartbeat keeps hops alive)[agent] The 360s check above must return output, not a bot-side resident worker /exec request failed transport error.
11: sandbox Worker sizes coreutils timeout from the body's timeoutMs (clamped); Dockerfile backstop (1240s) stays above the ceiling — needs a container rebuild to take effect[agent] In a non-resident thread: run \sleep 300 && echo OK` with timeoutMs 360000→OK; without it → exit 124` at 280s.
15: exec/readFile/writeFile are exec.* spans under the tool span with backend and budget, never command/path/output; release/moveTo/readBytes (exec.read_bytes) wrap only when present; a throw ends error and propagates[unit] src/execution/tracingExecutor.test.ts::TracingExecutor::*
15: the factory names the backend on every selection — local for the null and local executors, sandbox for the per-thread Cloudflare sandbox, resident for a warm resident[unit] src/execution/factory.test.ts::makeExecutor per-agent provisioning::an agent declaring no repo gets a null executor with cloudflare configured (no sandbox call, no token needed), ::a local backend gives model commands the run bearer without an App token, ::a repo-requiring agent gets the Cloudflare backend when configured, src/execution/factory.test.ts::makeExecutor resident selection::warm probe → ResidentExecutor, attached on open, with the resident discriminant set
16: the sandbox template's container is standard-4 — 4 vCPU / 12 GiB / 20 GB, the largest predefined type, rendered against the example profile and read back as JSON[unit] src/deploy/wranglerTemplate.test.ts::the sandbox Worker's container::the cold per-thread sandbox runs on standard-4 …
16: both execution images set NODE_OPTIONS=--max-old-space-size explicitly, to the same 8,192 MiB; the cap covers this repository's own typecheck with room[unit] src/deploy/imageNode.test.ts::the execution images' Node heap::* (3); [agent] at a checkout of this repository, NODE_OPTIONS=--max-old-space-size=1536 npx tsc --noEmit exits 134 with "JavaScript heap out of memory" and the same command exits 0 at 3072 and at 8192
16 live: after the sandbox Worker deploys, a cold run's nproc; free -m; df -h / prints 4 / ~12 GiB (Mem: total ≈ 12 000 MiB) / ~20 GB (/ size ≈ 19–20 GB)[agent] In a NON-resident thread: @switchboard agent:review run nproc; free -m; df -h / in <owner/repo>, report raw output. The previous standard-3 read 2 / 8189 MiB / 15 GB. Then the shape that motivated the size: the large monorepo's root typecheck in a cold thread completes instead of dying on memory.
17: the sandbox Dockerfile installs docker.io and iptables in the apt layer without recommends, still drops the apt lists in that layer, and installs the wrapper as /usr/local/bin/docker (mode 755) ahead of the engine's own client[unit] src/deploy/sandboxDocker.test.ts::the cold sandbox image ships a Docker engine::installs docker.io and iptables in the apt layer, without recommends, ::still drops the apt lists in the same layer, ::installs the wrapper as /usr/local/bin/docker, ahead of the engine's client on PATH
17: the wrapper is plain sh that parses, starts dockerd under setsid -f and never as a plain background job, enables IPv4 forwarding before the start, starts only when the socket is missing or docker info fails, gives up after 40 s naming /var/log/dockerd.log, ends with exec "$REAL" "$@", and stays under 45 lines[unit] src/deploy/sandboxDocker.test.ts::the docker wrapper::* (7)
17 live: a cold run pulls and runs a container with working networking, and the engine survives to the next command[agent] After the sandbox Worker+image rollout, in a NON-resident thread: agent:coding run \docker run --rm alpine:3 wget -qO- https://registry.npmjs.org/pnpm | head -c 80`, then `docker ps`(or the same two commands through a direct/exec on the sandbox Worker). Expect the first to print the start of the registry's JSON ({"_id":"pnpm"…) after a few extra seconds for the daemon start, and the second to answer at once with the engine already running. Failure signatures: docker: command not found= an old image still serving;docker: the engine did not come up within 40 s — see /var/log/dockerd.log= the daemon failed to start (read the log in a follow-up); JSON absent withbad address/network is unreachable` = forwarding or the bridge is off.
21: the Dockerfile installs the supervisor as /usr/local/bin/sandbox-runtime-supervisor.mjs (mode 755) and names tini running it as the image's one ENTRYPOINT, with no CMD[unit] src/deploy/sandboxRuntimeSupervisor.test.ts::the sandbox image's PID 1::is tini running the supervisor, installed at mode 755, named by the image's one ENTRYPOINT
21: the supervisor is dependency-free Node ESM that runs /container-server/sandbox unless SANDBOX_RUNTIME names another, pauses one second between starts, carries the ten-second / five-exit give-up, and maps a child's end to the shell convention (its code; 128 + the signal's number; 127 for a start that failed)[unit] src/deploy/sandboxRuntimeSupervisor.test.ts::the runtime supervisor::* (2)
21: supervise driven through its injected process factory, clock, sleep and signal source — no real process, shell or wall-clock wait in the tests: an exit is followed by a second start with the same arguments and a stderr line naming the status; SIGTERM reaches the live runtime and the supervisor exits with the runtime's own status, starting nothing more; SIGINT is forwarded as TERM; a stop during the pause between two starts, or before the first child is known, ends it with the last status and starts nothing again; five exits (or failed starts) in a row inside ten seconds of starting end it with the last status after exactly five starts, and a slower exit resets the count[unit] src/deploy/sandboxRuntimeSupervisor.test.ts::the runtime supervisor, driven::* (8)
21 live: a cold thread's workspace and a detached process survive the runtime's death, the supervisor starts the server again, and the next command runs without a re-clone[agent] After the image rollout, in a NON-resident thread: run \echo alive > /workspace/marker; setsid -f sleep 600; kill -9 $(ps -eo pid,comm | awk '$2=="sandbox"{print $1}')`(that command's own answer is lost with the server), thenrun `cat /workspace/marker; pgrep -x sleep | head -1`→aliveand a pid; the thread's container loggedsandbox-runtime-supervisor: the runtime exited with status 137; starting it again (containersdataset). Failure signatures: the second/execansweringHTTP error! status: 500= the old image (PID 1 was the server);pgrep` empty = the detached process went with the runtime.
22: the verdict — propose teardown on a full idle window or a stuck request, subject to the preservation fence; keep while served or serving; requests measured one by one[unit] src/execution/sandboxIdle.test.ts::idleVerdict::keeps a container served inside the window and says when to look again, ::proposes teardown at SANDBOX_SLEEP_AFTER, subject to the preservation fence, ::a request in flight is service, however long ago the previous one finished, ::proposes teardown for a stuck request, subject to the preservation fence, ::an overlapping chain of short requests is never one stuck request: the oldest LIVE request decides, ::a clock that went backwards is not an idle container
22: the constants agree — the millisecond window equals SANDBOX_SLEEP_AFTER, the sweep fits the window, the stuck cap clears the longest command[unit] src/execution/sandboxIdle.test.ts::the constants agree::SANDBOX_SLEEP_AFTER_MS is the millisecond twin of the SDK grammar string the Worker sets, ::the sweep runs several times inside one idle window, so a deadline is met within a minute of passing, ::a request counts as stuck only past the longest command the Worker admits plus its margins
22: the guard — a wake reads the baseline or takes now and arms one sweep; an unseeded leaked container may stop one window later, while a seeded/unknown checkout is retained without preservation proof; served() counts in and out and persists on cadence; the sweep re-arms while there is a container; the SDK's expiry uses the same fence; only a permitted destroy can end with a platform kill; overlapping deadlines destroy once[unit] src/execution/sandboxIdle.test.ts::IdleGuard::wake() takes the stored last-served time as the baseline and arms one sweep when none is scheduled, ::wake() with no record uses now — the leaked fleet's containers are idle from the deploy, and gone one window later, ::served() counts the request in flight, records the finish, and persists the time at most every PERSIST_EVERY_MS, ::served() records the finish and releases the count when the operation throws, ::a sweep during a request in flight keeps the container and re-arms; the deadline restarts from the request's finish, ::a request in flight past INFLIGHT_STUCK_MS does not hold the container: it is destroyed as stuck, ::the SDK's own activity expiry is answered by the same verdict — its process probes never decide, ::a destroy the SDK cannot finish inside DESTROY_GRACE_MS ends with the platform's own kill, ::a destroy that throws still ends with the platform's kill, ::two overlapping deadlines destroy once, ::a sweep on a Durable Object whose container is not running re-arms only while a request is in flight (a container starting), ::the first request after a destroy arms the sweep again, ::a host whose running state is unknown is guarded like a running one
22 live: a proven unseeded idle container stops within one window plus one sweep, while an idle seeded checkout without independent quiescence remains intact at five minutes and a seeded checkout with a stuck request remains intact beyond 30 minutes; only permitted stops log sandbox.idle-stop. Fleet count can exceed inFlight because preservation retains seeded or unknown containers.[agent] On an authorized disposable thread, compare wrangler containers list, GET /healthz inFlight and Worker sandbox.idle-stop logs: an unseeded marker file persists at 4 min and requires a fresh container after ~6 min; a seeded dirty checkout and .git still exist after ~6 min without a quiescence witness; with a controlled stuck request they remain past 30 min. No live preservation rehearsal is claimed by the source tests.
23: the start gate — one warm-up per not-running container, the starting answer without running the operation, the same answer while in flight, pass-through once up with the duration logged, a failed warm-up thrown once with the SDK's own error, a stopped container started again, no binding treated as not running[unit] src/execution/sandboxStart.test.ts::StartGate::a request to a container that is not running begins one warm-up and answers starting at once, without running the operation, ::while the warm-up is in flight every request answers starting and no second warm-up begins, ::once the container is up the operation runs, and the start's duration is logged, ::a running container passes the operation through without any warm-up, ::a failed warm-up is thrown once, on the next request, with the SDK's own error — a full fleet keeps its name, ::a container that stopped (the idle guard destroyed it) is started again by the next request, ::no container binding at all is treated as not running: the warm-up is what says what is wrong
23: joining a warm-up waits for the original host promise after the starting response, retains success or the exact rejection after one-shot failure delivery, and never turns caller abort, timeout, a new gate or a running flag into successful settlement; a captured join stays with its attempt across later warm-ups[unit] src/execution/sandboxStart.test.ts::StartGate warm-up settlement::*
23: the answer shapes and the wait constants[unit] src/execution/sandboxErrors.test.ts::the sandbox-starting answer shapes the Worker sends::names the reason, the explanation and the start's phase as the cause; the exec shape is the dual in-body form, ::the three wait tokens are the only reasons the executor re-sends on; anything else is an ordinary failure, ::the start budget covers the platform's own start allowances with room, and its poll is denser than the fleet wait's
23: a token change restarts the backoff ladder — starting, starting, busy re-sends after the fleet's first 10 s step, not the start ladder's 30 s[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor sandbox-starting wait::a token change restarts the backoff ladder: starting → starting → busy re-sends after 10 s, not 30 s
23: the instance grant is asked for with a ten-second limit[unit] src/execution/sandboxLifecycle.test.ts::sandbox Worker wiring (static)::the instance grant is asked for with a ten-second limit, so a refused start under a burst reaches the gate in seconds
23: the executor's start wait — identical re-sends at 5/10/15 s, the start budget over the command's, ExecCapacityError at the cap, the file routes' 503, the hand-over to the fleet budget[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor sandbox-starting wait::re-sends the SAME request after 5 s, 10 s, then 15 s while the container starts, and returns the eventual result, ::a 60 s command waits through a two-minute start: the start budget, not the command's, bounds the wait, ::gives up at SANDBOX_START_WAIT_MAX_MS with ExecCapacityError naming the start, never ExecInfraError, ::a /read answered HTTP 503 with reason sandbox-starting is the same wait, ::a start that turns into a full fleet is judged by the fleet's budget from then on, the time already waited counted
23: the Worker wires the gate on every route[unit] src/execution/sandboxLifecycle.test.ts::sandbox Worker wiring (static)::every route goes through the start gate; the warm-up is \true` through the SDK inside the idle ledger`
23 live: a run that falls to a fresh sandbox (a resident refusal, or a repository with no resident) reaches its first command instead of dying at 90 s; Workers Logs carry sandbox.starting and sandbox.started with the start's duration for that thread; the card's elapsed time includes the wait and the run goes on[agent] a review of a pull request in a repository with no resident, or one whose resident refuses on disk pressure; the run page's run.agent span carries exec spans after the start; wrangler tail switchboard-sandbox -c deploy/cloudflare-sandbox/wrangler.jsonc filtered on sandbox.start
24: the detach redirects are one string across the seam, the hint and the docs; the Worker's output wait stays inside the executor's margin; the held-output note names the code, the loss and the detach[unit] src/execution/sandboxLifecycle.test.ts::held output pipes::the detach redirects are one string, shared by the harness seam's start and the exit-124 hint, ::the Worker's wait for the output to end past the command's deadline stays inside the executor's per-send margin, ::the held-output note names the exit code, says the output is lost to this call and the job runs on, and spells the detach
24: the seam's start script redirects the wrapper's stdio[unit] src/core/harness/container.test.ts::the container scripts::starts the process detached behind a FIFO held open for writing, its pid recorded, its stdout through the filter into the log; the environment is not in the script
24: the Worker wires the bounded wait, the status question and both answers[unit] src/execution/sandboxLifecycle.test.ts::sandbox Worker wiring (static)::an output that never ends is told apart by the process's status: exited → its code and the held-output note, running → kill and exit 124, ::the timeout hint tells the model to detach a long job with setsid -f and the wrapper's stdio redirected, and never names nohup
25: the seed handle — every field checked and refused by name; the fix-up script — ownership, origin, the deps view, the thread's ref at its head or tip, the head last, values quoted, no credential; the constants under /workspace and the budget over both caps; the missing-backup classification by name and text[unit] src/execution/seedPlan.test.ts::parseSeed::*; src/execution/seedPlan.test.ts::seedFixupScript::*; src/execution/seedPlan.test.ts::the seed's constants::*; src/execution/seedPlan.test.ts::isBackupMissing::*
25: the Worker wires the seed — /seed through the ledger and the gate, presigned only, the marker, the sweep on failure, the streamed answer with its own root; the image carries squashfs-tools and image-provided rg for both fresh and seeded paths, never an agent download; the template's seed block names the resident's bucket exactly when the profile has a resident; the R2 secrets reach the sandbox[unit] src/execution/seedPlan.test.ts::the seeded sandbox wiring (static)::*; src/deploy/wranglerTemplate.test.ts::templateView::*
25: the client posts the handle and run-bearer env under the seed's budget, returns the verdict as it came — a refusal is an answer — and treats an answer without one as infra[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor seed::*
25 live: npm run load -- sandbox --seed-from repo:<slug> --threads 1 against a resident with a snapshot answers seeded: true with the restore, deps and fix-up timings, and git -C /workspace/checkout log -1 in that sandbox shows the snapshot's head; the same handle again answers cached; a handle from a rotated snapshot answers seed-missing[agent] the load receipt and the Worker's sandbox.seeded log line, posted on the tracking issue
26: the thread's seed from the handle (the ref and head ride along; no ref stays on the snapshot's branch), the retry-then-cold decision, the card's line[unit] src/execution/seedPlan.test.ts::seedForThread::*; src/execution/seedPlan.test.ts::seedRetryDecision::*; src/execution/seedPlan.test.ts::seededSandboxNote::*
26: the factory — one /seed with the thread's ref and head, the seeded facts and the note; the fresh-probe retry on seed-missing, the same handle → cold; an unclaimed seed's transport failure or older Worker without /seed → cold with the refusal; a claimed seed's transport failure, missing route or invalid receipt → setup refusal; nothing to seed from → the cold path unchanged; a local type seeds nothing[unit] src/execution/factory.test.ts::makeExecutor seeded sandbox::*; src/execution/factorySeedPreservation.test.ts::initial sandbox seed owner provenance::refuses a claimed seed with missing or malformed container before recording a binding or falling cold; ::refuses a claimed seed when an older Worker has no /seed instead of falling cold
26: a pending stop starts no seed or credential cleanup; a stop during cleanup, seeding, or the refresh probe closes setup without a model call; capacity and transport retry waits end with the typed stop and no further send[unit] src/core/dispatcher.test.ts::run ledger write-through (docs/reference/specs/run-history.md item 35)::a hard stop during cold %s aborts the request and closes the reservation without starting the model; src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor seed::an already stopped seed starts neither credential cleanup nor the seed request, ::CloudflareSandboxExecutor seed::a hard stop during the seed's %s wait aborts without another send
26: the seeded prompt variants, the composer's choice (resident wins; no variant → the agent's own prompt), the REVIEW TARGET block's seeded branch[unit] src/agents/registry.test.ts::seeded prompt variants::*; src/core/reviewRound.test.ts::makeSystemComposer — the seeded sandbox::*; src/core/reviewTarget.test.ts::reviewTargetBlock — the seeded sandbox::*
26 live: a review on a repository whose resident refuses the attach runs on a seeded sandbox — the card's note names the seed, the run's first command finds /workspace/checkout at the PR head; load:resident at N = 24 on one repository shows 16 warm and 8 seeded with named notes[agent] the run page and the load receipt, posted on the tracking issue
24 live: a run that falls to a fresh sandbox reaches pi's first turn — the start command answers in under a second and the run page's run.agent span carries exec spans; a command that detaches a job without the redirects answers by its deadline plus 20 s with the held-output note instead of dying at the executor's[agent] a review of a pull request in a repository with no resident; then, in a cold thread, setsid -f sleep 300 without redirects under a 10 s timeoutMs — the answer carries heldOutputNote and the run goes on
27: RestoreWaiters — the ledger of held requests: wait() holds until publish, one publish answers every held waiter with the outcome and empties the ledger, a later waiter is held for the next transition, a publish with nothing held is a no-op; hold() registers before the caller's own read and withdraw() takes an unanswered waiter off the ledger[unit] src/execution/restoreWaiters.test.ts::RestoreWaiters — the ledger of held /await-restore requests (item 27)::*
27: the resident Worker's route — /await-restore exists at operator scope, streams through the heartbeat convention, validates then answers a not-onboarded resource 404; awaitRestore registers its waiter before it reads the state, withdraws it and answers at once when the state is not restoring, and otherwise holds on the ledger with no timer and no poll; setResidentState publishes on every transition out of restoring; the ledger is the shared RestoreWaiters[unit] deploy/cloudflare-resident/awaitRestore.test.ts::POST /await-restore — the one held request while the resident restores (item 27)::*
27: the bot's attach path — a restoring probe opens the one held request; a warm answer attaches with the wait named on the card, a serviceable non-warm answer attaches naming the wait and the snapshot, an older Worker's 404 falls back cold with the wait named as unsupported, a non-serviceable landing names the state and the wait, an attach that fails after the wait names the failure and the wait, a wait that fails in transport is a named cold fallback, never a throw, and the run's stop during the hold is the stop's typed shape, never a cold fallback[unit] src/execution/factory.test.ts::a restoring resident — the one held /await-restore request (item 27)::*
27: the re-attach of a recorded resident holds the same request — a restoring probe holds /await-restore instead of refusing at once; a serviceable landing re-attaches the run's own worktree with the wait named; a non-serviceable landing, an older Worker's 404 and a failed wait are the typed re-attach refusal naming the wait, never a sandbox; the run's stop ends the hold with the stop's typed shape and a lease that ran into its write-up reserve under the hold is the run's end on its budget, never a refusal[unit] src/execution/factory.test.ts::a restoring resident on a re-attach — the restore is waited through, never refused at once (item 27)::*
28: the recognizer takes the platform's accept wording as a plain Error or a bare shape and nothing else; the message starts with the token, says nothing ran and the request is re-sent, and names the container and the platform's words; the typed error is recognized by name or by token after the RPC boundary and is neither fleet-busy nor runtime-unreachable; the platform's bare words are not the token; neither the explanation nor the exhausted message asserts a cause the platform never proved[unit] src/execution/sandboxErrors.test.ts::the runtime-busy signal, message and error::*
28: the answer shapes — /read//write {error, reason: "runtime-busy"}, /exec the dual form plus the reason — and the three wait tokens[unit] src/execution/sandboxErrors.test.ts::the runtime-busy answer shapes the Worker sends::*; src/execution/sandboxErrors.test.ts::the sandbox-starting answer shapes the Worker sends::the three wait tokens are the only reasons the executor re-sends on; anything else is an ordinary failure
28: the executor's wait — identical re-sends at 3/5/10 s and the eventual result, ExecCapacityError at the command's own budget and at the 5-minute cap, the file routes' 503, an older Worker's bare text one send as answered[unit] src/execution/cloudflareSandbox.test.ts::CloudflareSandboxExecutor runtime-busy wait::*
28: the Worker wires the token at the spawn and the warm-up alone, never on a running command's output; the file routes' typed error and the fetch handler's 503; the name after the RPC boundary[unit] src/execution/sandboxLifecycle.test.ts::sandbox Worker wiring (static)::a refused connect is the runtime-busy token at the spawn and the warm-up alone, the file routes' 503, and named after the RPC boundary
28 live: a cold thread running a whole-repository gate while the harness polls its transcript keeps the run — the sandbox Worker's logs for that thread carry runtime-busy answers followed by the poll's success, never a sandbox.exec error … taking too long to accept that ends the run[agent] a coding run in a repository with no resident whose gate is npm run verify; the run page's exec spans and wrangler tail switchboard-sandbox -c deploy/cloudflare-sandbox/wrangler.jsonc filtered on runtime-busy
31: operator-only, visible live executor, strict count-only output and fail-closed inspection; the resident Worker validates before transport, never uses output recovery, and pins the PID to birth identity captured before the harness program starts; cold refuses before remote execution; no publication authority[unit] src/core/commands/credentials.test.ts::credentials.inspect::*, src/core/credentialInspection.test.ts::live credential inspection::*, src/execution/credentialInspection.test.ts::credential inspection transport::*, src/execution/credentialInspectionRemote.test.ts::remote credential inspection::*, src/execution/credentialProbeSource.test.ts::credential probe source::*, deploy/cloudflare-resident/credentialInspection.test.ts::resident credential diagnostic::*, src/core/harness/container.test.ts::harness launch identity::*

| The Sandbox deployment template retains a ceiling of 250 in both build and registry image modes, so regenerating configuration for a release cannot restore the former ceiling of 25. | [unit] src/deploy/wranglerTemplate.test.ts::the sandbox Worker's container::keeps the deployed sandbox ceiling at 250… |

| Sandbox configuration renders the state endpoint for cancellation checks independently of optional resident cache bindings; a cold profile has no resident bucket. | [unit] src/deploy/wranglerTemplate.test.ts::sandbox runtime cancellation configuration::renders the state endpoint independently of optional resident cache bindings | | Fresh and seeded sandbox images provide rg and fd on PATH. The image build proves fd can find a source file; runs need no search-binary download | [agent] Run npm run check:image -w deploy/cloudflare-sandbox; the build must pass its PATH probes and fd --glob fixture search. This proves the candidate image; deploy and a natural fallback run remain separate live proof. | | Failed workspace movement identifies its command stage, exit status, truncation and redacted failure output. | [unit] src/execution/workspaceAdvance.test.ts::executor workspace advance::identifies the failed command stage, exit status and redacted output |