workflow-fix: dedup follow-up-round implementer dispatch (per-stage idempotency)
Highlight text on any card (body, plan, or an event) to anchor a comment, or leave a whole-task comment here. Mention @claude to summon a reply.
No comments yet.
Overview / Motivation
Auto-filed by the workflow-fix-on-bug protocol from a workflow-fix candidate raised on task #778 (emitting agent: experiment-implementer).
Goal
Before dispatching an experiment-implementer for a same-issue follow-up round, check events.jsonl for an existing same-round stage-dispatch marker within a recency window (or an in-flight implementer marker) and skip/no-op the re-dispatch, mirroring the single-orchestrator guard.
Workflow gap
- Bug observed: The /issue orchestrator posted
stage-dispatch stage=followup-implementing round=1 subagent=experiment-implementertwice (2026-07-01T22:28:53Z and 22:34:32Z) for the SAME follow-up round on #778, spawning two experiment-implementer subagents that concurrently edited one issue-778 worktree. Root context: two orchestrator sessions were driving the same round in parallel (a stale-wake unregistered session + the registered autonomous session); each independently ran planner → gate → implementer dispatch. The Step-0 single-orchestrator guard fired at INVOCATION time but the per-stage dispatch had no idempotency window, so the second orchestrator's Step-9-entry-guard freshness check (which keys on the LATEST marker being a stage-dispatch breadcrumb for the CURRENT stage) did not prevent the duplicate dispatch — intervening markers (codex-task, progress) buried the first breadcrumb. - Why it is a workflow gap: The follow-up-round implementer-dispatch step has no idempotency guard against re-dispatching the same round; the Step 9 entry guard only fires when the breadcrumb is the LATEST event, so any intervening marker defeats it. A stale/slow first dispatch triggers a second, causing a concurrent-write collision and duplicate epm:experiment-implementation markers (the #524/#535 incident family, now at the sub-dispatch grain).
- Confidence (emitter): medium
Proposed change (candidate diff sketch — refine in planning)
At the followup-implementing dispatch step (and generalize to every Step 8/9 stage-dispatch site):
+ prior = most recent `stage-dispatch stage=followup-implementing round=<r>` marker
+ (scan BACKWARDS through events.jsonl for the CURRENT stage's breadcrumb,
+ skipping other kinds — same per-stage scan the Step 9 parallel-stage note
+ already prescribes, instead of only inspecting the single latest event)
+ if prior exists AND (no epm:failure / epm:experiment-implementation since) AND age < freshness window:
+ log "implementer already dispatched for round <r> at <ts>; skipping re-dispatch"; EXIT
Also consider surfacing the Step-0 single-orchestrator guard's "stop the stale session first" action more prominently for the followup-loop entry path (the root cause was two live orchestrators).
Scope / surfaces
- Primary target:
.claude/skills/issue/SKILL.md - Grep the workflow surface for the pattern before editing (
grep -rln 'stage-dispatch' .claude/ CLAUDE.md scripts/) and update every hit; list them in the plan.
Constraints / invariants
- Workflow-surface only — never experiment code,
configs/, ortasks/. scripts/workflow_lint.py --check-askspasses; ruff on touched files passes; ifworkflow.yamlorCLAUDE.mdchange, they stay consistent with the rule file.- This session runs under
EPM_WORKFLOW_FIX_SESSION=1and carries aworkflow_fix_target:Provenance line — it MUST NOT auto-route any of its own subagents' workflow-fix candidates (recursion guard).
Provenance
- workflow_fix_target: .claude/skills/issue/SKILL.md
- fingerprint: ac5637ece583
target_file: .claude/skills/issue/SKILL.md
bug_observed: The /issue orchestrator posted stage-dispatch stage=followup-implementing round=1 subagent=experiment-implementer twice (22:28:53Z and 22:34:32Z) for the SAME follow-up round, spawning two experiment-implementer subagents that concurrently edited one issue-778 worktree.
why_workflow_gap: The follow-up-round implementer-dispatch step has no idempotency guard against re-dispatching the same round; a stale/slow first dispatch triggers a second, causing a concurrent-write collision and duplicate epm:experiment-implementation markers.
proposed_change: Before dispatching an experiment-implementer for a followup round, check events.jsonl for an existing same-round stage-dispatch marker within a recency window (or an in-flight implementer marker) and skip/no-op the re-dispatch, mirroring the single-orchestrator guard.
diff_sketch: |
At the followup-implementing dispatch step:
- prior = latest
stage-dispatch stage=followup-implementing round=<r>marker - if prior exists AND (no epm:failure/epm:experiment-implementation since) AND age < DISPATCH_DEDUP_WINDOW:
-
log "implementer already dispatched for round <r> at <ts>; skipping re-dispatch" ; return
(mirror the Step-0 single-orchestrator detect-and-exit guard for the sub-dispatch case) confidence: medium related_task: #778