paperclip/packages/adapter-utils
Michel Tomas e03a30dfac
fix(adapter-utils): close two leaks the scanner's reading set missed
A single quote and an ANSI-C `$'` are what JSON serialization leaves alone,
so unlike a double quote they name no layer. The reading set gave them
depth 0 alone, so a serialized double-quoted segment adjacent to the closing
quote was read as an escaped quote followed by a word-ending space, and its
bytes stayed in the clear at every depth from 1 up. Bash joins them to the
header word. Seed the tail after such an argument at every layer the unquoted
reading set already carries and take the longest, which is what the union
policy asks for.

The body stays at depth 0 on purpose: these quotes delimit the same bytes at
every layer, and reading the body deeper would take an ANSI-C escape for a
plain backslash and close the value early.

A backslash-newline is a line continuation. The token reader already named
it, but the word scan returned at it as though it were a boundary, so the
bytes on the next physical line stayed in the clear after an unquoted value
and after a closed quoted argument. The shell removes the pair and joins the
lines into one word, so the scan follows it now. Inside a quoted part the
handling is unchanged.

Claude-Session: https://claude.ai/code/session_01RYigf3eMFJjey9iKRApPGE
2026-09-06 02:45:31 +02:00
..
src fix(adapter-utils): close two leaks the scanner's reading set missed 2026-09-06 02:45:31 +02:00
CHANGELOG.md
README.md
package.json
tsconfig.json

README.md

@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.