Skip to content

Keep merge stop reasons and GitHub answers through every layer (#48) - #171

Merged
mchwang merged 16 commits into
mainfrom
feat/48-merge-lifecycle-concerns
Oct 14, 2026
Merged

mchwang merged 16 commits into
mainfrom
feat/48-merge-lifecycle-concerns

Conversation

@mchwang

@mchwang mchwang commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Part of #48 (server part; #48 stays open until the browser fixes land). The two browser polling defects will follow in a separate PR stacked on F6b (#161), because F6b rewrites scheduleMergePoll / pollMergeQueue in web/public/app.js.

What #48 asked

Reproduce Copilot's summary-only concerns on PR #46 about the merge lifecycle before changing code, and record evidence for any that cannot be reproduced.

Findings

Eight defects were reproduced. Cache invalidation was found correct.

# Defect Fix
1 Gateway reads (queue watermark, queue inspection, merge-state inspection, the issue gateway's bounded read) reported a later caller abort instead of their own earlier timeout, and replaced a failure gh had already reported Keep the first abort reason; pass gh's own exit (CommandFailed, ghAnswered()) through
2 The coordinator (#merge, #pollQueue) and readIssueAccess replaced a gateway's first stop reason with their own later abort Keep a timeout (timedOut()) or gh answer that came first (AGENTS.md: "Preserve the original timeout, cancellation, and shutdown reason through every layer")
3 A timeout before admission was saved as the click's definite refusal RemoteTimeout (github/timeout.ts); answered as a resendable 503 and never saved; an outcome saved meanwhile under the same click is still replayed; the runner-action trust gate does the same
4 An abort or gh's own timeout that fired after gh's exit replaced GitHub's answer; the gateway labelled a refusal unknown runWithInput ignores stop requests after exit; a refusal counts when gh answered
5 A failed queue inspection answered with the superseded attempt Answer from the current attempt; attach the error only to the attempt it read
6 The merge click's answer reported pre-command readiness after a submission MergeCoordinator.statusAfterMerge, sharing #withAttempt with status()
7–8 Browser: duplicate poll chains let an old poll's error or answer overwrite newer state Separate PR on F6b
— Cache invalidation Correct; test/merge-status-cache.test.ts is evidence

The rules are written up in docs/implementation/merge-queue.md, "Stop reasons and answers (#48)", including what is traced but not tested.

Also corrected: two older sentences in runner-lifecycle.md said a planning GitHub read failure (502) is not recorded; the code saves it and test/planning-production.test.ts checks the replay.

After Copilot's reviews (8e49642 onward)

  • Regression tests for the parallel merge-state inspection stages (test/merge-stop-reasons.test.ts, parallelStages()): a stage's gh exit stops the others, and the inspection waits for every stage and reports that exit, also when its deadline fires while a stopped rule read settles.
  • Older bug found by those tests, fixed: #inspectNow attached the already-fixed check's rejection handler only after the other stages settled. A check that a failed stage stopped, and that settled first, raised a process-wide unhandled rejection. Node's default is to exit on that. The handler is now attached when the check starts.
  • After a caller's abort, the already-fixed check's own earlier gh failure stays an unknown blocker. The inspection discards it for the abort, so the click can be resent. Before, it was saved as the click's refusal with raw gh text.
  • Merged main at 99e58a9 (Run review history work in an abortable worker thread #170, async review loads). #replay keeps Run review history work in an abortable worker thread #170's shutdown abort and tracking, plus this PR's handling of the click's own signal. The merge answer reads its queue snapshot after the review load. The click's settle bound is restated: a review load's history worker gets 1 s after the abort, so a click can settle about 1 s after its 14 s deadline.
  • A click's saved outcome is always checked before answering a stop, so an outcome another coordinator saved meanwhile is the answer even after a timeout and this click's own abort (6a6da0f).
  • The runner-action trust gate keeps a trust read's own timeout through shutdown, and still returns an outcome saved meanwhile; tests for a click aborted before its replay and for output past maxBuffer while the pipes drain (c3a5b3d).
  • Merge preparation and publish admission: a trust read that timed out, or that gh answered, before shutdown is saved or recorded as failed with that answer, not as the shutdown (tests in ebf520e).
  • Two review lessons added to AGENTS.md and follow-up Keep the rebase conflict resolver's trust-read timeout over a later stop #174 linked (ec135ba).
  • Merged main again at d4fe387 (Keep input made before the first review load, and explain a refused plan import #172, browser files only; no conflicts and no interaction with this PR) as 126b07f.
  • A merge click's answer never offers an action from its pre-command GitHub readiness: if a queue poll ended the attempt while the server reloaded the review, it asks for a refresh (0056e48, e967af3; regression test).

Follow-ups (not in this PR)

  • The rebase conflict resolver's trust read: runner/rebase.ts reports a pending cancel, shutdown or rebase deadline in place of the resolver's own timeout. Unchanged here (F5/F6 code); filed as Keep the rebase conflict resolver's trust-read timeout over a later stop #174.
  • The issue board's trust toggle, planning issue reads, and the already-fixed check's timeout blocker keep their current behaviour (documented).

Tests

New: test/merge-stop-reasons.test.ts, test/merge-queue-poll.test.ts, test/merge-status-cache.test.ts. Changed: test/runner-start.test.ts, test/pre-merge.test.ts and test/runner-publishing.test.ts, plus a comment in test/gh-env.test.ts. Each fix was break-checked: reverting it makes its test fail. The timing-dependent tests assert the interleaving they rely on, so under heavy load they fail rather than pass on a different order.

Validation (head 91da232, base d4fe387)

126b07f merges #172, which changes only browser files (web/public/*, test/browser/*) that this PR does not touch. Since ec135ba, the only server or runner change is MergeCoordinator.statusAfterMerge (0056e48, e967af3), which no Docker test exercises; the Docker results below are from ec135ba.

Gate State
Type check (tsc --noEmit) Pass
Full suite without the 7 Docker files 2095/2095 pass on 91da232
CI test Pass on 91da232
Mergeable Yes
Docker files, one at a time (on ec135ba) agent-gate 30/30, agent-network 21/21, agent-planning 1/1, agent-question 2/2 (1 skipped), runner-workspace 3/3, agent-container 151/151 (1 skipped). agent-supervisor 44/45: the one failure ("Could not confirm that allocationId is unused") passed when rerun alone.
Docker gate overall Pass, with the one agent-supervisor flake noted above. A first agent-container run reported "Failed to establish ownership of task container" during cleanup. The dev machine's Docker then held about 6,700 leftover test-fixture volumes and 1,000 containers from killed runs. After removing those (test runner labels only), agent-container passed cleanly.

Review record

Round Reviewer Base / head Result
1–64 Independent agent (fresh subagent each round, full diff) 22e76a8 / pre-ce44ba0 tree Findings fixed each round; round 64 clean on the tree committed as ce44ba0
Copilot 1 copilot-pull-request-reviewer 22e76a8 / ce44ba0 2 findings: parallel-stage coverage (fixed with tests); Closes #48 (now "Part of #48")
65–79 Independent agent 22e76a8 / ce44ba0 + fixes Found the unhandled rejection (66), test setups not matching production (65, 66, 72), tests that did not assert their interleaving (72, 73, 75, 78), the already-fixed check's own gh failure surfacing after an abort (70), and wording. All fixed and break-checked.
80–82 Independent agent 99e58a9 / e3e9ea1 → 2790321 Merge with #170: settle bound (80), test inspections running review loads (80), doc gaps (81), queue snapshot read before the load (82). All fixed.
83 Independent agent 99e58a9 / 6d1ddde Clean
Copilot 2 copilot-pull-request-reviewer 99e58a9 / 6d1ddde 1 finding: the replay check was skipped after a timeout plus this click's own abort, so a saved outcome answered 503. Fixed in 6a6da0f with a test.
84 Independent agent 99e58a9 / 6a6da0f 1 low finding: the new test didn't assert that the timeout came first. Fixed in 200012c.
85 Independent agent 99e58a9 / 200012c Clean
Copilot 3 copilot-pull-request-reviewer 99e58a9 / 200012c 1 inline finding: untested pre-aborted replay. 2 summary-only findings: the runner trust gate let shutdown replace an earlier timeout; maxBuffer during the pipe drain was untested. All fixed with tests in c3a5b3d.
86 Independent agent 99e58a9 / c3a5b3d Clean
Copilot 4 copilot-pull-request-reviewer 99e58a9 / c3a5b3d 1 finding: merge preparation and publish admission lacked stop-reason tests. Added in ebf520e (preparation layer and publish through the server's trust read), each break-checked.
87 Independent agent 99e58a9 / ebf520e Doc punctuation and coverage wording only; fixed in 21578cc.
88 Independent agent 99e58a9 / 21578cc Clean
Copilot 5 copilot-pull-request-reviewer 99e58a9 / 21578cc 0 open findings; summary named Docker validation and the review-lesson audit as incomplete. Audit added (ec135ba); Docker re-run recorded under Validation.
89 Independent agent 99e58a9 / ec135ba No code issues; PR-body audit gaps (stale line reference, unnamed Copilot round 3 finding, rounds 80/81 and 1–64). Fixed in the PR body.
90 Independent agent 99e58a9 / ec135ba No code issues; two audit notes (a more direct rule citation; Copilot's audit concern unclassified). Fixed in the PR body.
91 Independent agent 99e58a9 / ec135ba No code issues; audit gaps (rounds 87, 89–90 unclassified; Tests list missing two files). Fixed in the PR body.
92 Independent agent 99e58a9 / ec135ba No code issues; PR-body structure (one-off list holding covered items; record row 65–79; change summary stopping at 8e49642). Fixed in the PR body.
93 Independent agent 99e58a9 / ec135ba No code issues; PR-body wording (Copilot 5's Docker concern unclassified; a non-review item in the audit; unnumbered Copilot rows). Fixed in the PR body.
94 Independent agent 99e58a9 / ec135ba Clean
Copilot 6 copilot-pull-request-reviewer 99e58a9 / ec135ba 0 open findings; summary noted the Docker gate was not yet clean. Resolved by the rerun recorded under Validation.
95 Independent agent d4fe387 / 126b07f No code issues; #172 does not interact. PR-body note: record the second merge of main and this round. Fixed in the PR body.
96 Independent agent d4fe387 / 126b07f Clean
Copilot 7 copilot-pull-request-reviewer d4fe387 / 126b07f 1 finding: the merge answer could offer a retry from pre-command readiness after a poll ended the attempt. Fixed in 0056e48 with a regression test that failed before.
97 Independent agent d4fe387 / 0056e48 No correctness issues; blocker wording on the storeless path. Fixed in e967af3.
98 Independent agent d4fe387 / e967af3 Clean; one doc label overclaimed a test. Fixed in 91da232.
99 Independent agent d4fe387 / 91da232 Clean
Copilot 8 copilot-pull-request-reviewer d4fe387 / 91da232 0 open findings; Copilot 7's finding marked resolved.

Review-lesson audit

Rounds 1–64 ran before the PR was opened, on the uncommitted tree. Their findings were of the same kinds as the classes below (stop-reason precedence, saved versus resendable outcomes, test setups, doc and comment claims) and are covered by the same rules; they are not listed one by one.

New rules in this branch (AGENTS.md, ec135ba):

  • "Attach a rejection handler to a promise when it is created whenever anything is awaited before the promise is consumed" (Async jobs and polling). Captures round 66: the already-fixed check's handler was attached only after the other inspection stages settled, so an early rejection was unhandled.
  • "A race regression must assert that the interleaving it names actually happened" (Required race regressions). Captures rounds 72, 73, 75, 78 and 84: tests that could pass on a different order under load. (Round 72 also had a setup finding, classified below.) The existing rule "A validation fixture for a summary-only concern must assert the disputed intermediate representation …" covers only summary-only concerns.

Covered by existing rules:

  • A later abort replacing a gateway's, coordinator's or server's own stop reason (the Investigate summary-only merge lifecycle concerns #48 defects; Copilot 3's runner trust gate): AGENTS.md "Preserve the original timeout, cancellation, and shutdown reason through every layer".
  • A timeout before admission saved as a refusal; Copilot 2's saved outcome skipped after a timeout plus own abort: "look up its saved outcome before any other guard … Only a passing, nothing-applied outcome … stays resendable".
  • An abort after gh's exit replacing GitHub's answer; maxBuffer during the pipe drain: race "An abort error fires, then subprocess close arrives later", plus the stop-reason rule.
  • A failed queue inspection answering with a superseded attempt: "Never apply a background response without proving it is still current".
  • The merge answer showing pre-command readiness (the Investigate summary-only merge lifecycle concerns #48 defect, and Copilot 7's ended-attempt case): "Treat a successful external command as the transition it actually performed", and "Once an irreversible external command succeeds … keep repeat controls disabled".
  • After the merge with Run review history work in an abortable worker thread #170: the queue snapshot read before the new await (round 82), and the settle bound changing (round 80): "When synchronous work moves behind an await … list every await it adds", and "Count the subprocess shutdown grace periods … inside that overall deadline". The re-review follows "A parent merge, rebase, restack or base change invalidates prior review".
  • Test setups that did not match production (fake error types, the check's stop timer, waiting for gh before shutdown, test timeouts and timings, round 80's test inspections running review loads): "A test's setup must leave the state the production path would".
  • A test's database read racing shutdown: "must wait for observable state before acting".
  • Weak or vacuous assertions, the cache test's resolution order, missing tests (Copilots 1, 3 (the pre-aborted replay) and 4): "Every reproduced race requires a failing-before and passing-after regression", and the claim rule in Review readiness.
  • Inaccurate doc and comment claims (timing bounds, shutdown order, what is saved, test-coverage labels, round 81's doc gaps): "Treat every behavioural claim … as something to verify".
  • Rounds 87, 89–93, 95, 97 and 98, wording and audit-text findings only (punctuation, coverage wording, gaps and structure in this section, the Tests list and the change summary): "Treat every behavioural claim … as something to verify", and "Treat review-lesson extraction as a merge gate".
  • Copilot 5's and 6's summaries (21578cc, ec135ba), which named incomplete gates: the review-lesson audit is covered by "Treat review-lesson extraction as a merge gate" (this section is that audit); Docker validation by "the required gate must eventually pass or remain explicitly blocked" (see Validation).
  • The Docker hang during validation: "record the observed failure separately from product failures" (infrastructure, not product).
  • The rebase conflict resolver's timeout: "Reproduce summary-only review concerns or turn them into a concrete follow-up issue"; filed as Keep the rebase conflict resolver's trust-read timeout over a later stop #174.

One-off:

  • Round 70, the already-fixed check's own gh failure after an abort kept as gh's answer: specific to a check that turns failures into blockers; recorded in the #alreadyFixed comment.
  • Older doc errors (the planning 502 is saved) and "Closes Investigate summary-only merge lifecycle concerns #48": single corrections with no general lesson.

🤖 Generated with Claude Code

Reproduces the summary-only merge-lifecycle concerns from PR #46 and
fixes the six server-side defects:

- Gateway reads keep their own first timeout and gh's own answer
  instead of a later caller abort (github/merge.ts, github/issues.ts).
- The coordinator and readIssueAccess keep a timeout or gh answer that
  came before their own abort (runner/merge.ts, web/server.ts).
- A timeout before admission answers a resendable 503 instead of being
  saved as the click's refusal (RemoteTimeout in github/timeout.ts).
- An abort after gh's exit no longer replaces GitHub's answer
  (github/run-with-input.ts), and a refusal stays definite.
- A failed queue inspection no longer reports a superseded attempt.
- The merge click's answer reports the attempt, not pre-command
  readiness (MergeCoordinator.statusAfterMerge).

Cache invalidation was found correct; test/merge-status-cache.test.ts
is the evidence. The two browser polling defects go to a separate PR
stacked on F6b.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI 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.

🟡 Changes recommended

It would prematurely close #48 and leaves a changed parallel-inspection race without regression coverage.

2 open findings
What changed in this PR

Preserves GitHub responses and original timeout reasons throughout the server-side merge lifecycle.

Changes:

  • Introduces typed remote timeouts and preserves completed gh outcomes.
  • Corrects merge replay, polling, caching, and post-submission status handling.
  • Adds lifecycle race regressions and updates implementation documentation.
File Description
web/​server.ts Maps trust timeouts to resendable responses.
runner/​merge.ts Preserves merge reasons and current attempt status.
runner/​lifecycle.ts Documents upstream timeout semantics.
github/​timeout.ts Adds remote-timeout classification.
github/​run-with-input.ts Preserves process outcomes observed before aborts.
github/​merge.ts Propagates timeout and GitHub outcomes correctly.
github/​issues.ts Applies typed issue-read deadlines.
github/​already-fixed.ts Clarifies deadline behavior.
test/​runner-start.test.ts Covers resendable trust-read timeouts.
test/​merge-stop-reasons.test.ts Tests stop-reason precedence.
test/​merge-status-cache.test.ts Verifies cache invalidation races.
test/​merge-queue-poll.test.ts Tests polling and response ordering.
docs/​implementation/​runner-lifecycle.md Corrects action replay documentation.
docs/​implementation/​merge-queue.md Records issue #48 findings and behavior.
docs/​implementation/​guarded-merge.md Updates timeout and shutdown sequencing.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread github/merge.ts Outdated
Comment thread docs/implementation/merge-queue.md Outdated
mchwang and others added 5 commits October 13, 2026 18:52
…#48)

Answers Copilot's review on #171:

- Regression tests for the parallel merge-state inspection stages: a
  stage's gh exit stops the others, and the inspection waits for every
  stage and reports that exit, including when its deadline fires while
  a stopped rule read settles.
- The tests exposed an older bug: the already-fixed check's rejection
  handler was attached only after the other stages settled, so a check
  that settled first raised a process-wide unhandled rejection. The
  handler is now attached when the check starts.
- After a caller's abort, the check's own earlier gh failure stays an
  unknown blocker that the inspection discards for the abort, instead of
  being saved as the click's refusal with raw gh text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolves runner/merge.ts #replay (keeps #170's async load, shutdown abort
and tracked replays, plus #48's aborted-click answer and the click's own
signal on its fresh read) and web/server.ts (#170's async loadReview with
#48's statusAfterMerge). Adapts #48's new tests to the async review
service; one test now waits for the click's gh call before shutdown, as
production does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ctions

Since #170 a click can be in a review load when its deadline fires, and
the load's history worker gets 1 s before it is terminated, so a click
can settle about 1 s or a little more after its deadline, not 0.75 s.
The merge-queue poll tests read the recorded snapshot from the Store
instead of running a review load inside every mocked inspection.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since #170 the review load awaits a worker; reading the queue snapshot
before it could pair an older queue state with a newer attempt status.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI 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.

🟡 Changes recommended

An aborted timeout path can skip a concurrently saved outcome and incorrectly return a resendable 503.

1 open finding
2 resolved since last review

🧠 Review effort: Balanced

Comment thread runner/merge.ts Outdated
mchwang and others added 2 commits October 13, 2026 21:18
Copilot review on #171: when a read's own timeout was followed by this
coordinator's abort, the #merge catch skipped the replay check, so an
outcome another coordinator had saved under the same click was answered
as a resendable 503. The replay is now always checked; for an aborted
click it reads nothing from GitHub. Only recording a new refusal stays
limited to gh's answers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI 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.

🟡 Changes recommended

A later shutdown can still replace an earlier runner trust-read timeout, and two new safety claims lack required regressions.

1 open finding
1 resolved since last review
Previously missed (2)

In code that hasn't changed since last review

Medium severity Preserve read timeout before shutdown handling

web/​server.ts:350

Handle the timeout before the shutdown guard. If the issue read times out and shutdown starts while gh is still settling, stopping/signal.aborted is true here, so the earlier RemoteTimeout is replaced with ShuttingDownError and the concurrent saved-outcome lookup is skipped. Move this branch above the shutdown check and add the corresponding controlled interleaving regression; timedOut(error) already proves the read's timeout won.

Low severity Test max-buffer failure during post-exit pipe draining

github/​run-with-input.ts:29

The changed exit-order behavior also claims that output-limit failures arriving during post-exit pipe draining still win, but the existing max-buffer test only overflows while the child is running. Add a regression where the child exits, an inherited writer exceeds maxBuffer during the pipe grace period, and the promise rejects with the limit error; this verifies that the new exited guard remains limited to timeout/abort handling.

🧠 Review effort: Balanced

Comment thread runner/merge.ts Outdated
…48)

Copilot's third review on #171:

- The runner-action trust gate checked for shutdown before the timeout
  branch, so a trust read whose own timeout fired just before shutdown
  answered "shutting down" and skipped an outcome saved meanwhile. The
  timeout branch now runs first; nothing is admitted either way.
- Regression tests for paths that were traced but untested: a click
  aborted before its replay answers the saved success with a refresh
  blocker, without loading the review or reading GitHub; and output
  past maxBuffer while the pipes drain after gh exited still fails the
  command.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI 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.

🟡 Changes recommended

Pre-merge and publishing authorization paths still lack the mandatory controlled stop-reason regressions.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

Comment thread web/server.ts
mchwang and others added 2 commits October 13, 2026 22:03
Copilot's fourth review on #171: the shared trust-read change also
reaches merge preparation and publish admission, which had no tests.

- Merge preparation: a trust read that timed out, or that gh answered,
  before shutdown settles and saves the preparation as failed with that
  answer, not "Server shutdown."
- Publishing: the same read during publish admission is recorded as
  failed with that answer, not as stopped.

Both run against the error the server's readIssueAccess builds, and
fail if the server, the preparation or the publisher reports the abort
instead. The runner-start shutdown test now shows non-admission through
a distinct saved answer rather than a database read racing shutdown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI 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.

🔵 Needs a closer look

It changes irreversible merge and shutdown race handling, while Docker validation and the required review-lesson audit remain incomplete.

0 open findings

1 resolved since last review

🧠 Review effort: Balanced

- Attach a promise's rejection handler when it is created whenever
  anything is awaited before it is consumed (the already-fixed check's
  unhandled rejection).
- A race regression must assert the interleaving it names before using
  the outcome as evidence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI 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.

🔵 Needs a closer look

The cross-layer lifecycle changes are concurrency-sensitive, and the required Docker validation remains unclean.

0 open findings

🧠 Review effort: Balanced

Copilot AI 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.

🔵 Needs a closer look

Shared cancellation and durable merge behavior need final human review, with a status-response correction outstanding and exact-head CI reported pending.

1 open finding

🧠 Review effort: Balanced

Comment thread runner/merge.ts Outdated
mchwang and others added 3 commits October 14, 2026 10:49
Copilot review on #171 (126b07f): if a queue poll ended the attempt
while the server reloaded the review after a successful enqueue, the
merge answer combined the ended attempt with GitHub readiness read
before the command and could report ready with a retry action. The
answer now offers no action in that case and asks for a refresh, so a
retry is offered only from a fresh GitHub read. Regression test added;
it fails on the previous code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI 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.

🔵 Needs a closer look

Cross-layer cancellation and durable merge outcomes warrant human confirmation, with exact-head CI still reported pending.

0 open findings

1 resolved since last review

🧠 Review effort: Balanced

@mchwang
mchwang marked this pull request as ready for review October 14, 2026 19:01
@mchwang
mchwang merged commit 49f03db into main Oct 14, 2026
2 checks passed
@mchwang
mchwang deleted the feat/48-merge-lifecycle-concerns branch October 14, 2026 21:43
mchwang added a commit that referenced this pull request Oct 14, 2026
Resolves runner/pre-merge.ts and test/pre-merge.test.ts against #170's
async review loads:

- #pushRewrite takes a ReviewView; the refresh after a moved branch uses
  #170's #refresh signature, rechecking the push binding before it
  records the refreshed pair.
- The review reload after a successful push now awaits a history
  worker, so the push binding is checked again after it: a review edit
  during that reload is refused rather than adopted, and no command
  check runs under stale approvals (new regression; it fails without
  the check).
- F6a's tests read the review asynchronously, use reviewState() in
  synchronous hooks, and count the push's authorization in #170's
  final-load tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mchwang added a commit that referenced this pull request Oct 15, 2026
Conflicts resolved: github/merge.ts keeps F6b's #alreadyFixed(target)
with #171's early rejection handler; runner/merge.ts and the tests keep
both sides' imports and fixture options; web/server.ts keeps #170's
async loadReview with F6b's mergeView.

Ported F6b to #170's async review loads:
- MergeJobCoordinator.start is async. A saved click still replays
  first; then the review loads under the request's signal (a load
  stopped by shutdown decides nothing: 503); after it, a resend of the
  same click replays and shutdown refuses, and the token check, every
  admission check and beginMergeJob run in one turn.
- The job's own loads await, and the required-check wait lets
  MergeCoordinator.status load under the job's signal.
- Tests await the review service and the job's admission; one wait
  limit is raised for the worker loads, and two fixtures order their
  steps on observed state rather than fixed delays.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Investigate summary-only merge lifecycle concerns

2 participants