Repository navigation
fix(ipr): re-apply a signature on the new head when a cross-repo push races it - #8256
Conversation
… races it Several AAO repos call ipr-check-callable.yml, and each writes the shared signatures/ipr-signatures.json on adcp's main. Actions concurrency groups are per repo, so the write group does not serialize writes across repos. When two signatures were recorded at the same time, the second push was rejected and the script ran `git pull --rebase`. Both commits append to the end of the same JSON array, so the rebase conflicted and the record job failed. On a rejected push, the script now resets to the fresh remote head and applies the signature to that ledger again instead of replaying its commit. If the author is already recorded there (for example, they signed on another repo's PR at the same moment), it skips the write. It retries up to 5 times with exponential backoff and jitter, then fails with the last push error. The ledger format and the post-write status behaviour from #8224 are unchanged: the commented head and the PR's latest head are still marked success after the write. Tests run real git against a local bare remote. They cover a concurrent append from another repo, the same author recorded concurrently, retry exhaustion with backoff, and no success status when recording fails. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Ladon verdict: Escalate to human review
Escalate — gated workflow files require human/CODEOWNERS approval.
This PR modifies .github/workflows/ipr-agreement.yml and .github/workflows/ipr-check-callable.yml, both of which match the gated-path glob .github/workflows/** (gated_paths: true). The current review_decision is REVIEW_REQUIRED, not APPROVED, so the hard approval gate (decision rule row 2) is not satisfied and the outcome cannot be approve.
The change itself is clean on the merits: the reviewer reports no Critical/High/Medium findings. It replaces the rebase-retry loop in check-and-record.mjs with a reset-and-reapply approach (recordSignatureWithRetry + createGitLedgerOps), correctly fixing the cross-repo append conflict on the shared ledger JSON. Logic is idempotent (findSignature guards every write), fails closed on retry exhaustion, and is tested against a real local bare remote. The only non-blocking note is a pre-existing lack of explicit timeout on git fetch/push, bounded by the concurrency group and job timeout and not worsened here.
Escalation reasons
.github/workflows/ipr-agreement.ymland.github/workflows/ipr-check-callable.ymlare under the hard approval gate (.github/workflows/**) andreview_decisionisREVIEW_REQUIRED. Human/CODEOWNERS approval is required before merge.
Once a human/CODEOWNERS approval lands (review_decision: APPROVED), this PR would fall through to a clean approve given no blocking findings.
Why human review
- Gated paths touched without approval: .github/workflows/ipr-agreement.yml and .github/workflows/ipr-check-callable.yml match .github/workflows/** (gated_paths: true) while review_decision is REVIEW_REQUIRED — human/CODEOWNERS approval required.
- This PR touches a path under a hard, non-overridable approval gate (.github/workflows/ipr-agreement.yml (modified) matches
.github/workflows/**; .github/workflows/ipr-check-callable.yml (modified) matches.github/workflows/**) and the current GitHub review decision is 'REVIEW_REQUIRED', not APPROVED. This is a hard gate enforced in code — Ladon cannot auto-approve until a human/CODEOWNERS approval is recorded, regardless of how clean the diff is.
Summary
Several AAO repos call
ipr-check-callable.yml, and every one of them writes the sharedsignatures/ipr-signatures.jsonon adcp'smain. Actions concurrency groups are per repo, soadcp-ipr-signature-writeserializes writes within one caller repo but not across repos.When two signatures landed at the same time, the second push was rejected and
check-and-record.mjsrangit pull --rebase. Both commits append to the end of the same JSON array, so the rebase conflicted and therecordjob failed. I reproduced the conflict with two shallow clones of a local bare remote.Fix
scripts/ipr/check-and-record.mjsnow records throughrecordSignatureWithRetry:origin/main, runsreset --hard FETCH_HEAD, re-reads the ledger, and adds the entry again. It does not replay the old commit.Unchanged from #8224:
writeSignatures.success.The handler now uses one
ledger.record(entry, message)call in place ofwrite+commit. The git ops take a directory, so tests can point them at a temp repo. The workflow comments andgovernance/ipr-bot-setup.mdnow describe the new retry instead of the rebase.Tests
New cases in
tests/ipr-check-and-record.test.mjs. The first two run real git against a local bare remote with two shallow clones of the same commit:writeSignaturesformat, and the head getssuccess.success.The existing handler tests were updated for the
recordinterface.Verification
npm run typecheck: passnpm run test:unit: passnpm run test:server-unit -- --shard=N/3: 1/3 4612 passed; 2/3 3311 passed, 31 skipped; 3/3 4687 passedcheck-changeset-protocol-scope.cjs --has-protocol-scoped-changesreports no protocol-scoped changes.🤖 Generated with Claude Code