feat(cancel): target specific agent or task_id #91
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!91
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "boss/87"
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
Makes
POST /canceltargetable so operators can stop one specific task in a multi-worker run instead of whichever worker happens to be first in the map.POST /cancelnow accepts:{}— cancel the sole busy worker;409 { "error": "multiple workers busy; specify agent or task_id" }when more than one is busy.{ "agent": "<name>" }— cancel that worker's current task.404if the agent name isn't known.{ "task_id": "<uuid>" }— cancel the specific running task wherever it lives.404if the id isn't currently running. Preferred overagentwhen both are given.Response carries
task_id,agent, andissue_numberso the operator can confirm the right target was hit.Dashboard:
{ task_id: "<id>" }so it behaves correctly under concurrent load.cancelbutton that POSTs{ agent: "<name>" }(behind aconfirm()prompt).Closes #87.
Test plan
bun test— 254 pass, 0 fail (newPOST /cancelsuite covers empty body / agent / task_id / 404 / 409 / idle-agent / precedence).bun x biome check src/— clean.bun x tsc --noEmit— clean.curl -X POST http://192.168.1.164:4500/cancel -d '{"agent":"dev"}' -H 'Content-Type: application/json'— onlydevaborts, response echoes the cancelledtask_idandissue_number.busy · N queuedwith a cancel button; clicking it fires{ agent }after confirm.`POST /cancel` now accepts: - `{}` — cancel the sole busy worker; 409 when >1 busy - `{ "agent": "<name>" }` — cancel that worker's task (404 if agent unknown) - `{ "task_id": "<uuid>" }` — cancel by id wherever it lives (404 if not running) Response carries `task_id`, `agent`, and `issue_number` so the operator can confirm the right target. Dashboard cancel button now POSTs the task_id; new workers bar exposes a per-agent cancel affordance. Closes #87. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>Review: feat(cancel): target specific agent or task_id
CI: green (run #1612,
success, 2m46s on head7713ebe)All acceptance criteria from #87 are met.
Endpoint shape
{}with sole busy worker cancelled correctly{}with >1 busy workers returns 409multiple workers busy; specify agent or task_id{ agent }targets that worker; 404 if unknown agent name; 200nothing runningif known but idle{ task_id }finds the owning worker regardless of which agent it is; 404 if not runningtask_idwins overagentwhen both are providedtask_id,agent,issue_numberDashboard
cancelTask(e, taskId)now POSTs{ task_id }correctly targeted under concurrent load/healthevery 5s, renders per-agent busy state with a cancel button that callscancelAgent(name)behindconfirm()and POSTs{ agent: name }Tests
254 pass, 0 fail. All 9 specified cases covered: empty body / sole busy / >1 busy 409 / cancel by agent / cancel by task_id / precedence (task_id wins) / 404 unknown agent / 404 unknown task_id / known-but-idle 200.
Code correctness
The TOCTOU window between finding
targetand calling.abort()is handled correctly:currentTaskandcurrentAbortare snapshotted before abort, then a defensive guard fires in case the worker already cleaned up. In a single-threaded event loop this window is zero within the same sync block, but the guard is still good practice.Minor observation (non-blocking)
cancelTask()andcancelAgent()in the dashboard are fire-and-forget with no error handling and no immediate UI refresh. Not a regression from pre-#87 behaviour; SSE events update task state in real time and the Workers card polls every 5s.Approved. Clean implementation, solid test coverage, CI green.
✅ APPROVED
CI green (run #1612, 2m 46s). All acceptance criteria from #87 are met.
Acceptance criteria — all checked
{}cancels sole busy worker{}→ 409 when >1 busy{"agent"}cancels named worker, 404 if unknown{"task_id"}cancels by id, 404 if not runningtask_idwins when both giventask_id,agent,issue_number{task_id}Code quality notes
Race-condition guard (
src/main.ts, snapshot block beforecurrentAbort.abort()) — the snapshot-before-abort pattern is exactly right. There is a window between computingbusyWorkersand snapshottingcurrentTask/currentAbortwhere the worker's finally block could race and null those fields. The second guard (if (!currentTask || !currentAbort)) closes it cleanly.pushEventmoved outside theif (record)guard — behavioural change is safe.pushEventalready handles a missing record gracefully (just broadcasts SSE, skips the push intorecord.events). The broadcast still notifies connected dashboard clients even in the edge case where the cancelled task has no history record.Workers card polling at 5 s — appropriate for a live-ops panel.
/healthis O(workers) so the cost is negligible.Test isolation via
afterEach— directly mutatingworker.currentTask/worker.currentAbortis the right approach; it exercises the routing logic without pulling in the Agent SDK. TheafterEachcleanup is thorough.