Files
PaperClipAI/packages/paperclip-runner/runner
DottaandPaperclip c39e076638 fix(runner): retain the cause of closed ACPX sessions (#18872)
## Thinking Path

> - Paperclip helps people manage AI agents and their work.
> - Runner records provider failures so the task can show their cause.
> - An ACPX control request can fail and close its session before the
event pump runs.
> - The next poll then reports only that the session is closed.
> - A second cleanup error can also drop a valid startup cause.
> - This pull request retains each failure at its existing reporting
boundary.
> - Operators can inspect the cause while the failed session stays
closed.

## Linked Issues or Issue Description

Related: Refs #18622, #18847, #18854. Those changes preserve provider
causes and handle proven startup failures. This change covers a control
failure after provider execution starts.

**What happened?**

A rejected sidecar control closes the ACPX session. A later poll
replaces its error with `ACPX provider session is closed`. The native
terminal loses the control classification. Separately, cleanup can
discard an existing validated startup cause.

**Expected behavior**

Retain the first control classification in the failed terminal. Do not
invent a provider explanation for that code. Keep a validated startup
cause when cleanup also fails. Keep the session closed, uncertain tool
outcomes pending, and the existing recovery checks effective.

**Steps to reproduce**

1. Start an ACPX turn and receive an authorized tool call.
2. Return its result after the provider has retired that call. The
sidecar rejects `tool.resolve`.
3. Poll the native backend. Before this change, the terminal reports
only the closed-session guard.
4. For the separate startup case, combine a rejected `session.open`
cause with a cleanup error. Before this change, the combined error drops
its validated provider cause.

**Paperclip version or commit**

The two subprocess regressions and the cleanup-composition regression
fail against unchanged production code at `683fa4725e`.

**Deployment mode**

Native Runner with ACPX. The process regressions use the real Rust
subprocess transport and durable native backend. Their controlled
stale-tool rejection matches the production sidecar response shape. The
separate cleanup unit injects a cleanup error at the existing
error-composition boundary.

## What Changed

- Retain the first failure in the ACPX session and return it from later
closed-state checks.
- Keep the validated provider cause when cleanup also fails.
- Match the stale-tool fixture to production. It carries the closed code
and no invented provider cause.
- Cover a rejected tool result, normal shutdown, raw sidecar-text
exclusion, pending tool preservation, refusal of another turn, and
terminal replay after controller restart.
- Document the existing failure and recovery boundary.

## Verification

- Red: both process regressions fail on the base code. Polling replaces
the named control failure with the generic closed-session error. The
cleanup unit also fails because the combined error loses its existing
startup cause.
- Green: all 12 ACPX session tests, all 36 native backend tests, and all
four session unit tests pass. The stale-tool cases require the
classification and absence of an invented provider cause.
- Commands: `cargo test --manifest-path
packages/paperclip-runner/runner/Cargo.toml --locked -p
paperclip-runner-core --test acpx_provider_session` and the same command
with `--test native_provider_backend`.
- Map structure and whitespace checks pass.
- At head `981faa4`, the full Runner core Rust suite passes: 847 tests
passed and three existing tests are ignored. Workspace typecheck and
build pass. The existing TypeScript provider-failure and
durable-settlement boundary suites pass all 30 tests.
- An earlier concurrent local Rust attempt missed an existing Codex
diagnostic deadline. That case passes alone on baseline and head, and
the final full suite passes with the local socket support its tests
require.
- Complete current-head CI passes: 55 successful checks, five
intentional skips and two neutral checks; no failed or pending checks.
Both `ci / verify` and `ci / e2e` pass.
- Failed CI attempts were investigated before rerunning affected jobs.
Logs showed runner shutdowns/lost communication, an unchanged Windows
WebSocket authentication deadline, and an unchanged Cursor Cloud
Stop-fixture assertion. The Cursor Cloud case passes on baseline and
head. No test assertion or CI guard was disabled.
- The local full `pnpm test:run` invocation passed all six chat
partitions, covering 1,838 expanded cases. It was stopped as a duplicate
while still in general-server after complete sharded CI passed. A
completed local full Vitest run is not claimed.
- Greptile and Paperclip reviewed the corrected head at 5/5 with no
actionable findings. There are no unresolved review threads.
- The process test exercises control rejection, process cleanup, durable
terminal persistence, and controller restart. The provider boundary is a
deterministic sidecar fixture. It is not a live Claude or hosted
recovery test.
- A genuine provider refusal or uncertain external effect remains
unresolved. This change preserves its evidence and does not make it safe
to replay.

## Risks

- The first control classification becomes visible where a generic
closed-session error appeared before. Raw sidecar error text stays
excluded. Startup details use the existing validated and redacted
`ProviderFailure` contract.
- A stored failure must not grant execution authority. The failed
session still rejects calls and new turns. Normal shutdown has no
fabricated failure cause.
- No provider profile, digest, retry budget, process ownership rule,
migration, credential, or permission change is required.

## Model Used

OpenAI Codex, based on GPT-6, with source inspection, code editing,
command execution and regression tests. The exact runtime snapshot and
context-window capacity 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 and available 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 described the issue in this PR with the bug template fields
- [x] I have not referenced internal or instance-local Paperclip issues
or links
- [x] My branch name describes the change and contains no internal
ticket ID
- [x] I have run the local Rust, typecheck, build and boundary checks;
complete sharded CI passes and the partial local Vitest result is
reported above
- [x] I have added behavioral regression tests
- [x] I have updated the relevant execution and run-log documentation
- [x] I have considered and documented the risks
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open findings
- [x] I will address all review findings before requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-11-04 07:17:58 -06:00
..