reserveBodyRead answered 503 bridge_busy for two different conditions: the bound is transiently full (a retry can succeed) and the read's own charge is larger than the whole bound (no retry ever succeeds). The second case invites the same endless-retry defect the earlier 502-on-oversize-body fix closed. Split the two. reserveBodyRead now returns a RESERVE_NEVER_FITS marker when reserveBytes alone exceeds maxInflightBodyBytes, and both gateways answer a non-retryable 413 request_too_large for that marker, before they take any reserve. They keep the existing 503 bridge_busy for the transient case. In-spec traffic at the production defaults does not reach the never-fits branch (the file gateway's worst-case charge is about 42 MiB against a 64 MiB bound); this is a backstop, so a future factor change that breaks that property fails loudly with a clean 413 instead of turning legal traffic into a retry loop. Also add explicit coverage for the three real no-declared-length request shapes: a DELETE with no body sends no Content-Length header; a POST with a streamed body sends chunked transfer encoding and no Content-Length; a POST with an explicit empty body sends Content-Length: 0. All three now reach the host worker at the production inflight default, not a 503. Verified: 45 tests in sandbox-callback-bridge.test.ts (2 new), 19 in duplex-bridge-local-e2e.test.ts, 136 in execution-target-sandbox.test.ts, and 2 in execution-target-ssh-buffer.test.ts pass. npx tsc --noEmit on packages/adapter-utils is clean. Co-authored-by: Paperclip <noreply@paperclip.ing>
@paperclipai/adapter-utils
Shared utilities for Paperclip adapters: process spawning, environment injection, sandbox/SSH transport, workspace sync, and the round-trip helpers that move code between the local execution-workspace cwd and wherever the agent actually runs.
For the adapter-author guide see
docs/adapters/creating-an-adapter.md
and the in-repo notes at packages/adapters/AUTHORING.md.
No-remote-git contract
The local execution-workspace cwd is the only persistence boundary across runs. No adapter may depend on a git remote for cross-run state.
Adapters that run the agent on a different host should use the SSH round-trip
helpers in src/ssh.ts:
prepareWorkspaceForSshExecution({ spec, localDir, remoteDir })— bundles the local cwd (tracked files, dirty edits, untracked additions, and the git history needed to reconstruct it) toremoteDirbefore the run starts. Runs with nogit remoteconfigured.restoreWorkspaceFromSshExecution({ spec, localDir, remoteDir, ... })— syncs the remote cwd back intolocalDirafter the run, including any new commits the agent created. Also runs with nogit remoteconfigured.
prepareRemoteManagedRuntime in
src/remote-managed-runtime.ts wraps both
calls for adapters that want a per-run remote workspace and an automatic
restoreWorkspace() finally hook.
The invariant is pinned by the no-remote-git contract case in
src/ssh-fixture.test.ts, which asserts that a
remote-only commit propagates to the local worktree through the
prepare → restore round-trip with no git remote configured at any point. Do
not regress that test.