Files
PaperClipAI/doc
DottaandClaude Opus 5.5 d1e4109df1 fix(runner): keep a connected adopted Runner through a failed liveness probe (#20965)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - After a controller restart, the controller adopts the remote Runner
that kept running in the sandbox. A monitor then checks the Runner's
liveness every 250 ms through the environment plugin.
> - #20940 and #20945 made a stalled plugin probe "unknown" for the
launched Runner's monitor, reattachment and launch. The adopted Runner's
monitor still failed the run on the first probe that failed without an
answer.
> - A short plugin stall after adoption therefore failed a healthy run
with `native_adopted_runner_identity_unverifiable`.
> - This pull request uses the evidence that is available: while the
adopted Runner holds its authenticated connection to the controller, a
liveness check that fails without an answer does not fail the run, for
at most the reconnect grace.
> - The benefit is that a healthy adopted Runner survives a short plugin
stall. A check that reports the process gone still fails the run at
once.

## Linked Issues or Issue Description

Refs #20940, #20945

**What happened?**
`#watchAdoptedRunner` in
`packages/paperclip-runner/src/live/runnerd-codex-transport.ts` called
`failTransport("native_adopted_runner_identity_unverifiable: runner
liveness could not be revalidated")` on the first `isAlive()` rejection.
For a remote adopted Runner, `isAlive()` is a plugin
`environmentExecute` RPC. A plugin stall makes it reject, even while the
Runner holds its authenticated connection and works normally.

**Expected behavior**
A rejection is not an answer. While the Runner holds its authenticated
connection, the monitor keeps checking. The bound is the reconnect
grace. A check that returns `false` (the process is gone) still fails
the run at once.

**Steps to reproduce**
1. Adopt a remote Runner after a controller restart.
2. Stall the plugin worker, so that the liveness probe rejects.
3. Within 250 ms the run fails with
`native_adopted_runner_identity_unverifiable`.

**Paperclip version or commit**
master at ef755cecc6.

## What Changed

- `#watchAdoptedRunner`: on an `isAlive()` rejection, the monitor
records when verification was first lost. It does not fail the transport
while the control plane has an authenticated Runner connection and less
than `runnerReconnectGraceMs` has passed. A successful check resets the
clock.
- Unchanged: an `isAlive()` result of `false` fails the run at once. A
rejection with no Runner connection fails it as before. A transport with
no reconnect grace (local adoption) fails it as before. The close path
still uses `isAlive()` directly.
- `doc/execution-semantics.md` describes the rule.

## Verification

- Red on master: `npx vitest run
src/live/runnerd-codex-transport-adoption.test.ts -t "liveness probe
rejects while"` (in `packages/paperclip-runner/`). It failed with
`native_adopted_runner_identity_unverifiable: runner liveness could not
be revalidated`.
- Green: the test passes. The real adopted runnerd keeps its connection,
`isAlive()` rejects for 1.5 seconds (6 monitor ticks), the run still
answers `thread/read`, no signal is sent, and the runner is alive.
- `runnerd-codex-transport-adoption.test.ts` passes (24 tests),
including "retains authenticated adopted-runner ownership when a close
probe rejects". `tsc --noEmit` passes for `packages/paperclip-runner`.

## Risks

- A remote adopted Runner whose liveness cannot be verified, but which
stays connected, keeps running for up to 7 minutes before the run fails
closed. Its own authenticated connection is the evidence during that
time, and no exit is reported.
- The change touches `packages/paperclip-runner/src`, so it changes the
Daytona runner image content ID.

> 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 Opus 5.5 (`claude-opus-5-5`), Anthropic, through Claude Code
with tool use (shell, file edit, code search). Extended reasoning was
on.

## 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 Opus 5.5 <noreply@anthropic.com>
2026-11-11 17:49:13 -06:00
..
…
…
…
…