Skip to content

Bug: stale canonical PR routing can spray duplicate PRs and merge the wrong lane #242

Description

@fujiwaranosai850

Summary

DevClaw allowed one issue to fork into multiple live PRs, then continued review and merge routing against stale PR metadata.

This is not just duplicate PR clutter. In the reproduced lane, the workflow ultimately merged the wrong PR after reviewer intent had already converged on a different canonical PR.

Reproduction case

Observed on First Light issue:

PR spray created for the same issue:

  • #426 feature/425-runtime-authority-move-budget
  • #427 feature/425-runtime-budget-source-of-truth
  • #428 feature/425-harness-runtime-source-of-truth
  • #429 feature/425-harness-runtime-sole-source-of-truth-for-move-limit-runtime-budget-during-simulation-and-replay-validation
  • #430 feature/425-harness-runtime-move-budget-authority
  • #431 feature/425-harness-runtime-authority
  • #432 feature/425-runtime-move-budget-authority

All were opened the same day by the same worker actor against main.

Core failure

The issue-level canonical PR pointer effectively stayed stale on #426 while later developer retries created and/or updated newer PRs. Reviewer dispatch and later merge routing continued to trust the stale tracked PR instead of the latest resolved canonical PR.

In other words:

  1. developer retries were allowed to create replacement PRs for the same issue
  2. issue state did not atomically switch canonical prUrl to the replacement PR
  3. reviewer runs continued to inspect the stale PR
  4. merge routing later acted on stale PR identity

Evidence

1) Initial PR was #426

GitHub issue timeline for firstlight#425:

2) Repeated To Improve -> Doing redispatches created or updated newer PRs

Observed PR sequence:

  • #426 created 2026-05-29T16:24:43Z
  • #427 created 2026-05-29T16:34:53Z
  • #428 created 2026-05-29T17:30:49Z
  • #429 created 2026-05-29T17:47:28Z
  • #430 created 2026-05-29T18:13:22Z
  • #431 created 2026-05-29T19:32:24Z
  • #432 created 2026-05-29T20:26:04Z

Representative workflow events from orchestrator_intervention(get_events, issueId=425):

  • 2026-05-29T17:31:27.527Z developer completed with prUrl=https://github.com/yaqub0r/firstlight/pull/428
  • 2026-05-29T18:14:05.136Z developer summary explicitly says it opened PR #430, but event prUrl still points at https://github.com/yaqub0r/firstlight/pull/426
  • 2026-05-29T20:26:28.927Z developer summary explicitly says it opened PR #432, but later workflow state still did not reliably converge on #432

This suggests the worker output and the issue’s tracked PR identity diverged.

3) Reviewer kept reviewing stale PR #426

Repeated reviewer completions reference #426, for example:

  • 2026-05-29T17:33:36.819Z reviewer rejected after reviewing PR #426
  • 2026-05-29T17:50:56.414Z reviewer rejected PR #426
  • 2026-05-29T18:04:16.015Z reviewer rejected PR #426
  • 2026-05-29T18:15:31.151Z reviewer rejected PR #426
  • 2026-05-29T18:28:03.645Z reviewer rejected PR #426
  • 2026-05-29T18:39:04.866Z reviewer rejected PR #426
  • 2026-05-29T18:57:54.526Z reviewer rejected PR #426
  • 2026-05-29T19:09:00.285Z reviewer rejected PR #426

So review routing stayed anchored to the stale PR handle even while the developer lane was moving.

4) Reviewer later converged on #432, but merge still targeted stale identity

Late-lane evidence:

  • 2026-05-29T21:03:29.305Z developer completed and explicitly said canonical PR #432 is the correct fix branch; summary also states #426 is stale/superseded
  • 2026-05-29T21:05:23.575Z reviewer approved after reviewing canonical PR #432

But the workflow lane had already shown evidence of stale PR routing earlier, and during the investigation we observed a particularly dangerous mismatch state:

  • reviewer text referenced the canonical newer PR
  • workflow merge routing still resolved through stale tracked PR identity

That is the integrity bug this issue is about.

5) Tester then showed the base branch still reflected stale behavior

A downstream test run on the merged/base branch still found the old runDoc.levelJson authority behavior. That means this is not merely cosmetic metadata drift. Stale PR identity can result in the wrong artifact being treated as the lane’s truth.

Expected behavior

For any one issue, DevClaw should maintain exactly one canonical live PR identity unless a replacement is explicitly performed.

If a developer run creates a replacement PR:

  • the issue’s canonical prUrl must update atomically
  • older sibling PRs must be marked superseded or removed from routing
  • reviewer dispatch must resolve from the new canonical prUrl
  • merge must target the exact same PR id that reviewer approved

Actual behavior

DevClaw allowed this inconsistent state:

  • worker summary names PR #X
  • issue state still points at PR #Y
  • reviewer dispatch uses #Y
  • later approval text may refer to #X
  • merge/path routing can still act on #Y

Likely fault domains

Please investigate at least these boundaries:

  1. work_finish / completion ingestion

    • when developer returns prUrl and summary text, what becomes the canonical PR for the issue?
    • can summary text and stored prUrl diverge without a hard failure?
  2. issue state / canonical PR storage

    • where is the canonical PR for an issue stored?
    • when a later developer run opens a new PR, why is the old PR still considered authoritative?
  3. review dispatch resolution

    • does reviewer dispatch pull PR identity from issue state, from labels, from PR cross references, or from worker summary text?
    • why did repeated reviewer runs continue to inspect #426 even after newer PRs existed?
  4. merge routing

    • what PR identifier is used when reviewer approves?
    • can approval of one PR accidentally merge a different PR because merge consumes stale issue metadata?
  5. duplicate-run / spawn dedupe

    • repeated To Improve -> Doing cycles were all sessionAction: "spawn"
    • is there missing dedupe or reuse protection when an issue already has an active developer lane or open PR?

Acceptance criteria

  • Reproduce or convincingly trace how one issue can end up with multiple open PRs while keeping a stale canonical PR pointer.
  • Identify the exact source of truth for canonical PR identity in the workflow.
  • Prove that reviewer dispatch always uses the current canonical PR, or fix it so it does.
  • Prove that merge uses the same PR id that reviewer approved, or fix it so it does.
  • If worker output names PR #X but stored issue state still resolves to #Y, fail closed with an explicit routing error.
  • Add protection so replacement PR creation requires explicit supersession handling for older sibling PRs.
  • Add coverage for the failure mode where multiple PRs are cross-referenced to one issue during repeated To Improve redispatches.

Nice-to-have safeguards

  • one issue -> one canonical PR invariant
  • explicit supersedes / superseded-by handling in the workflow
  • auto-comment or auto-label stale sibling PRs when canonical PR changes
  • reviewer refusal when current issue PR identity and worker-reported PR identity disagree
  • merge refusal when approved PR id does not equal canonical issue PR id

Operator impact

This bug makes the workflow unsafe to trust in fix/review loops:

  • review can stay attached to stale artifacts
  • newer corrective work can be ignored
  • the wrong PR can be merged
  • testers can be handed a base branch that does not actually contain the reviewed fix

Repo / environment notes

Local DevClaw repo assumed for this issue:

  • repo path: /home/sai/git/devclaw
  • remote: https://github.com/yaqub0r/devclaw.git

Suggested labels

  • bug
  • workflow-integrity
  • review-routing
  • pr-lifecycle

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions