Files
PaperClipAI/packages/adapter-utils
Priya RamanandPaperclip 1646bb072b fix(adapter-utils): end a stalled duplex stdout write on a bounded stall timeout
A backpressured stdout write in the duplex gateway waited for drain or
a stream error, with no other end condition. A pipe that stays open
but stops draining -- for example a reader that stops consuming
without closing the pipe -- never fires either event, so the wait,
the send, and the admission reserve for that read stayed open
forever. Later requests then answered 503 bridge_busy for good.

Add a stall-timeout end condition to the embedded stdout frame writer.
It reuses the existing response timeout as its bound: a write that
stays backpressured that long already means no response can arrive in
time, so ending the wait costs nothing beyond what the response side
already gives up on. Once the bound elapses, the writer treats the
wait exactly like a stream error -- it strands every queued line and
refuses further writes -- so the same finally block that already
releases the reserve on a stream error now also runs.

Add a unit test that proves a stalled write past its bound rejects and
strands the queue, and a companion test that proves a write which
drains before the bound clears its timer and settles normally. Add an
end-to-end test that pauses the real child's stdout pipe and never
resumes or destroys it, proving a later request is admitted again once
the stall bound elapses. Confirmed each new test fails against the
prior commit and passes after this change.

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-08-23 22:33:44 +00:00
..

@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) to remoteDir before the run starts. Runs with no git remote configured.
  • restoreWorkspaceFromSshExecution({ spec, localDir, remoteDir, ... }) — syncs the remote cwd back into localDir after the run, including any new commits the agent created. Also runs with no git remote configured.

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.