## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Adapter utilities run remote process-session wrappers for sandbox work. > - A wrapper can outlive its host run when the host removes its session directory during shutdown. > - A failed directory read can look like an empty queue, so the wrapper can poll forever. > - This pull request adds an explicit shutdown acknowledgement and fail-closed identity checks. > - The benefit is deterministic wrapper cleanup without killing an unrelated session. ## Linked Issues or Issue Description Refs: #11916 **What happened?** Remote process-session wrappers could remain after a host run ended. The host could remove the session directory before the wrapper read the shutdown marker. The wrapper then treated directory errors as an empty queue and continued to poll. **Expected behavior** The host must receive an explicit shutdown acknowledgement before it treats the wrapper as stopped. The wrapper must stop when its session identity becomes invalid or untrusted. **Steps to reproduce** 1. Start a remote process-session wrapper. 2. Stop the bridge while the wrapper polls its session directory. 3. Remove the session directory during the poll. 4. Observe that the wrapper must terminate with its child. **Paperclip version or commit** `7cfbd1ecbe4a40261ba51fed07f624524352ada2` **Deployment mode** Built from source with the adapter-utils test suite. ## What Changed - Add a shutdown control file and wait for a bounded `shutdownAck` before session cleanup. - Require `shutdownAck` as proof of host-side shutdown. - Capture and verify session and stdin directory identity before each poll. - Terminate and latch the wrapper on missing, changed, linked, non-directory, or untrusted paths. - Reject unusable creation times and treat all identity-check `lstat` errors as terminal. - Add focused regression coverage for shutdown races and identity failures. ## Verification - `npx vitest run packages/adapter-utils/src/execution-target-stdin-race.test.ts` passes. - The full execution-target set passes: 175 tests across three files. - The `packages/adapter-utils` typecheck passes with `tsc --noEmit`. - CI will run on this pull request. - Greptile will review the pull request. ## Risks - A platform with unreliable directory creation times can stop a wrapper earlier than before. This fail-closed result prevents an orphan. - A transient identity-check error now stops the wrapper. This favors cleanup over continued polling when the session identity cannot be trusted. - Session cleanup remains unconditional after the bounded acknowledgement wait. ## Model Used OpenAI Codex — GPT-5. Context window size is not exposed in this run. The model used tool calls and code execution. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [x] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- 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.