Files
PaperClipAI/packages/adapter-utils
Nicky LeachandPaperclip 2e5bf12411 test(adapter-utils): retry HTTP/2 fixture port collisions (#16888)
## Thinking Path

> - Paperclip manages work for AI agents.
> - Sandbox agents use a callback bridge to reach the API.
> - Tests start the generated HTTP/2 gateway on an assigned loopback
port.
> - The test probe releases that port before the gateway binds it.
> - Another process can claim the port during that gap.
> - This PR retries that specific fixture startup collision and tests
its limits.

## Linked Issues or Issue Description

**What happened?**

The unchanged callback bridge test failed in #16605 because its selected
port was already in use. [CI job
118251110752](https://github.com/paperclipai/paperclip/actions/runs/39308303837/job/118251110752)
reports `EADDRINUSE` before the gateway READY frame. The same helper is
present on master `4d92589799`; no merged or open fix for this exact
failure was found.

**Expected behavior**

The fixture starts on a fresh port after a confirmed bind collision. It
stops after three attempts and preserves other startup failures.

**Steps to reproduce**

The new held-probe case keeps the selected socket open before the real
child starts. Before the correction, the original authorization test
fails with `EADDRINUSE`. The unchanged no-collision case passes.

**Additional context**

This is the separate scoped correction for #16605's unrelated CI
failure. It changes one test file.

## What Changed

- Retry only pre-READY gateway exits with code 1 and the specific
`EADDRINUSE` bind diagnostic.
- Wait for child close and fully drained stderr before classifying
startup. Kill failed attempts and preserve the original failure after
three collisions.
- Keep the real probe socket open to inject collisions without a timing
race.
- Verify successful recovery, bounded exhaustion, delayed stderr, and
immediate failure for unrelated startup errors. Accept successful starts
anywhere within the three-attempt limit. Preserve the original
authorization and header-stripping assertions.

## Verification

- Negative controls: the held-port authorization case fails with
`EADDRINUSE`, and retry exhaustion observes only one attempt before the
correction. The ordinary case passes.
- `pnpm exec vitest run
packages/adapter-utils/src/sandbox-callback-bridge.test.ts`: all 89
tests pass on the final head.
- `pnpm --filter @paperclipai/adapter-utils typecheck`: passes.
- Initial standard Greptile returned 4/5 for late stderr classification
and exact success-count assertions. Both are corrected. The final
real-pipe control reproduces missing diagnostics with the old exit
listener and passes with close-based readiness. An initial pause-only
control was not discriminating because Node resumes pipes on exit; the
final control holds that resume until after exit.
- Diff checks and the complete secrets/PII review pass.
- Full local repository tests and build were not run for this one-file
fixture change. Hosted CI and standard Greptile are pending.

## Risks

The fixture can still fail after three consecutive collisions. Other
startup failures remain failures. The generated gateway and its
assigned-port protocol are unchanged.

## Model Used

OpenAI Codex, GPT-6, with reasoning, repository inspection, code
execution, and testing. The exact deployment model ID and context window
are not exposed in this session.

## 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
- [ ] All Paperclip CI gates are green
- [ ] 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>
2026-10-19 12:49:25 -07: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.

Sandbox bridge deadlines

Command-managed bridge control operations (queue reads, input delivery, setup, and cleanup) have a host-enforced deadline of at most 30 seconds per shell command. A shorter configured timeout still applies. These operations cannot inherit the agent run's hours-long lifetime or wait forever on a provider that ignores its timeout. Long-lived agent session commands keep their own limits.

A control timeout reports a transport failure. It does not prove that the remote command stopped and does not replay an uncertain write. The process session closes failed input delivery and uses its existing shutdown path; normal execution settlement must still verify termination.

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.