Plan: Verify PhaseRouterService.route() Idempotency for BullMQ Retry#
Summary#
Audit and harden PhaseRouterService.route() and executePhase() so that BullMQ stalled-job retries produce no duplicate side effects — no duplicate comments, DB rows, or Claude invocations. This unblocks AW-13 (BullMQ migration).
Files#
| File | Action | Description |
|---|---|---|
src/phase-router/phase-router.service.ts | modify | Add idempotency guards for createTask() race and comment dedup |
src/hooks/phase-hooks.service.ts | modify | Guard onPhaseComplete() hooks against duplicate comment posting |
src/task-state/task-state.service.ts | modify | Convert createTask() to upsert or wrap with try/catch for unique constraint |
src/phase-router/phase-router.service.spec.ts | modify | Add retry-idempotency tests: call route() twice, assert no duplicates |
src/hooks/phase-hooks.service.spec.ts | modify | Add tests for duplicate-call safety on hook methods |
Steps#
- Guard
createTask()against unique-constraint race: WrapcreateTask()call inroute()with a try/catch that falls back tofindByIssueAndRepo()on duplicate key error, or convert to upsert pattern inTaskStateService. - Guard
onPhaseComplete()hooks against duplicate comments: Before posting phase-completion comments inonPlanComplete(),onDeliverComplete(), andonCompoundComplete(), check if a matching comment already exists on the issue/PR, or check task phase status is not already'complete'before calling the hook. - Guard
onPhaseBlocked()against duplicate blocked comments: Add a check inPhaseHooksService.onPhaseBlocked()to skip posting if anagent-blockedcomment was already posted for the current phase. - Verify
invoke()cannot double-enqueue: Confirm the phase-status'active'guard inroute()(line ~272) prevents a secondexecutePhase()call, and add a defensive early-return inexecutePhase()if phase is already'active'when re-entered. - Write retry-idempotency unit tests for
route(): Callroute()twice with identical event; assertcreateTask()called once,invoke()called once, hooks called once, no duplicate comments. - Write retry-idempotency unit tests for hooks: Call
onPhaseComplete()twice for the same phase; assert comment posted once, labels applied once.
Verification#
npm run testpasses with new retry-idempotency test casesnpm run lintpasses with zero warningsnpm run buildcompiles cleanly
Risks#
- ⚠️ The
createTask()upsert must preserve existing task state on retry — must not reset phase columns if the task already exists. - ⚠️ Comment-existence checks add GitHub API calls; use task-state flags instead where possible to avoid rate-limit pressure.