mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-14 08:53:19 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Tasks that come from a connected chat (for example GitHub pull request reviews) show a Retry button when a run fails > - The task projection offers Retry, but the Retry route sends a connected task to the chat retry owner, which only replays the exact failed chat request > - When that exact request cannot be replayed (the attempt already saved a result, its output was published, the latest run did not start from a chat message, or the requester's access changed), the route returns 409 and tells the person to check the connection by hand > - A Retry button that is shown must work, and recovery must not ask the person to inspect anything > - This pull request makes a person's Retry continue the task from its saved work when the exact chat replay is refused, the same way a new message on the task would > - The benefit is that the projection and the route agree, and blocked connected tasks become retryable after deploy with no migration ## Linked Issues or Issue Description No public issue exists. This is the bug description, in the fields of `.github/ISSUE_TEMPLATE/bug_report.yml`. **What happened?** On a hosted deploy, five blocked tasks that came from a connected GitHub conversation showed Retry (`interruption.canRetry: true`). Each click on Retry returned 409 `chat_failed_run_retry_not_authorized`. The message was "The exact failed chat request can no longer be retried safely. Check its current connection, access, and recovery state." or "This attempt already recorded a result. Send a new message on the task to continue from the saved work." The latest runs were legacy sandbox setup failures, native runs that needed reconciliation, a run that started from an issue monitor instead of a chat message, and a native run that already saved its result. Root cause: `admitTaskRetry` in `server/src/services/task-retry.ts` called the chat retry owner for every connected task and passed its exact-replay refusal straight to the person. The projection cannot predict that refusal. **Expected behavior** A shown Retry works. If the exact chat request cannot be repeated, Retry continues the task from its saved work, the same way a new message on the task would. Users do not get operator instructions. **Steps to reproduce** 1. Bind a task to a restricted chat conversation. 2. Make its latest run fail so that the chat retry owner refuses the exact replay. For example, use a run whose source is not a chat message, or a native run with an accepted result. 3. Click Retry (`POST /api/issues/:id/retry`). 4. The route returns 409 `chat_failed_run_retry_not_authorized`. **Paperclip version or commit** `master` at5a7378af5(the deployed revision), reproduced on51d77648bc. **Deployment mode** Hosted (cloud) instance with Daytona remote sandboxes and a GitHub chat connection. **Agent adapter(s) involved** Not adapter-specific (core bug). Seen with `paperclip_runner` legacy and native runs. ## What Changed - `server/src/services/task-retry.ts`: a person's Retry runs the exact chat replay inside a savepoint. On a `chat_failed_run_retry_not_authorized` refusal, the savepoint rolls back and the same Retry receipt stays an ordinary board continuation. The clicking person is the responsible user, the run starts a fresh session (`forceFreshSession`) from saved work, and no chat action, chat authority, or output replay is created. Retry that Paperclip requests by itself (`initiatedBy: restart_recovery | provider_limit_reset`) keeps the exact-replay requirement. Other errors (403, lock contention, database errors) still propagate. - `server/src/services/chat-channels/failed-run-retry.ts`: plain refusal wording. Runs saved by an earlier release with the old diagnostic are still recognized by the pre-provider check. - Tests: new red/green cases in `server/src/services/task-retry.test.ts` (four refusal reasons on a chat task whose latest run came from an issue monitor, plus automatic Retry still refused). Updated two real-service route cases in `server/src/__tests__/chat-channels.integration.test.ts` (accepted native result on an inherited GitHub task, and an unknown chat source) from expecting 409 to expecting a board continuation without a chat replay. - A person's Retry on that person's own saved board turn (`source: issue.comment.reopen`) that cannot be certified for replay also continues from saved work, without replaying the saved message. Automatic Retry still gets 409 `saved_board_message_not_replayable`. - A person's Retry when no chat retry owner is wired also continues from saved work. Automatic Retry still gets 409. - The fallback is not used while the conversation holds saved or unadmitted chat input for the task. That input keeps its own admission path and the existing refusal. - Docs: `doc/execution-semantics.md` and `doc/connections/GITHUB.md`. ### 409 paths in the task Retry route after this change | Path | Who sees it | Result | | --- | --- | --- | | `chat_failed_run_retry_not_authorized` from the chat retry owner | Person | Continues from saved work (board continuation) | | `saved_board_message_not_replayable` | Person | Continues from saved work | | No chat retry owner wired | Person | Continues from saved work | | Any of the three above while saved or unadmitted chat input is pending | Person | 409. The pending input continues through its own admission and delivery. | | Any of the three above | Automatic Retry (restart recovery, provider limit reset) | 409, recorded by the hook. No button. | | `task_retry_busy` (chat authorization lock held) | Person | 409 with `retryable: true` after 10 internal attempts. A new click works. | | Idempotency key reused for a different task or user | Client error | 409. The UI makes a new key for each click. | ## Verification - Red before the fix: `npx vitest run src/services/task-retry.test.ts -t "connected task"` failed 4/4 with `Error: refused` (409). The board-turn and missing-owner cases failed with "This saved message cannot be retried safely..." and "Retry is unavailable for this connected conversation." `src/__tests__/chat-channels.integration.test.ts -t "continues an inherited GitHub task"` failed with `expected 202 "Accepted", got 409 "Conflict"`. - Green after the fix: `src/services/task-retry.test.ts`, `src/__tests__/durable-chat-wakeup.test.ts`, `src/services/budget-resume-continuation.test.ts`: 160/160 passed. - Full `src/__tests__/chat-channels.integration.test.ts`: 1949/1951 passed. The 2 local failures (`binds Slack tools...`, `materializes direct external-chat finals...`) read `.claude/skills/slack/SKILL.md` from the parent checkout of a nested local worktree (ENOENT). They do not touch Retry and depend on the local checkout layout. - `npx tsc --noEmit -p server/tsconfig.json`: passed. - `pnpm verify` exited 2 (a doc file is a global CI input), so required CI supplies broad coverage. - Staging was inspected with GET requests only. No POST was sent to staging. ## Risks - Behavior change: a person's Retry on a connected task can now start a board continuation where it was refused before. It uses the same authority as a new message from that person, and dispatch keeps all normal gates (task access, executor containment, recovery holds, unadmitted chat input). - The continuation does not publish the earlier result again and does not reuse the chat requester's authority. - No migration. Existing blocked tasks become retryable at the next click. > 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, with tool use and code execution in Claude Code. ## 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>