Skip to content

fix(ci): re-send a resolve-conflicts, fix-ci or dependency round a head-lag stop lost, once - #13230

Merged
kyle-sexton merged 2 commits into
mainfrom
fix/head-lag-resend-other-dispatchers
Nov 4, 2026
Merged

kyle-sexton merged 2 commits into
mainfrom
fix/head-lag-resend-other-dispatchers

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

No related issue: hub ledger L282, the follow-up named in #13224. A head-lag stop also lost resolve-conflicts, sync-shared-copies, fix-ci and review-dependency rounds.

Summary

#13224 makes the address-feedback sweep re-send a round that a head-lag stop lost. The other dispatchers treat the same neutral superseded-sha check on the PR's current head as handled, so on a head-lag stop nothing sends their activity again:

  • dispatch.mjs through anyHandles;
  • fix-ci-dispatch.mjs, where the check is not retryable;
  • dependency-dispatch.mjs, where it is in TERMINAL_SKIPS.

Fix

  • limit-stop.mjs: one shared rule, HEAD_LAG_STOP and headLagStop (:116-117), with a lagStopsRelease opt-in inside released (:129).
    • With the opt-in, a superseded-sha check on the PR's current head handles nothing, the way a kill-switch stop does.
    • It counts toward limit-stops-per-head, so each such stop is re-sent at most until the cap.
    • A newer round still going, or one that acted, holds the head under anyHandles, so a stop is never sent twice.
  • Opted in:
    • dispatch.mjs: resolve-conflicts and sync-shared-copies.
    • fix-ci-dispatch.mjs: the stop joins paused, so it retries under the per-head cap without spending a restart.
    • dependency-dispatch.mjs: TERMINAL_SKIPS keeps superseded-sha for the select job's own skip, which lands on a head the PR has already left (review-dependency.mjs:221). A lag stop on the current head is no longer recorded as handled.
  • Each re-send logs refire: #<n>: head <sha>'s <check> was stopped head-lag; sending it again. address-feedback stays on fix(ci): re-send an address-feedback round a head-lag stop lost, once #13224's sweepRefused, never on this path.

Risk

  • The dependency dispatcher has no in-flight run guard, which was already true before this change. A re-sent review still running when the next tick reads the head could be sent again (UNVERIFIED). Only the per-head cap bounds it.

Verification

  • Per dispatcher: a lag stop on the current head is re-sent once with the refire line. Nothing is sent when a newer round is still going, when a newer round acted, or at the cap.
    • resolve-conflicts: a stop on a head the PR left is not re-sent.
    • fix-ci: no refire line when the head moved.
    • limit-stop.test.mjs: a unit test for the opt-in.
    • With origin/main's four source files, all four dispatcher tests dispatch nothing.
  • All 69 affected node suites pass. scripts/check-affected.sh --base origin/main --no-fetch: exit 0.

Related

🤖 Generated with Claude Code

…ad-lag stop lost, once

check-trusted-trigger's head-lag stop posts a neutral superseded-sha
skip on the head the run read. Every other superseded-sha check lands
on a head the PR has left, so on the head a dispatcher reads, the PR's
current one, it is a stop that did no work. The tick's dispatchers
read it as handling the head, so the round was lost until a push.

limit-stop.mjs gains headLagStop and a lagStopsRelease opt-in that
reads such a stop as it reads a kill-switch stop: it handles nothing
and counts toward limit-stops-per-head. A newer run still going or
finished decides the head, so each stop is sent again once.

- dispatch.mjs: resolve-conflicts and sync-shared-copies opt in.
- fix-ci-dispatch.mjs: the stop is a paused run, retried under the
  per-head cap without spending a restart.
- dependency-dispatch.mjs: opts in. TERMINAL_SKIPS keeps superseded-sha
  for the select job's own skip, which lands on the head it was sent
  for; a lag stop on the current head is no longer recorded handled.

Each logs a `refire: #<n>: head <sha>'s <check> was stopped head-lag`
line. address-feedback keeps its own path (sweepRefused).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review November 4, 2026 13:27
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-04T13:29:32.380081Z a99b2ef Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed a7dfcf8eeff462faf8d93b309d8a6de6afa9cb4a: read 3 of 3 changed file(s); 0 finding(s), 0 as inline comments.

Security review: PR #13230 (incremental, head a7dfcf8)

I found no security issues. This review covered only the changes since a99b2ef, and only CRITICAL and IMPORTANT findings were in scope because this lane already reviewed an earlier head. I read every hunk of all 3 files in the incremental list.

What changed

  • dispatch-record.mjs#L28 raises RECORD_VERSION from 5 to 6. The comment above it gains a line for the new version.
  • dispatch.test.mjs and dependency-dispatch.test.mjs get new tests: a version-5 record is now read as empty, so a head-lag-stopped head is dispatched again.

What I checked

  • What the reader does with an old record. parseRecord returns an empty record for any version other than 6. The parser and its validation (SHA and run-id regexes, the MAX_DOCS_RUNS cap) are unchanged.
  • Effect. The record is a skip cache, so dropping it only makes the dispatchers read heads again. It admits no new actor, widens no token scope, and lets no new untrusted text reach a lane. The trusted-trigger check, the kill switch, the merge holds and the token mint are all unchanged by this push.
  • Trusted path. dispatch-record.mjs is on the trusted-path list. I read its whole change and the reader it gates. A version number changing is not a widening.

Spend. Dropping the cached version-5 record once means each open pull request's heads are read again. Lanes the old record held as handled may be dispatched again, and per-head fix-docs counts restart, so at most a cap's worth of extra runs per head. That uses prepaid or subscription usage only, so it is not a hold.

Settled threads. The thread on dispatch.mjs line 257 is marked lines changed since resolution: no. This push is that thread's fix, and I raised nothing on it.

Prior findings. The earlier summary had 0 findings, so nothing carries over.

Embedded text. The PR description and the settled-thread text contain no instructions aimed at this lane.

critical-findings: 0

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a99b2ef572

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/scripts/pr-unblock/dispatch.mjs Resolved
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed a7dfcf8eeff462faf8d93b309d8a6de6afa9cb4a: read 3 of 3 changed file(s); 0 finding(s), 0 as inline comments.

Code review (incremental, blocking only): no findings

Scope. This review covers only what changed since a99b2ef5. I read every hunk of the 3 files listed for this push. This lane already reviewed an earlier head, so I report blocking findings only.

What changed. RECORD_VERSION goes from 5 to 6 in dispatch-record.mjs. The comment above it now names version 6's reason: a resolve-conflicts, sync-shared-copies or review-dependency head that a head-lag stop left. Two new tests check that a version-5 record is read as empty and that the lag-stopped head is sent again.

What I checked

  • The reader drops old records. parseRecord returns an empty record for any version other than RECORD_VERSION. So a cached version-5 record, which may hold a lag-stopped head as handled under the old rule, is dropped once, and the head is read again under the new rule.
  • Side effect of dropping the record. Dropping it also clears that record's dispatched and docsRuns entries. Earlier version bumps did the same, and the module header already accepts that cost ("a lost fix-docs run list restarts that head's count, at most the cap's runs more"). It is not a new risk.
  • The updated round-trip test. In dispatch.test.mjs the record round-trip test now writes and expects version: 6, which matches the new constant.
  • The new tests run. Every helper they use already exists in its test file: lagStop, copyApi, copyRefusal, copyDispatch, harness and dispatchedNumbers in dispatch.test.mjs, and roundRun and headOf in dependency-dispatch.test.mjs. Each test asserts that the version-5 record is read as empty before it asserts the re-send.
  • Unverified claim. The owner reply says both tests fail with the version set back to 5. I could not run tests here, so that claim is unverified.

Settled threads. The thread on dispatch.mjs:257 (resolved, owner reply) is the point this push fixes. Its lines are unchanged since it was resolved, and I raise nothing on it.

Closing issue: none. The PR context names no closing issue, so there are no acceptance criteria to check, and it holds no instructions aimed at this lane.

Trusted paths: .github/scripts/pr-unblock/dispatch-record.mjs matches one. I read its whole change and the reader it gates. It needs no hold.

Spend (not a finding): dropping the cached records once makes the next dispatcher tick re-read every open PR's head, using GitHub API reads. It can also re-send lag-stopped rounds under the per-head cap, using prepaid model and Actions usage.

A version 5 record could hold a resolve-conflicts, sync-shared-copies or
review-dependency head as handled for good when its only check was a
head-lag stop, and each dispatcher's isHandled skip read it before the
lagStopsRelease opt-in could. RECORD_VERSION 6 makes parseRecord read
an older record as empty, so each such head is read once more under
the new rule. The cache key does not name the version, so no workflow
changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit 4aa79a7 Nov 4, 2026
46 checks passed
@kyle-sexton
kyle-sexton deleted the fix/head-lag-resend-other-dispatchers branch November 4, 2026 13:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant