Skip to content

nightly and tend-mention both action the same review, duplicating a full fix-and-verify cycle before the dedup guard fires #865

Description

@cargo-affected-bot

Problem

When tend-nightly opens a PR and stays in-session polling its CI, tend-review fires on that PR and posts findings — and then two actors race to fix them: the still-live nightly session, and the tend-mention session dispatched by the pull_request_review event. Both read the review, make the same edits, run the same test suite, and commit. The loser only discovers the duplication at git push time, discards its commit, and throws away everything it just computed. No wrong output reaches the repo — every dedup guard fires correctly — but a full fix-and-verify cycle is paid twice.

Observed on max-sixty/cargo-affected#77:

Time (UTC) Actor Event
07:13:13 tend-nightly 31080083613 session starts
07:21:27 nightly pushes 67e96ec, opens PR #77
07:21:33 tend-review 31080609471 starts on #77
07:27:36 review posts review 4872123225 — one inline nit + one structural point in the body
07:27:49 tend-mention 31081009768 dispatched on that review, 32s after the event — nightly is still live
07:31:15 nightly pushes 56406d7, the fix for both review points
07:31:34 nightly posts the inline reply, then exits at 07:36:51
~07:33 mention finishes the same edits, runs the full suite, commits locally
~07:34 mention git fetch finds 56406d7, git reset --hard, discards its commit
07:36:32 mention salvages the one non-duplicated thing it had — an isolated-break verification the sibling had explicitly flagged as unclosed — and posts that as a reply

Run 31081009768 cost $3.55 / 58 turns. Reading its session log, the discarded portion includes: reading both test files and src/db.rs, four Edit calls that duplicate nightly's, a rustup component add llvm-tools + nextest install, two injected-break verification rounds, a cargo clippy --all-targets, and two full cargo test runs — all before the git fetch that revealed the sibling push. That is roughly three-quarters of a $3.55 session, on a day whose total tend spend was $13.89.

Why the existing guards don't catch it

Every dedup rule in running-in-ci is anchored at the output boundary, not the work boundary:

  • Dedup recheck immediately before gh pr create — checks for sibling PRs, not sibling sessions, and only at create time.
  • Re-check PR state before pushing a follow-up commit — checks state == OPEN, which is true here.
  • Recheck Before Posting — re-fetches the thread before commenting, which is exactly what let the mention run notice the sibling reply and post only the residual.

All three fired correctly. There is no rule that says before starting expensive work, check whether another run is already doing it, so the collision is only ever discovered after the cost is sunk.

Why it recurs

It is close to deterministic given the current workflow shape. Nightly opens a PR and then must stay alive polling CI (nightly SKILL.md L323, plus the gating rule in running-in-ci). tend-review fires on the PR immediately. The mention dispatch arrives ~30s after the review is submitted. So whenever a nightly-opened PR draws a review with actionable content, nightly is still in-session when the mention run starts, and neither knows about the other.

Second occurrence in four windows on this repo. The first was 2026-08-03 on max-sixty/cargo-affected#49: nightly 30793699011 pushed two review-response commits while mention 30795051884 independently re-derived the identical edit, verified it against src/project.rs, committed, then found nightly's equivalent already pushed and discarded it ($1.07 / 22 turns). Same mechanism, smaller bill.

This is also the fourth distinct symptom of "multiple tend sessions live on one PR" recorded here, after #828, #829, and #830 — all three of which were confirmed and fixed. Those three patch the consequences of head instability; this one is about the wasted work that produces it.

Two candidate fixes

A cheap pre-flight sibling check in running-in-ci. Before a review-response session starts editing, ask whether another tend run is already in-flight against the same PR, and if so either exit or wait for it:

# Sessions that will edit a PR branch: bail if a sibling tend run is already live on it.
SIBLINGS=$(gh api "repos/$GITHUB_REPOSITORY/actions/runs?status=in_progress&per_page=100" \
  --jq --arg wf "$GITHUB_WORKFLOW" --arg own "$GITHUB_RUN_ID" \
  '[.workflow_runs[] | select((.id|tostring) != $own) | select(.name | startswith("tend-")) | {id, name, head_branch}]')

Matching on head_branch catches the mention-vs-mention case; the nightly-vs-mention case needs the branch nightly is working on, which is only visible from its checkout — so this alone is partial.

Or a role boundary in the nightly skill, which is the cleaner cut. tend-mention exists precisely to answer reviews; nightly's documented job at step 8 ends at "create PR, poll CI". Saying so explicitly — nightly gates on CI for the PRs it opens but does not action reviews on them; the mention workflow owns that loop — removes the race by construction rather than by detection, and costs one sentence. It also avoids the failure mode where nightly's 20-minute session is stretched further by review work that a dedicated run is already handling.

The second reads as the better fix to me, but the trade-off is real: if nightly hands off, the review response no longer rides along inside a session that already has the repo built and the test tooling installed. Flagging both rather than assuming.

Evidence

  • Nightly session log: run 31080083613, artifact claude-session-logs-28f5e7d0.
  • Mention session log: run 31081009768, artifact claude-session-logs-b6b3a7b3 — the git reset --hard origin/tests/replace-dead-shim-tripwires call and the preceding text turn ("A sibling run already pushed a substantively equivalent fix. I'll drop my duplicate commit") are the moment the duplication is discovered.
  • Prior occurrence: runs 30793699011 and 30795051884, recorded on review-runs-tracking: 2026-08 cargo-affected#73.

Filed from tend-review-runs on max-sixty/cargo-affected under the standing cross-repo exception in that repo's running-tend overlay.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions