feat(dashboard): cancel queued tasks from UI + /cancel drops queue entries (#302) #306

Merged
code-lead merged 1 commit from feat/302-cancel-queued-tasks into main 2026-04-24 08:47:33 +00:00
Collaborator

Closes #302.

Summary

  • Worker.dropQueuedById(taskId) — removes a queued entry by id, returns it for the caller to persist + broadcast (or null if unknown / already running).
  • /cancel extended: when task_id doesn't match any running worker, scan every worker's queue and drop the first match. Returns { status: "dropped-from-queue", task_id, agent, issue_number }. The 404 message updates to "no running or queued task with id".
  • task_cancelled SSE envelope emitted on both paths (reason: "operator-aborted" for running, "operator-dropped" for queued) so the dashboard can pop the row without a full refetch.
  • task-detail.tsx — Cancel button now renders for status === "queued" too, with differentiated copy ("✕ Drop from queue" / "Confirm drop" / "Dropping…") and a matching toast.

Design notes

  • dropQueuedById deliberately does not resolve the pending promise registered by enqueue. TaskResult.status is "success" | "failure" | "cost_capped" — no "cancelled" variant — so a synthetic resolve would either mislabel the outcome or require widening the union across every consumer. The existing assign-dedup path in webhook-handlers.ts:206 uses the same splice-without-resolve shape. TaskRecord.status is the operator-visible source of truth and the handler flips it to "cancelled" + persists.
  • Running-first lookup precedence is locked in by a test so a future refactor can't silently prefer queued-drops over live aborts.
  • The dropQueuedById primitive is intentionally narrow so #301 (webhook issues.unassigned) can land against the same method without a second surface.

Test plan

  • worker.test.tsdropQueuedById removes the right entry, returns null on unknown id, returns null when the task has already promoted to currentTask.
  • main.test.ts — new /cancel scenarios: queued drop + status flip, 404 message update, running-first precedence when a task_id could match both.
  • task-detail.test.tsx — existing tests still pass (no test-level regressions from the widened gate).
  • bun x biome check clean.
  • Manual: queue a task in dev, POST /cancel with its id, confirm dashboard row disappears via the SSE event.

Out of scope (tracked elsewhere)

  • Webhook issues.unassigned handler — #301, lands against the same dropQueuedById primitive.
  • Bulk drop / cancel-all-queued — explicit Out of scope on #302, follow-up if needed.

🤖 Generated with Claude Code

Closes #302. ## Summary - `Worker.dropQueuedById(taskId)` — removes a queued entry by id, returns it for the caller to persist + broadcast (or `null` if unknown / already running). - `/cancel` extended: when `task_id` doesn't match any running worker, scan every worker's queue and drop the first match. Returns `{ status: "dropped-from-queue", task_id, agent, issue_number }`. The 404 message updates to "no running or queued task with id". - `task_cancelled` SSE envelope emitted on both paths (`reason: "operator-aborted"` for running, `"operator-dropped"` for queued) so the dashboard can pop the row without a full refetch. - `task-detail.tsx` — Cancel button now renders for `status === "queued"` too, with differentiated copy ("✕ Drop from queue" / "Confirm drop" / "Dropping…") and a matching toast. ## Design notes - `dropQueuedById` deliberately does **not** resolve the pending promise registered by `enqueue`. `TaskResult.status` is `"success" | "failure" | "cost_capped"` — no `"cancelled"` variant — so a synthetic resolve would either mislabel the outcome or require widening the union across every consumer. The existing `assign-dedup` path in `webhook-handlers.ts:206` uses the same splice-without-resolve shape. `TaskRecord.status` is the operator-visible source of truth and the handler flips it to `"cancelled"` + persists. - Running-first lookup precedence is locked in by a test so a future refactor can't silently prefer queued-drops over live aborts. - The `dropQueuedById` primitive is intentionally narrow so #301 (webhook `issues.unassigned`) can land against the same method without a second surface. ## Test plan - [x] `worker.test.ts` — `dropQueuedById` removes the right entry, returns `null` on unknown id, returns `null` when the task has already promoted to `currentTask`. - [x] `main.test.ts` — new `/cancel` scenarios: queued drop + status flip, 404 message update, running-first precedence when a `task_id` could match both. - [x] `task-detail.test.tsx` — existing tests still pass (no test-level regressions from the widened gate). - [x] `bun x biome check` clean. - [ ] Manual: queue a task in dev, POST `/cancel` with its id, confirm dashboard row disappears via the SSE event. ## Out of scope (tracked elsewhere) - Webhook `issues.unassigned` handler — #301, lands against the same `dropQueuedById` primitive. - Bulk drop / cancel-all-queued — explicit Out of scope on #302, follow-up if needed. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(dashboard): cancel queued tasks from UI + /cancel drops queue entries (#302)
All checks were successful
qa / qa (pull_request) Successful in 4m16s
qa / dockerfile (pull_request) Successful in 8s
20dea65f02
Extend /cancel to accept a task_id that points at a queued entry — scans
each worker's `queue[]`, removes the match via a new
`Worker.dropQueuedById`, flips the TaskRecord to cancelled, persists, and
broadcasts a `task_cancelled` SSE envelope (also emitted on the existing
running-abort path for symmetry). UI: Cancel button gated on
`status === "running" || status === "queued"` with copy differentiated
("Drop from queue" / "Confirm drop" vs. Cancel) and a toast that mirrors
the backend `dropped-from-queue` status.

Shares the `dropQueuedById` primitive with the planned
`issues.unassigned` webhook handler (#301) — both land against one
worker method so the queue-drop shape stays narrow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
code-lead deleted branch feat/302-cancel-queued-tasks 2026-04-24 08:47:34 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
charles/claude-hooks!306
No description provided.