feat(monitor): one-click re-dispatch for failed / cancelled tasks #226
No reviewers
Labels
No labels
area:agents
area:dashboard
area:database
area:design
area:design-review
area:flows
area:infra
area:meta
area:security
area:sessions
area:webhook
area:workdir
security
type:bug
type:chore
type:meta
type:user-story
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
charles/claude-hooks!226
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "dev/222"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
POST /task/:id/redispatchendpoint (auth-gated, M18-8) that reads the originaltask_historyrow, checks the Forgejo issue is still open, resolves the agent type, and enqueues a fresh task through the same pool-scheduler path asissues.assignedsessions.jsonstill holds a session for<type>:<repo>:<issue>, the new task picks it up↻ Re-dispatchbutton in the task detail pane header (failure/cancelled states) and in the task list rows (existing button wired to the real API)202queued ·404unknown id ·409still running or already succeeded ·410issue is closed ·503no agent/tokenChanges
forgejo-api.ts: addstatefield toIssueSummaryfor closed-issue guardtask-store.ts: addgetTaskById()— single-row lookup from SQLite by task idwebhook-handlers.ts: exportdispatchIssueForAgentso the re-dispatch handler can reuse the template + pool-dispatch path without duplicationmain.ts:handleTaskRedispatch+POST /task/:id/redispatchrouteapps/web/src/lib/api.ts:postRedispatch()fetch helperapps/web/src/components/task-detail.tsx:↻ Re-dispatchbutton in top-right actions barapps/web/src/routes/monitor.tasks.tsx: wireonRedispatchto callpostRedispatchwith toastTest plan
apps/server/src/main.test.ts— 404 (unknown id), 409 (running), 409 (succeeded), 503 (no token for failed row) — all pass (32 pass)apps/web/e2e/monitor.spec.ts— mock failed row, click re-dispatch button, assert POST fires[redispatch] <old> → <new>Closes #222
🤖 Generated with Claude Code
88f25cb9306fe9e6a5e9CI still pending at review time (run #1871, sha
6fe9e6a). Stepping off the review request — will be re-dispatched automatically when CI completes.Review: APPROVED ✅
CI green (run #1871,
6fe9e6a, 4m6s). Full diff reviewed against issue #222 acceptance criteria.What's correct
Server endpoint (
main.ts—handleTaskRedispatch):guardMutating(M18-8) ✓currentTaskand the worker queue — covers all live states ✓getTaskByIdfor the authoritative record (not the capped 50-entry in-memory history) ✓successstatus correctly blocks re-dispatch ✓dispatchIssueForAgent— reuses the same pool-scheduler path asissues.assigned, session resume included ✓type: "redispatch") ✓[redispatch] <old> → <new>matches the operator verification step in the test plan ✓task-store.ts—getTaskById: Clean single-row query;PersistedTaskRowshape contains exactly what the handler needs. ✓forgejo-api.ts: Addingstate?toIssueSummaryis the minimal, non-breaking change needed for the closed-issue guard. ✓webhook-handlers.ts: Only change is exportingdispatchIssueForAgent— correct, avoids duplication. ✓apps/web/src/lib/api.ts—postRedispatch: UsesencodeURIComponenton the task id ✓. Error-handling matches the rest of the file. ✓task-detail.tsx: UsesuseMutationproperly; invalidates["history"]on success; pending state disables the button. ✓main.test.ts: 404 (unknown id), 409 (running), 409 (succeeded), 503 (no token) — four paths explicitly exercised. Unit-testing the 202 path requires a live Forgejo/config, which the test environment doesn't have; the test plan correctly routes that to manual verification. ✓Minor observations (non-blocking)
Toast doesn't link to the new task — issue #222 AC says "a link to the new task detail". The current toast shows
↻ Re-dispatched → task ${short_id}with no clickable link. Given that the selected-task state lives in Zustand (not the URL), a deep-link would require navigating + mutating store state — non-trivial. The history list does refresh immediately viainvalidateQueries, so the new task appears at the top of the list anyway. Suggest tracking this as a follow-up UX improvement rather than blocking the merge.interruptednot in the UI button condition — the issue spec mentionsstatus ∈ {failed, cancelled, interrupted}for the button, butinterrupteddoesn't exist inPersistTaskInput.statusorSTATUS_LABEL. Not a real gap.monitor.tasks.tsxre-dispatch uses rawthen/catch(notuseMutation) — means the task-list row button has no pending/loading state while the call is in flight.task-detail.tsxdoes it correctly withuseMutation; the list view is a minor inconsistency but not a correctness issue.All acceptance criteria that can be verified without a live Forgejo instance are met. Core safety rails (409 × 2, 410, 404, 503) and the dispatch path are correct.