Files
PaperClipAI/packages
DottaandClaude fcf4e39b41 fix(runner): do not count stopped time against a turn start or an ACPX sidecar request (#20720)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - A native run starts each provider turn with `turn.start`. The
control plane waits for the Runner, and an ACPX Runner waits for its
sidecar, which starts the provider and sends the prompt.
> - Both waits use a 30 s bound. When the host or container stops the
whole tree (SIGSTOP, a host sleep), the clocks keep moving while no
process can act.
> - On thaw both bounds have passed. The control plane reports `PRP
command turn.start timed out`, and the Runner fails the sidecar request
and closes the ACPX session.
> - #20704 added a stopped-time clock for startup and recovery. This
pull request puts `turn.start` and its follow-up waits on the same
clock, and adds the same rule to the Runner's sidecar response wait.
> - The benefit is that a freeze during a turn start no longer fails the
turn or closes the session.

## Linked Issues or Issue Description

**What happened?**
Restart-chaos wave `w-20261111T011704-s9107`, task t05 (local, claude,
workload C1, fault TM2). A follow-up run sent `turn.start` at 01:22:21.
The harness then stopped the server, Runner, sidecar and Claude twice:
28 s, then 33 s after a 1 s thaw. At 01:23:25 the control plane reported
`provider_transport_failed: PRP command turn.start timed out`. Three
same-run resumes then failed with `ACPX session is not open`, recovery
stopped with `native_session_retry_exhausted`, and the task needed a
manual Retry. The Retry run succeeded.

**Expected behavior**
Time that the processes spend stopped does not count against the
turn-start bound or the sidecar request bound. A real timeout still
fires while the processes run.

**Steps to reproduce**
1. `cd packages/paperclip-runner && npx vitest run
src/live/runnerd-codex-transport-stall.test.ts`. On master it fails with
`provider_transport_failed: PRP command turn.start timed out`.
2. `cargo test --manifest-path
packages/paperclip-runner/runner/Cargo.toml -p paperclip-runner-core
--test acpx_sidecar_stopped_time`. On master it fails with `ACPX sidecar
request timed out at initialize`. It stops the process early in the wait
and again in its last half second.

Note on the repeated `ACPX session is not open`: the wave ran
`eafd5853d9`, which does not contain #20675. On master, #20675 settles a
cut-off start from the sidecar's dispatch journal. If the prompt never
left, the Runner reopens the session and sends it once. If the journal
shows the prompt left, or cannot prove it, the session stays closed and
the run ends with a working Retry, as the class rule requires. This pull
request stops the freeze from cutting off the start in the first place.

Refs #20704, #20675

## What Changed

- `packages/paperclip-runner/src/live/runnerd-codex-transport.ts`:
`#startTurn` runs under `#withAdmissionClock`. The turn-start deadline,
the held-tool-call wait, the start-event wait, the adoption check time
and the `session.snapshot` wait all read `#waitNow()`, so they share one
stopped-time credit.
-
`packages/paperclip-runner/runner/crates/runner-core/src/acpx_sidecar_transport.rs`:
`receive_response` blocks in 1 s slices. `credit_stopped_time` treats a
slice overshoot beyond 2 s as a stop. It moves the deadline by the
overshoot, and to at least 5 s after the thaw, so a stop in the last
slice still leaves the sidecar time to answer. Total credit per request
is capped at 15 min.
- The control-plane clock (`RunnerStallLedger`) now uses the same rule:
a stop is credited in full plus a 5 s grace, capped at 15 min per
admission.
-
`packages/paperclip-runner/src/live/runnerd-codex-transport-stall.test.ts`
(new): drives the real transport and Runner. It SIGSTOPs the Runner
tree, jumps `Date.now` by 40 s mid-wait, then resumes the Runner. It
fails if the turn start leaves the clock, or if the command wait stops
reading it (checked by removing `clock: this.#admissionClock`). This is
the transport-level test requested in the #20704 review.
-
`packages/paperclip-runner/runner/crates/runner-core/tests/acpx_sidecar_stopped_time.rs`
(new, its own test binary): a sidecar stops the test process for 6 s
against a 3 s bound and answers after the thaw.
- Unit test for `credit_stopped_time`.

## Verification

- Red on master, green here: both commands in "Steps to reproduce".
- `npx vitest run src/live/runnerd-codex-transport.test.ts
src/live/runnerd-codex-transport-stall.test.ts
src/live/runnerd-acpx-source-reconciliation.test.ts
src/live/runnerd-codex-transport-adoption.test.ts`: 342/342 pass.
- `cargo test -p paperclip-runner-core --lib acpx_sidecar_transport` (12
pass), `--test acpx_sidecar_transport --test acpx_park_preparation
--test acpx_provider_turns` (all pass), `cargo fmt --check` clean.
- `npx tsc --noEmit -p tsconfig.json` and `-p tsconfig.test.json`:
clean.
- Not run: a new chaos wave.

## Risks

- A real stall longer than 2 s in the Runner (or 5 s in the control
plane) during a turn start or a sidecar request now extends that wait by
the stall plus 5 s. The waited-on process could not be observed during
the stall either. The 15 min cap bounds a process that keeps stalling.
- The Rust wait now wakes once per second while it waits for a sidecar
reply. This is an active request with no I/O per wake.
- The 1 s control-plane ticker from #20704 now also runs during a turn
start, and stops when the start returns.
- No persisted state, launch identity or saved-session shape changes.
Old saved runs resume through the same code, and an older Runner keeps
its old wait. Nothing new runs before `turn.start`, and no wait is added
on the normal path.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- Claude (Anthropic), model ID `claude-opus-5-5`, run in Claude Code
with tool use (shell, file edit) and extended reasoning.

## 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

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-10 23:25:32 -06:00
..