Files
PaperClipAI/server/src/__tests__
DottaandPaperclip d20cb69ce5 fix(heartbeat): preserve active retries when comments arrive (#16855)
## Thinking Path

> - Paperclip manages agents and their work.
> - Heartbeat retries preserve task ownership after a recoverable
failure.
> - The issue read model includes retries that are scheduled, queued, or
running.
> - The human comment route treated every retry in that model as parked
work.
> - A normal status comment could therefore cancel a running
continuation and return its task to Todo.
> - This pull request limits comment supersession to parked retries and
checks that state at the cancellation fence.
> - The agent can keep working when a person adds context. Explicit Stop
and Interrupt remain available.

## Linked Issues or Issue Description

**What happened?**

A Board comment on an in-progress task cancelled its running recovery
continuation. The handler read a live retry from
`getCurrentScheduledRetry` but did not check its status. The
cancellation also forced workspace recovery when copy-back did not
complete.

**Expected behavior**

A comment must preserve queued and running execution. Only a retry that
is still `scheduled_retry` may be superseded. If promotion wins the
race, the comment must keep the task in progress and follow the normal
wake path.

**Steps to reproduce**

1. Let a task retry advance from `scheduled_retry` to `running`.
2. Keep its retry reason and source run identity in the stored row.
3. Post a normal human comment, without an Interrupt request.
4. Observe the old handler cancel the run and move the task to Todo.

**Paperclip version or commit**

Reproduced on master base `9966f31bee`. The staging incident served
`f3cd3f4ea89539b68410841d39ec15a5c626007f`.

**Deployment mode**

Staging server with a remote runner. The defect is in the core comment
and cancellation path.

Refs #16835 and #16843. Those repairs cover runner health and workspace
preservation. This change prevents a normal comment from requesting the
wrong cancellation. Related #9732 promotes human wakes on parked
retries; it does not protect a retry that is already running.

## What Changed

- Check the live retry status in both comment routes: POST comments and
PATCH issue with a comment.
- Add an optional expected-status fence to cancellation. A concurrent
promotion or terminal outcome returns a no-op result.
- Make due-retry promotion honor a cancellation claim under its issue
and run row locks. A scheduler cannot promote work after cancellation
has won.
- Finish persisted cancellations of parked retries through the existing
startup and periodic recovery sweep. Preserve their original request and
retry a failed settlement without starting provider work.
- Keep the task active when guarded cancellation does not win. Do not
record a cancellation that did not occur.
- Add route regressions, real PostgreSQL race tests, and pending-retry
and explicit Stop safety coverage.
- Exercise both normal HTTP comment routes with real services, stored
Board membership, PostgreSQL, and a live child process. Assert that the
process can still perform work, the task keeps its original execution,
and no second executor starts.
- Document the comment supersession contract and update the task
feature/access maps with the local regression coverage and deployed
acceptance recipe.

## Verification

- Before the fix, all four POST/PATCH regressions cancelled queued or
running work.
- Before the fix, the real PostgreSQL race test cancelled a retry that
became running after lookup.
- The comment-route suite passes **103 tests**. The final dispatch,
cancellation, restart recovery and HTTP/live-process suites pass **119
tests**, for **222 affected tests** across these runs. They cover
comment delivery, both sides of the promotion race, concurrent terminal
outcomes, pending retries, explicit Stop, and a live process that can
continue work.
- Command: `node node_modules/vitest/vitest.mjs run
server/src/__tests__/issue-comment-reopen-routes.test.ts
server/src/services/heartbeat/run-control.test.ts`.
- The normal HTTP/live-process acceptance suite passes all **14 tests**,
including both comment endpoints with stored Board membership and
deterministic wakeup teardown.
- Review exposed a second race after the cancellation claim. Its new
PostgreSQL test failed before the dispatch guard with `promoted` and
passes afterward with `not_promoted`, followed by completed
cancellation.
- A real PostgreSQL constraint interrupts the terminal cancellation
write after its intent commits. The assembled recovery service failed to
settle that row before the repair. Afterward a fresh service settles it,
clears its task lock, cancels its saved wake, preserves the original
request and leaves unrelated parked retries alone. A repeated database
failure preserves the intent for a later successful sweep; a second
successful sweep adds no duplicate event.
- Full `pnpm -r typecheck` and `pnpm build` pass. Server typecheck and
build pass again after the final server-only review fix. A local
full-suite attempt started on `f8773bf` did not return an overall
result; its log is preserved and no local full-suite pass is claimed.
The new recovery change passes the focused suites and fresh server
typecheck/build. Latest-head CI on
`8f928adee67c089e1ac405d2ad3b169eedb8da27` is green: the full PR
workflow and commitperclip review passed; only the two optional
Storybook jobs skipped.
- Greptile reviewed final head
`8f928adee67c089e1ac405d2ad3b169eedb8da27` at **5/5**, with no
unresolved findings. All actual latest-head CI checks passed; the two
optional Storybook jobs skipped.
- `node scripts/check-maps.mjs` passes after the map updates (34 areas,
172 capability IDs); it checks structure only.
- Fixed-revision staging acceptance is pending deployment. The original
workspace remains held and its latest source checkpoint is backed up and
verified.

## Risks

- A promoted retry must keep its execution and ownership when a comment
arrives. The database status predicate and route no-op handling enforce
this at the race boundary.
- A cancellation intent must prevent promotion without leaving work
parked forever after a restart. The recovery sweep consumes at most 20
such intents per pass through guarded cancellation, and a failed
settlement keeps its intent for retry.
- A parked retry must still accept fresh instructions. Its existing
cancellation and wake behavior remains covered.
- Ordinary Stop and Interrupt calls omit the expected-status option.
Their cancellation authority is unchanged and remains covered by the
run-control suite.
- No schema migration or API request contract changes are required.

## Model Used

OpenAI `gpt-6.1-sol` in Codex, with extra-high reasoning, tool use, and
code execution. The session does not expose its context-window limit.

## 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 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
- [x] All Paperclip CI gates are green
- [x] 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 11:34:27 -05:00
..
…

Server Tests

Server tests that need a real PostgreSQL process must use ./helpers/embedded-postgres.ts instead of constructing embedded-postgres directly.

The shared helper creates a throwaway data directory and a reserved-safe loopback port for each test database. This protects the live Paperclip control-plane Postgres from server vitest runs; see PAP-2033 for the incident that introduced this guard.