Repository navigation
Keep the rebase conflict resolver's trust-read timeout over a later stop (#174) - #197
Merged
Merged
Conversation
When the rebase conflict resolver's issue-trust read timed out on its own (RemoteTimeout, timedOut()) or gh answered it (CommandFailed, ghAnswered()), and a cancel, shutdown or the rebase work deadline became pending while gh was stopping, GitRebaser#resolveConflict reported the stop instead. The preparation was then saved as failed with "Task cancelled.", "Server shutdown." or the deadline text. The resolver's catch now rethrows such an error before it checks for a pending stop, as the gateways, the merge coordinator and the server's trust read do since #48: had the stop come first, the read would have reported the stop. RebaseResourcesUnsettled still comes first, and any other resolver failure still reports a pending stop. Tests: - runner-rebase: a real rebase with a resolver whose trust read gave its timeout or gh's answer before a cancel, and before the rebase work deadline; each asserts that the stop was pending when the error was handled, with a control row (another failure reports the stop). Also a cancel that came first, and retained ownership ahead of a timeout. - pre-merge: a real conflicting rebase through PreMergeCoordinator, with the resolver re-reading authorization as production's does; a timeout or gh answer before a cancel or a shutdown is the saved preparation outcome, and a stop that came first is saved as the stop. - runner-rebase-conflict: the resolver passes an authorization timeout through a later cancel and releases its resources. The answer-first cases fail without the fix with the stop's text. Docs: merge-queue.md (the #174 follow-up note) and pre-merge-rebase.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- merge-queue.md: split the resolver trust-read bullet into a short list (stop after the read, stop before it, the work deadline, other failures, retained resources). Scope the deadline claim to GitRebaser with a resolver that settles. With the production resolver a pending work deadline leaves no time for cleanup, so it usually ends as RebaseResourcesUnsettled, whose message carries the timeout or answer. pre-merge-rebase.md says the same. - runner-rebase-conflict test: the work deadline passes during a trust read that timed out; the resolver starts no cleanup, keeps its child claim, and throws RebaseResourcesUnsettled with the timeout text. - gh fake: test/fixtures/gh-answers.ts gives gh 2.102.0's real shape for `gh api` on a missing issue (exit 1, "gh: Not Found (HTTP 404)" on stderr, GitHub's JSON body on stdout), used by the #174 tests and the #48 pre-merge test it was copied from. Only the class and message are read. - rebase.ts: the comment names its assumption that the issue-trust read is the production resolver's only GitHub read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… test cost - Replace the last issue-trust-read gh fakes in the old shape (runner-publishing.test.ts x2, merge-queue-poll.test.ts x1) with ghIssueNotFound(), keeping their assertions on the real message. - test/fixtures/gh-answers.ts: ghFailedExit() builds runWithInput's message, 2,000-character slices included; replayGh in test/fixtures/gh/recorded.ts now uses it, so there is one copy. - Drop the unused answer axis from the stop-first tests (pre-merge: 2 real-rebase tests instead of 4; runner-rebase: 1 instead of 2). - merge-queue.md: the production resolver does settle at the work deadline, by reporting retained resources, not the read's failure; a deadline that passes before the read fails, also while it runs, gives a message without the timeout text (traced, not tested). - pre-merge-rebase.md: regression evidence names the pre-merge.test.ts saved-outcome cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 21, 2026
Name what each trust-read row reports, so the control row's title no longer says the opposite of its assertion; say that any other stop of the rebase takes the cancel's path; point the saved-message claim at the test that covers it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ut (#197 review round 4) The retained-ownership test's cause was a timeout, so the new timeout check kept it even without the precedence line. A second row with a failure of another kind fails without that line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…own timeout (#197 review round 5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tested (#197 review round 6) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…review round 7) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ase (#197 review round 8) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… sentence (#197 review round 9) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
No actionable defects were identified, but final-head CI and real-Docker validation remain unconfirmed in the supplied record.
No open issues 🎉
What changed in this PR
Addresses #174 by preserving the rebase conflict resolver’s earlier trust-read timeout or GitHub error instead of replacing it with a later stop.
Changes:
- Preserves timeout/error reporting while retaining resource-ownership precedence.
- Adds race regressions covering stop ordering and saved preparation outcomes.
- Standardizes GitHub error fixtures and documents behavior.
| File | Description |
|---|---|
| test/runner-rebase.test.ts | Tests error precedence and cleanup. |
| test/runner-rebase-conflict.test.ts | Tests resolver timeout reporting and retained resources. |
| test/runner-publishing.test.ts | Uses realistic GitHub error fixtures. |
| test/pre-merge.test.ts | Tests saved outcomes through a real conflicting rebase. |
| test/merge-queue-poll.test.ts | Updates the issue-read error fixture. |
| test/fixtures/gh/recorded.ts | Reuses shared failed-exit formatting. |
| test/fixtures/gh-answers.ts | Adds shared GitHub error fixtures. |
| runner/rebase.ts | Preserves earlier timeout and GitHub errors. |
| docs/implementation/pre-merge-rebase.md | Documents precedence and regression coverage. |
| docs/implementation/merge-queue.md | Replaces the follow-up note with implemented behavior. |
Review effort: Balanced
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #174. Part of closing build step 4.
Problem
When the rebase conflict resolver's issue-trust read failed with its own timeout or with gh's answer, and a cancel, shutdown or the rebase work deadline was pending when the error was handled,
Rebaser.#resolveConflictreported the pending stop instead (AGENTS.md: keep the original timeout, cancellation and shutdown reason through every layer).Change
runner/rebase.ts#resolveConflictcatch, in the #171 idiom:RebaseResourcesUnsettledstill wins;timedOut(error) || ghAnswered(error)is rethrown as is (new);A stop that came first already makes the gateway and
readIssueAccessthrow the stop.rebase-conflict.ts,GitRebaser.runandpre-merge.tspass the message through unchanged (checked, no change needed).Tests
test/runner-rebase.test.ts: timeout and gh answer before a cancel and before the rebase deadline, each asserting the stop was pending when the error was handled; a control row (another failure still reports the stop); stop first; retained ownership ahead of a timeout plus a later cancel.test/pre-merge.test.ts(newconflictRebasefixture: realGitRebaser, production store hooks, a resolver re-reading authorization as production does): timeout and gh answer before a cancel or shutdown, asserting the result, the savedprepare-mergeresponse, the cleared rebase marker, and that the failing read was the resolver's own; stop-first cases.test/runner-rebase-conflict.test.ts: the resolver passes an authorization timeout through a later cancel and releases its resources.test/runner-rebase-conflict.test.ts, pins the unchanged resolver and passes either way, as do the stop-first, control and ownership cases, by design.Docs:
merge-queue.md(follow-up note replaced),pre-merge-rebase.md.Validation
Current head: 5105e98 (a test title and a doc sentence only since d99298a). Each item names the commit it ran on.
vitest run --project parallel): 2467 passed, 8 failed, 1 skipped. All 8 failures were timeouts (5 to 30 s) inreview.test.ts,history.test.tsandpre-merge.test.tsat load average about 110; those three files re-run alone at d99298a: 177/177 passed. Recorded as load failures, not product failures.Review record
RebaseResourcesUnsettledcarries the timeout text and that no cleanup starts. That test passes with or without the fix, by design: it pins the resolver layer.gh apioutput. Shared faketest/fixtures/gh-answers.tsnow matches gh 2.102.0 (exit 1,gh: Not Found (HTTP 404)on stderr, JSON body on stdout), version named; also replaced at the Investigate summary-only merge lifecycle concerns #48 test that used the same shape.runner-publishing.test.ts(2) and, by grep,merge-queue-poll.test.ts(1). NowghIssueNotFound(). Other HTTP 404/502 fakes fake other gh calls and are out of scope.merge-queue.md. Reworded; the deadline-during-the-read case is marked traced, not tested.runWithInput's message format. One sharedghFailedExithelper, used byghIssueNotFoundandreplayGh.it.eachtitles also named the control row, which asserts the opposite. Titles now name what each row reports.test/merge.test.ts'sghExittoghFailedExit. It is outside this diff and harmless; it belongs with Read app-pinned required checks from the head's check runs (#176) #195, which edits that file.test/pre-merge.test.tsfor saving aRebaseResourcesUnsettledmessage, but that test saved nothing. It now passes an action ID and asserts the savedprepare-mergeanswer (passes).merge-queue.mdstates this behaviour, so it is left as it is and recorded here as a follow-up candidate. (2) a test title said "during" the read; fixed in 5105e98. (3) a 90-word doc sentence; split in 5105e98. (4) this body's validation record; rewritten above.Review-lesson audit (merge gate)
No new rule is needed.
pre-merge-rebase.mdandmerge-queue.mddescribe this behaviour. Not filed as an issue; it can be filed if wanted.Before Copilot (head 5105e98, validated head for this request)
testandreal-dockerin progress on 5105e98. Mergeable: yes. Unresolved threads: 0. Closes Keep the rebase conflict resolver's trust-read timeout over a later stop #174. Deferred follow-ups: the declined trust-refusal case above (not filed).Copilot round 1 (head 5105e98)
Copilot (copilot-pull-request-reviewer): no open issues. Its one caveat was that final-head CI and real-Docker had not finished; both are the merge gate, checked on 5105e98 before merging. No change made.
🤖 Generated with Claude Code