Files
PaperClipAI/maps/feature-map/recovery.md
T
83e13db8fa fix(inbox): keep automatic recovery cleanup out of personal participation (#16063)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The personal inbox uses issue activity to identify a person's
participation.
> - Issue reads can cancel stale recovery actions and record that
cleanup as the reader's action.
> - This adds unrelated tasks to the inbox and refreshes their activity
timestamps.
> - A workspace owner hold can also alternate between creation and
cancellation after a task finishes.
> - This pull request corrects cleanup attribution, excludes historical
read cleanup, and retains one exact owner hold.
> - Agents can read authorized issue activity to diagnose the same
problem themselves.

## Linked Issues or Issue Description

Refs #15742 excludes logs by the execution-recovery actor from activity
timestamps. This PR covers reader-attributed stale recovery cleanup,
personal inbox participation, exact workspace owner holds, and sandbox
issue activity access. I searched open issues and PRs for recovery inbox
and activity bridge fixes. I found no duplicate for this defect.

**What happened?**

Loading issue lists cancels stale recovery actions under the reader's
identity. The personal inbox treats those rows as user participation.
One completed issue had hundreds of such rows and no edits or comments
by the affected reader. A retained native workspace owner repeatedly
creates a verification action that read projections then cancel. Sandbox
agents cannot inspect the issue activity because the reverse bridge
rejects the route.

**Expected behavior**

Automatic cleanup belongs to the system and does not add an issue to a
reader's inbox. Historical cleanup rows remain auditable but do not
count as participation or recent activity. An unresolved physical
workspace owner keeps one stable verification action. Agents can inspect
activity only for issues their identity may read.

**Steps to reproduce**

1. Finish a task whose native run retains an unverifiable workspace
owner receipt.
2. Repeat workspace recovery checks and GET requests for issue detail,
compact lists, and recovery actions.
3. Observe new recovery action IDs and reader-attributed cancellations
in the activity history.
4. Query the personal inbox for that reader and observe the unrelated
task.
5. From a sandbox agent, request GET /api/issues/:id/activity and
observe Route not allowed.

**Paperclip version or commit**

Observed at f5346c05b9. Regression tests
use current master behavior.

**Deployment mode**

Authenticated hosted instance with native and legacy sandbox agents. The
inbox defect is independent of the adapter.

## What Changed

- Attribute automatic stale recovery cleanup to the recovery system
actor.
- Exclude exact historical read cleanup rows from personal inbox
membership, unread counts, and both activity timestamp queries. Keep all
audit rows and explicit user recovery decisions.
- Retain owner verification holds only while the exact company, run,
issue, and owner fingerprint still match. Apply the same rule to reads
and background recovery.
- Make repeated checks for the same owner hold leave its identity,
attempts, and timestamps unchanged. Keep physical takeover blocked.
- Allow GET issue activity through the sandbox bridge. Keep the existing
company and issue-read authorization and reject broader audit routes.
- Add regressions and update the runtime API and recovery documentation.
Refresh generated source references for the documentation edits. Update
the paired activity, inbox, and recovery maps.

## Verification

- Focused Vitest verification: 315 tests pass across five files. The
updated terminal owner hold regression also passes. The files cover
issue service, recovery routes, activity authorization, sandbox callback
bridge, and native workspace finalization.
- `pnpm -r typecheck` and `pnpm build` pass locally. The full general
and serialized unit test matrix passes in CI on the latest commit. The
duplicate local `pnpm test:run` was stopped after about 30 minutes once
that full CI coverage passed; it did not report a local full-suite pass.
- `node scripts/check-maps.mjs` passes for all paired areas and
capability IDs.
- Apex is 5/5 on commit 44c3787888. Its
access map finding is fixed and resolved. All CI checks are green on
this commit.
- The new regression repeats reads and background sweeps for done and
cancelled tasks. It checks one unchanged hold, zero physical copyback,
zero personal inbox participation, and system cleanup after the exact
receipt is cleared.
- Historical cleanup tests preserve explicit recovery decisions,
nullable metadata, unread counts, and the original audit rows.
- Authorization tests permit an authorized agent and reject
cross-company or issue-boundary reads. Bridge tests reject write
methods, extra path segments, and company-wide audit routes.

## Risks

Existing inbox items can disappear if their only participation evidence
was automatic read cleanup. No data migration or audit deletion is
needed. An unresolved workspace owner remains visible after task
completion; this preserves the physical writer fence. A stale or cleared
owner does not retain the hold. Issue activity access expands only the
sandbox transport allowlist and still uses existing controller
authorization.

## Model Used

OpenAI GPT-6 through Codex, with reasoning, repository inspection, code
editing, shell execution, and test tools. The session identifies the
model family as GPT-6. A more specific runtime model ID and context
window size are not exposed.

## 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
- [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>
Co-authored-by: Amy (Opus) <noreply@anthropic.com>
2026-10-12 19:25:15 -07:00

9.1 KiB

Recovery

People can see why a task stopped, who owns the next action, and whether a safe continuation is available. Recovery retains task context and ownership, honors pause and budget gates, and distinguishes confirmed failure from an uncertain external outcome. A retry acknowledgement alone is not proof that work resumed.

Agent access: companion.

Sub-features

  • diagnosis: a task/run exposes the recorded cause, evidence, owner, attempt progress, and next action without requiring a raw-log interpretation.
  • bounded-continuation: supported interrupted work continues with preserved conversation context and bounded attempts, without blindly replaying tools.
  • source-action: source-scoped recovery actions remain attached to the task; independent repair work may have its own issue and return owner.
  • exhaustion: exhausted or overdue recovery exposes the current gate and permitted operator action; it does not claim an automatic path is still live.
  • workspace: workspace divergence/export failures show the applicable repair, reconcile, or isolated reissue action with evidence and authority checks.
  • unknown-outcome: uncertain external side effects require inspection and an explicit decision, not automatic replay.
  • user-continuation: saved user messages after a stop are reconsidered once cleanup proves the old execution is stopped; duplicate wakes are avoided.
  • reconnect: browser stream recovery refreshes persisted state without claiming the agent itself restarted or that a failed run succeeded.

How to get to it (user POV)

source-task

Open the affected task from Tasks, Inbox/Blocked, search, or a direct task link. Inspect the recovery notice/card in its history, expand details if needed, and use the currently offered action. Follow any linked repair task and return owner.

run-detail

Open the agent's run (/agents/:agentId/runs/:runId) from activity or the task's run link. The run page hosts workspace recovery controls only for a failed run with error code workspace_validation_failed and a linked source task that still has a live workspace_validation recovery action. Use the source task for other stopped-run recovery checks.

operator-retry

On an exhausted disposition or workspace recovery card, inspect the current reason and use the permitted retry/repair/reconcile action. Some repairs require confirmation; unavailable actions must explain the gate or remain absent.

post-stop-message

After a stop, send a new instruction on the same task or inspect one already saved while cleanup was pending. Observe the waiting message, continuation, and eventual result rather than manually changing the task status to look complete.

browser-reconnect

While viewing an active or recovering task, interrupt the browser connection and restore it, or suspend and return to the tab. Reload is a separate check. The current task and run state should reconcile with the server.

Driving it

Preconditions: follow the baseline. Use a disposable instance and reproducible failure. Record source task/run, adapter/runtime mode, cause, attempt count, current owner, and verified stop evidence. Do not kill an unrelated server or induce provider side effects just to get a failure screen. Use the existing deterministic recovery suite first.

source-task

Automated: recovery actions covers source scoping, bounded attempts, operator stops, quota monitoring, ownership, stale action retirement, and continuation deduplication. Recovery cards cover diagnosis, retry progress, exhaustion, and action availability. The fixture-backed execution-recovery journeys start their own test-drive instances and exercise safe, uncertain, restart, manager-lineage, and legacy-unknown paths:

pnpm exec vitest run server/src/__tests__/issue-recovery-actions.test.ts ui/src/components/IssueRecoveryActionCard.test.tsx
pnpm exec playwright test -c tests/e2e/execution-recovery/playwright.config.ts recovery.spec.ts

Manual: open a stopped task through each affected navigation path. Confirm its explanation, original owner, recovery owner if different, and next action. Follow the supported recovery until the source task produces a useful result or a specific escalation. Reload and verify that resolved notices retire and attempt history remains. A fixture pass does not qualify every live harness/provider.

For a native workspace owner hold, repeat issue reads and recovery checks after the task becomes done or cancelled. Verify one unchanged action ID, attempt count, and timestamps; task completion must not authorize copyback. Clear only the exact receipt after operator stop verification, then confirm stale action cleanup. The route regressions and heartbeat regression cover this sequence without a live operator recovery claim.

run-detail

Automated: run workspace recovery tests this host; it is component coverage, not an end-to-end repair.

Manual: prepare a failed run with error code workspace_validation_failed, a linked source task, and a live workspace_validation recovery action on that task. Open that exact run and compare its recovery diagnosis to the source task. Use an eligible action from the run surface, return to the task, and verify the same action state, persisted outcome, and restored execution. Repeat with an actor who lacks authority. Verify that the run-page controls are absent for other error codes or after the source action retires; continue other recovery checks on the source task. Full run-detail navigation remains a manual gap.

operator-retry

Automated: disposition notices tests pending/error/gated states and waiting for the real retry acknowledgement. The recovery-card suite covers workspace divergence, reissue, reconcile, and confirmation. Workspace export covers its separate repair surface.

Manual: reproduce an exhausted disposable action. Click once, verify pending state prevents repeated clicks, and inspect a successful continuation or an explicit rejection. Reload to confirm the durable outcome. For a workspace repair, record the compared branches/revisions and preserve uncommitted work; confirm the repair's actual result before resuming. Check the denied and stale action paths. A changed card label is insufficient proof of workspace integrity.

post-stop-message

Automated: legacy continuation, queue routes, and native restart recovery cover their respective recovery paths. Legacy failure continuation adds a browser journey; it is not native runtime proof.

Manual: stop a disposable run, submit a distinguishable follow-up, and observe any cleanup wait. After confirmed stop, verify the follow-up reaches one eligible continuation and produces the requested answer. Repeat with a task/ancestor pause and a budget hold: recovery must not bypass the gate. For unknown external outcomes, inspect the provider before authorizing another operation.

browser-reconnect

Automated: live-update recovery covers client transport recovery with mocks. It does not prove runner restart.

Manual: go offline during a disposable run, let the server reach a new state, then reconnect. Confirm task/run/attention data refresh without duplicate comments or decisions; reload and compare. A healthy websocket proves transport only. Separately inspect the underlying execution if it is still stuck.

Gotchas

  • Legacy and native runtimes have different ownership and restart paths. Record which one was exercised instead of inferring coverage from the adapter name.
  • A process disappearance, elapsed timeout, or successful API retry does not prove an external tool operation never happened. Unknown outcomes stay unknown until corroborated; automatic replay can duplicate a side effect.
  • Active-run watchdogs, task watchdogs, reviewers, and recovery owners have different authority. No recovery card grants board permission by itself.
  • Idle Agent Chat is not stranded work. An intentional operator stop must not silently trigger recovery that fights the user's instruction.
  • Source inspection and mocked component tests do not qualify real server restart continuity. Use the appropriate runtime suite for that claim.
  • See execution semantics for the governing recovery contract and steering for saved follow-ups.