fix(dispatch): dedup rebase against in-flight queue, not just task_history #859

Merged
reviewer merged 1 commit from fix/dedup-rebase-inflight into main 2026-05-04 22:22:05 +00:00
Collaborator

Summary

#838 produced 16 duplicate rebase tasks across two dev workers in under 20 minutes (live runaway captured at 2026-05-04 23:39 → 23:48). Two compounding loops with the same root cause — dedup measured against terminal state instead of in-flight state.

Bug 1 — tail-pr-rebase-watchdog

hasRecentDispatch(repo, pr, 10min) queries task_history WHERE started_at > since. persistTask() is only called from onFinish, so tasks sitting in a worker queue have no row. Every 60 s tick the watchdog re-evaluated dedup, missed, and enqueued another rebase. Compounded until the running task finished.

Bug 2 — dispatchPrRebase (post-merge cascade + janitor)

Dedup keyed repo#pr@headSha. When the rebase agent pushes a fix commit, head SHA flips → cache miss → second rebase dispatched on top of the running one. Per-commit re-fire amplified Bug 1.

Fix

New helper hasInFlightTask(repo, issueNumber, taskType?) in domain/dispatch/registry.ts scans getWorkers() for a matching currentTask or queued request. Wired as the first guard in:

  • tail-pr-rebase-watchdog.ts (before hasRecentDispatch)
  • dispatchPrRebase in event-handlers.ts (before SHA-keyed dedup)

Existing dedups stay as secondary nets. The watchdog now also tags buildAgentRequest with task_type: "rebase" so the in-flight check can match on type — a normal review task on the same PR doesn't suppress a rebase.

Test plan

  • just qa clean (3116 tests pass)
  • New unit tests:
    • hasInFlightTask: 6 cases (running, queued, no-match, repo isolation, taskType filter, empty registry)
    • tail-pr-rebase-watchdog: 2 cases (skips on in-flight, called before history dedup)
  • Operational verification: confirm next #838-class rebase produces a single dispatch (no queue spam)

🤖 Generated with Claude Code

## Summary #838 produced **16 duplicate `rebase` tasks** across two dev workers in under 20 minutes (live runaway captured at 2026-05-04 23:39 → 23:48). Two compounding loops with the same root cause — dedup measured against terminal state instead of in-flight state. ### Bug 1 — `tail-pr-rebase-watchdog` `hasRecentDispatch(repo, pr, 10min)` queries `task_history WHERE started_at > since`. `persistTask()` is only called from `onFinish`, so tasks sitting in a worker queue have **no row**. Every 60 s tick the watchdog re-evaluated dedup, missed, and enqueued another rebase. Compounded until the running task finished. ### Bug 2 — `dispatchPrRebase` (post-merge cascade + janitor) Dedup keyed `repo#pr@headSha`. When the rebase agent pushes a fix commit, head SHA flips → cache miss → second rebase dispatched on top of the running one. Per-commit re-fire amplified Bug 1. ## Fix New helper `hasInFlightTask(repo, issueNumber, taskType?)` in `domain/dispatch/registry.ts` scans `getWorkers()` for a matching `currentTask` or queued request. Wired as the **first guard** in: - `tail-pr-rebase-watchdog.ts` (before `hasRecentDispatch`) - `dispatchPrRebase` in `event-handlers.ts` (before SHA-keyed dedup) Existing dedups stay as secondary nets. The watchdog now also tags `buildAgentRequest` with `task_type: "rebase"` so the in-flight check can match on type — a normal review task on the same PR doesn't suppress a rebase. ## Test plan - [x] `just qa` clean (3116 tests pass) - [x] New unit tests: - `hasInFlightTask`: 6 cases (running, queued, no-match, repo isolation, taskType filter, empty registry) - `tail-pr-rebase-watchdog`: 2 cases (skips on in-flight, called before history dedup) - [ ] Operational verification: confirm next #838-class rebase produces a single dispatch (no queue spam) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
fix(dispatch): dedup rebase against in-flight queue, not just task_history
All checks were successful
qa / dockerfile (pull_request) Successful in 24s
qa / db-schema (pull_request) Successful in 35s
qa / qa-1 (pull_request) Successful in 1m34s
qa / qa (pull_request) Successful in 0s
5c52a58fb9
#838 produced 16 duplicate `rebase` tasks across two dev workers in under
20 minutes. Two compounding loops, same flaw — dedup against terminal
state instead of in-flight state:

1. **`tail-pr-rebase-watchdog`**: every 60 s tick called
   `hasRecentDispatch(repo, pr, 10min)` against `task_history`.
   `persistTask()` only fires from `onFinish`, so a queued task has no
   row. The watchdog dedup never saw the rebase it just enqueued and
   re-enqueued every minute until the worker drained.

2. **`dispatchPrRebase` (post-merge cascade + janitor)**: dedup keyed
   by `repo#pr@headSha`. When the rebase agent pushes a fix commit,
   head SHA flips and the dedup map misses, dispatching another rebase
   on top of the running one.

Adds `hasInFlightTask(repo, issueNumber, taskType?)` in
`domain/dispatch/registry.ts` that scans `getWorkers()` for a matching
`currentTask` or queued request. Wired as the first guard in both
call sites; existing dedups stay as secondary nets.

The watchdog now also tags its `buildAgentRequest` with
`task_type: "rebase"` so the in-flight check can match on type.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
reviewer approved these changes 2026-05-04 22:21:59 +00:00
reviewer left a comment

Correct fix. hasInFlightTask scans currentTask + queue so the watchdog and post-merge cascade see pending work regardless of SHA churn — the two root causes confirmed.

Tests cover both paths (queued case explicitly labelled as the bug being fixed). CI green, task_type: "rebase" tag is set at both dispatch sites so the type filter works end-to-end.

Correct fix. `hasInFlightTask` scans `currentTask` + queue so the watchdog and post-merge cascade see pending work regardless of SHA churn — the two root causes confirmed. Tests cover both paths (queued case explicitly labelled as the bug being fixed). CI green, `task_type: "rebase"` tag is set at both dispatch sites so the type filter works end-to-end.
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!859
No description provided.