The sandbox process-session bridge writes each stdin message to its own remote file. The remote stdin poller reads the files in name order and holds that order only across files it already sees. It cannot know that a file with an earlier name is still on the way. The socket handler fired each stdin write without serialization, so two writes could interleave. Each write runs several execs, so a later stdinEnd file could land before the earlier data file it must follow. The poller then ended the child stdin before the input file landed, and the child exited with no output. Under load this made the streamed-output bridge test deliver an empty stdout instead of the echoed input. Chain the stdin writes so each file lands before the next write starts, which keeps the files in send order. Chain the teardown stdinEnd write too, so it lands after any in-flight input file. Add a regression test that delays the first data file's finalize exec, so the stdinEnd write wins the race, and proves the bridge still delivers the echo. 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.