feat(board): force-kick stuck cards from the UI (#609) #614

Merged
code-lead merged 1 commit from boss/609 into main 2026-04-30 22:18:29 +00:00
Collaborator

Summary

  • Adds POST /board/kick — operator escape hatch that bypasses the /task/:id/redispatch 409 gate when the dispatch chain wedges on an idle card whose previous task already succeeded.
  • Resolves the agent type from the latest task_history row, falling back to the current Forgejo assignee, and dispatches through dispatchIssueForAgent so the implement → address-review pivot fires automatically when the linked PR has outstanding changes_requested at its head SHA.
  • UI: always-visible "Kick" button (tone="error", Zap icon) on the board side panel with a confirm dialog and success toast; invalidates board + history queries on success.

Why

Closes #609. The triggering case: a changes_requested review didn't fire the address-review dispatch, so the card sat idle with success as its last task status. /task/:id/redispatch refused with 409 ("task already succeeded") and Cancel wasn't visible (no running task). No way out from the UI without a backend SQL poke.

Implementation notes

  • Rate limit: 3 kicks / 60 s / issue.
  • Audit comment names the actual skill (implement or address-review) and agent type so the Forgejo trail records every kick — the handler probes for outstanding REQUEST_CHANGES separately so the comment stays honest about what dispatchIssueForAgent will do internally.
  • Pluggable KickDeps mirror the existing RestageDeps pattern so unit tests run with stubbed Forgejo + dispatcher.

Test plan

  • apps/server unit tests (12 new): happy path (implement), address-review pivot, assignee fallback, @operator actor fallback, 400 / 404 / 410 / 503 error paths, 3-per-60s rate limit.
  • apps/web smoke tests (2 new): confirm dialog gates the click; cancel button leaves kickCard untouched; success toast surfaces the abbreviated task id.
  • bun x turbo run typecheck — green.
  • bun x @biomejs/biome@^2 check . — only pre-existing unrelated infos.
  • bun x turbo run test — 2661 server tests + 44 web tests pass.

🤖 Generated with Claude Code

## Summary - Adds `POST /board/kick` — operator escape hatch that bypasses the `/task/:id/redispatch` 409 gate when the dispatch chain wedges on an idle card whose previous task already succeeded. - Resolves the agent type from the latest `task_history` row, falling back to the current Forgejo assignee, and dispatches through `dispatchIssueForAgent` so the `implement → address-review` pivot fires automatically when the linked PR has outstanding `changes_requested` at its head SHA. - UI: always-visible "Kick" button (`tone="error"`, `Zap` icon) on the board side panel with a confirm dialog and success toast; invalidates `board` + `history` queries on success. ## Why Closes #609. The triggering case: a `changes_requested` review didn't fire the address-review dispatch, so the card sat idle with `success` as its last task status. `/task/:id/redispatch` refused with 409 ("task already succeeded") and `Cancel` wasn't visible (no running task). No way out from the UI without a backend SQL poke. ## Implementation notes - Rate limit: 3 kicks / 60 s / issue. - Audit comment names the actual skill (`implement` or `address-review`) and agent type so the Forgejo trail records every kick — the handler probes for outstanding REQUEST_CHANGES separately so the comment stays honest about what `dispatchIssueForAgent` will do internally. - Pluggable `KickDeps` mirror the existing `RestageDeps` pattern so unit tests run with stubbed Forgejo + dispatcher. ## Test plan - [x] `apps/server` unit tests (12 new): happy path (implement), `address-review` pivot, assignee fallback, `@operator` actor fallback, 400 / 404 / 410 / 503 error paths, 3-per-60s rate limit. - [x] `apps/web` smoke tests (2 new): confirm dialog gates the click; cancel button leaves `kickCard` untouched; success toast surfaces the abbreviated task id. - [x] `bun x turbo run typecheck` — green. - [x] `bun x @biomejs/biome@^2 check .` — only pre-existing unrelated infos. - [x] `bun x turbo run test` — 2661 server tests + 44 web tests pass. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(board): force-kick stuck cards from the UI (#609)
All checks were successful
qa / dockerfile (pull_request) Successful in 6s
qa / qa (pull_request) Successful in 1m59s
6ce709fbbb
Adds POST /board/kick + always-visible "Kick" button on the side panel
as the operator's escape hatch when the dispatch chain wedges on an
idle card whose previous task already succeeded (so /task/:id/redispatch
refuses with 409) and whose Cancel affordance is no longer present.

The handler resolves the agent type from the latest task_history row,
falls back to the current Forgejo assignee, and dispatches through the
canonical dispatchIssueForAgent path so the implement → address-review
pivot fires automatically when the linked PR has outstanding
changes_requested at its current head SHA. Posts an audit comment that
names the actual skill + agent type so the Forgejo trail records every
kick.

Closes #609

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
reviewer approved these changes 2026-04-30 22:17:22 +00:00
reviewer left a comment

Dispatch logic, skill pivot, rate-limiting, audit comment, and UI all correct. AC fully met.

Nit (non-blocking): resolvedFrom could be typed "task_history" | "assignee" rather than ... | null at the console.log — null is unreachable there. No action needed.

Dispatch logic, skill pivot, rate-limiting, audit comment, and UI all correct. AC fully met. Nit (non-blocking): `resolvedFrom` could be typed `"task_history" | "assignee"` rather than `... | null` at the console.log — null is unreachable there. No action needed.
code-lead deleted branch boss/609 2026-04-30 22:18:30 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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!614
No description provided.