Files
PaperClipAI/server
DottaandClaude 52b906b112 fix(native): let Retry continue after a run that failed before native selection (#20482)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - Before Paperclip starts a new message or a Retry after a failed
native run, it proves that the run's local process stopped
> - A native run can fail before native selection, for example when a
budget reservation is refused or a preparation query fails. That run
never had a runner, a native session or a process ID
> - The stop gate still waited for a stop record of that process. Retry
returned 202, then the task stayed on
`retryWait.reason=process_identity_missing` and no run started
> - Native selection writes the run's native issue, native session,
runner instance and coordinator in one transaction. A runner launch
needs all of them, and the executor records
`native.process_start_requested` before any spawn
> - This pull request treats a settled run with none of that evidence as
having nothing to stop
> - The benefit is that Retry and new messages continue after a failure
that happened before the agent started

## Linked Issues or Issue Description

**What happened?**
In local restart-chaos database-load wave `w-20261110T050818-s20386`,
all 4 tasks failed before any agent process started. One example is t01,
run `f7e2ee55`. A preparation query failed, and the run ended
`adapter_failed` with no native session, no runner instance, no process
ID and no launch event. Retry returned 202. For 16 or more minutes,
`retryWait` then stayed on `process_identity_missing` and no run
started.

**Expected behavior**
If the previous run never had a process, there is nothing to verify, so
Retry and new input continue.

**Steps to reproduce**
1. Make a native run fail before native selection (for example, a
refused budget reservation or a failed query).
2. Click Retry, or send a message. Before this change, the input waits
on `process_identity_missing` and does not continue.

## What Changed

- `native-local-process-stop.ts`: `nativeRunRefusedBeforeLaunch`
(renamed `nativeRunNeverLaunched`) now also proves "never launched" for
a settled native run in this state:
  - no process ID and no process group;
- no native issue, no native session and no runner instance (native
selection writes them together). A coordinator row requires the native
issue by foreign key, so the native-issue check also rules out a
coordinator;
- no launch request, no identity record, no `adapter.invoke` and no
runner-authored event (any event with a source event ID).
The existing case, a run that the cleanup quarantine refused before
launch, does not change.
- `explicit-native-continuation.test.ts`: the guard "failed for another
reason without a process record" now separates real pre-selection
failures from the shapes that must stay held.
- Admitted: a run that failed before it requested a launch. This case is
red on `master`.
- Still held on `process_identity_missing`: a run that has a runner
event, a run with a prepared native session, and a run with a
coordinator, for `failed`, `timed_out` and `interrupted` runs. A
`timed_out` run that failed before launch is admitted. The chat suite's
`unreconciled` case (a native issue and phase, no session) also stays
held.

## Verification

- Red/green: on `master`, `routes input after a run failed before it
requested a launch` fails with `process_identity_missing`. It passes
with this change.
- `vitest run src/services/explicit-native-continuation.test.ts
src/services/quarantined-task-retry.test.ts
src/__tests__/issue-confirm-run-stopped-routes.test.ts`: 3 files, 462
tests pass. This includes every existing "keeps the hold for
missing_process" and historical-run case.
- Server `tsc --noEmit` passes.

## Risks

- Low risk. The new proof needs structural evidence that no runner could
exist: no native selection and no launch or runner event. A run that
reached native selection, or that has any launch evidence, keeps every
existing stop proof.
- The proof does not depend on the build: a runner from any build needs
the native session and the runner instance. Saved runs from older builds
that were selected keep those fields, so they stay held exactly as
before.
- Relates to #20477, which fixes the restart window inside an answer
run's attachment. This pull request is independent of it.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), via Claude Code with tool use
(shell, file edits, test execution).

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

Refs #19829

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-11-10 02:20:31 -06:00
..