Skip to content

F6a: push the rewritten head behind a durable marker - #153

Draft
mchwang wants to merge 26 commits into
mainfrom
codex/f6a-rewritten-head-push
Draft

mchwang wants to merge 26 commits into
mainfrom
codex/f6a-rewritten-head-push

Conversation

@mchwang

@mchwang mchwang commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Summary

F6a of #22: prepare-merge now pushes the rewritten head to the task's pull request.

  • When: after a rebase whose refreshed review has no blocker, before the cmd: checks run. A rewrite that made an approval stale stays local until a person reviews it.
  • What it may overwrite: exactly the PR head the rebase rewrote. GitBranchPusher.push gains expected: the lease is pinned to that commit, and any other tip refuses with nothing pushed, even a commit codeboost made. Production wires expected: from; MergeCoordinator.remotePair now reports the task PR's branch.
  • Durable marker (schema v18): tasks.push_in_progress is written before the first external write and cleared only after the outcome is known (push read back, refusal, or one branch read). While it exists, merge admission, runner admission, status changes, reassignment, rebases and another push refuse, and merge status names it. A cancel waits for it and closes the task when it clears.
  • Settling an unknown outcome: one branch read, bounded by 30 s and the preparation deadline; only shutdown aborts it. If it cannot run, the marker is settled by the next preparation, by a tracked job started under the verified runner lock at startup, or by that job when a cancel arrives while no preparation runs (and again on a repeated cancel). A task it closes has its PRs closed.
  • GitHub's PR API lag: after a push or refusal, preparation waits (at most 30 s plus one inspection, within the deadline) until the PR reports a new head. The pushed head continues only if the task, review and snapshot are exactly what the push left; any other head is reviewed through the rewrite lineage.
  • Trust: refreshed before the claim and revalidated immediately before the write, together with the task state version, review version, snapshot and cancel state.

F6b (next, stacked on this) adds the required-check wait, the already-fixed rerun and the one-click merge handoff. F6c adds common CI.

Validation

Exact pair: base 22e76a8bc90b59c8407bb78bee1c229b249b4936 (main), head 749a977c04b626b7c253310732f6c361ae16348c (current head; main merged in 143b5f6).

  • current head, independent full-diff review: clean on 22e76a8..749a977 (post-merge round 8; 11 focused files, 787 of 787 tests passed; the new regression fails with its fix removed)
  • intermediate checkpoint 57b7ccb: independent review clean (post-merge round 7); CI and real-Docker passed; Copilot round 5 returned 0 open findings
  • intermediate checkpoint f3bc3e1: independent review clean (post-merge round 3); CI and real-Docker passed
  • intermediate checkpoint 93af176, independent full-diff review: clean on 22e76a8..93af176 (post-merge round 2), including the merge integration with Size the plan import request limit from the parser bound (#146) #158, Close out Lane G status records (#145) #154 and Read an oversized body before refusing it, and close on early refusals (#157) #160; 11 focused files, 774 of 775 tests passed, the one failure a known load-sensitive F5 deadline test that passed alone; 22 of 23 mutation probes killed (the survivor guards a case where continuing is safe)
  • intermediate checkpoint 143b5f6 (the merge; 93af176 changes one doc line): full non-Docker suite (CI's command), local: 74 files, 2,047 of 2,048 passed; the failure (refreshes a pull request head that moves during final authorization, a 15 s timeout at load average ~120) passed alone. Browser suite: 134 of 137 passed under load; the 3 failures passed on rerun. npm run typecheck passed
  • current head, CI and real-Docker: pending
  • intermediate checkpoint 93af176: CI and real-Docker passed

Earlier validated head 9e1f695 (base ae9fc3d):

  • earlier head, full non-Docker suite (CI's command), local and run alone: 74 files, 2,028 passed; npm run typecheck and git diff --check passed
  • current head, independent full-diff review: clean on ae9fc3d..9e1f695; 6 focused files, 341 tests passed; 7 mutation probes all caught
  • current head, CI: push and pull-request runs passed; each ran 74 non-Docker files with 2,028 tests, 137 browser tests, and type checking
  • current head, remote full/real-Docker gate: 81 files, 2,282 passed, 1 skipped
  • local load note for this round: two overlapping full-suite runs on 58e63e0 timed out in 10 and 20 tests under a host load average of 150–200; the deadline tests that failed by assertion passed alone, and the suite passed when run alone

Earlier validated head 87dd3bf:

  • full non-Docker suite (CI's command), local: 74 files, 2,025 passed; npm run typecheck and git diff --check passed
  • independent full-diff review: clean on ae9fc3d..87dd3bf; 8 focused files, 527 tests passed in the review
  • every new regression was shown to fail before its fix, or to fail under a mutation that removes the guarded behaviour
  • local load note: several full-suite runs on intermediate heads hit 15–30 s timeouts in review.test.ts and pre-merge.test.ts under a host load average near 200; each affected file passed alone and the full suite passed on rerun. These are recorded as infrastructure, not product, failures.
  • exact-head CI: push and pull-request runs passed; each ran 74 non-Docker files with 2,025 tests, 137 browser tests, and type checking
  • exact-head remote full/real-Docker gate: 81 files, 2,279 passed, 1 skipped

Review ledger

Round Head Findings Remediation
independent 1 12e96ba cancel aborted settlement reads; startup settlement did not close PRs; PR API lag; wrong comment; no wiring tests a63e5cf
independent 2 a63e5cf base move during lag ended the wait; fake read ignored its signal b1eaaae
independent 3 b1eaaae wait bound stated as 30 s; really 30 s plus one inspection 9e98ad7
independent 4 9e98ad7 stranded marker on cancel; refused path re-inspected without waiting; stale doc bullet 50ba765
independent 5 50ba765 no actionable defects; noted an unbound failure and a non-production test setup e5ce306
independent 6 e5ce306 repeated cancel could not retry settlement; push-boundary checks untested 496d4b3
independent 7 496d4b3 refusal traced from the wrong head after an earlier landed push; failure after a push reported stale d0639c9
independent 8 d0639c9 state-version check is load-bearing (plan revisions) and untested; settlement and wait bounds untested; doc overclaimed "not pushed" 6ab346d
independent 9 6ab346d cancel during next preparation's settlement, attempt admission, shutdown reads untested; non-production cancel setup 0be7462
independent 10 0be7462 failed-push binding untested 7cc5477
independent 11 7cc5477 failed push bound after an awaited read adopted a concurrent plan revision cedd594
independent 12 cedd594 same on success and refusal paths a0ecaf0
independent 13 a0ecaf0 mid-push plan revision adopted before checks; unreachable publish guard fc26627
independent 14 fc26627 stale pusher doc comments 365cda0
independent 15 365cda0 admission guards and busy settlement skip untested 87dd3bf
independent 16 87dd3bf no actionable defects —
Copilot 1 87dd3bf claim version read after the trust refresh; guard ran before an awaited gap instead of as the pusher's finalizer; settlement read did not reserve Git's settlement time 58e63e0; three failing-before regressions; threads answered and resolved
independent 17 58e63e0 Copilot fixes correct; docs claimed a read after a deadline-ended push, but none starts 9e1f695
independent 18 9e1f695 no actionable defects —
Copilot 2 9e1f695 0 open findings; the three round-1 findings resolved; summary notes the base advance must be integrated, re-reviewed and revalidated before merge already recorded under Gate status as a merge prerequisite
merge main 143b5f6 merged 22e76a8 (#158, #154, #160) with no conflicts —
independent post-merge 1 143b5f6 architecture.md still named schema version 17 93af176
independent post-merge 2 93af176 no actionable defects —
Copilot 3 93af176 the push-settlement branch read could wait without bound on a process that escaped Git's group holding its output f3bc3e1 (reads return failed after the bounded drain; the marker stays)
independent post-merge 3 f3bc3e1 no actionable defects (noted, not changed: git push itself still waits fail-closed for an escaped holder, per the process-group contract; no realistic trigger with HTTPS and the gh credential helper) —
Copilot 4 f3bc3e1 allowUnsettledReturn also abandoned processes still in Git's group, which nothing owns 97b6305 (new boundEscapedOutput: bounds only an escaped output holder; group members waited for)
independent post-merge 4 97b6305 three comments still claimed every branch read is bounded 230e5b6
independent post-merge 5 230e5b6 reserve comment wrong about which calls are bounded; one inspection's settle time understated 33450c8
independent post-merge 6 33450c8 cat-file reads also waited without bound on an escaped holder 57b7ccb (bounded like ls-remote; stale rebaser doc line corrected)
independent post-merge 7 57b7ccb no actionable defects —
Copilot 4 (previously missed) f3bc3e1 when a collaborator moved the branch between the push and its read-back, preparation failed instead of reviewing that head 749a977 (a moved settlement is reviewed, as after a refusal)
Copilot 5 57b7ccb 0 open findings —
independent post-merge 8 749a977 no actionable defects —

Declined: none. One noted out-of-scope item: F5's rebase-cleanup failure binding (merged in #149) reads the task state after an awaited cleanup, the pattern fixed here for pushes; recorded as a follow-up.

Review-lesson audit

  • lifecycle, cancellation, shutdown, deadline and race findings (rounds 1, 2, 4, 6, 9, 11–13, 15, Copilot 1, Copilot 3, Copilot 4 including its previously missed finding, post-merge 6): covered by Async jobs and polling and Required race regressions in AGENTS.md
  • untested-claim and fail-before findings (rounds 2, 8–10, 15): covered by Review readiness ("give each claim a test that fails if the claim is false") and the Design system and plan format #1 rule of tests
  • non-production test setups (rounds 5, 9): covered by Review readiness ("A test's setup must leave the state the production path would")
  • documentation claims (rounds 3, 4, 8, 14, 17, post-merge 1, 4, 5): covered by Review readiness (treat every behavioural claim as something to verify)
  • no new rule is needed: each finding is an instance of existing guidance

Gate status

  • independent exact-head review: clean on base 22e76a8bc90b59c8407bb78bee1c229b249b4936 / head 749a977c04b626b7c253310732f6c361ae16348c (earlier: clean on ae9fc3d / 9e1f695)
  • CI and real-Docker: pending on 749a977; passed on 57b7ccb, f3bc3e1, 93af176 and 9e1f695
  • Copilot review: rounds 1–5 addressed (round 5 on 57b7ccb: 0 open findings; round 4's previously missed finding fixed in 749a977); round 6 requested only after exact-head CI passes
  • base: main (22e76a8) merged in at 143b5f6; reviewed and revalidated on the new pair
  • merge: not authorized

🤖 Generated with Claude Code

mchwang and others added 16 commits October 9, 2026 09:18
prepare-merge now pushes a rebased head whose review is still clear over
exactly the PR head it rewrote, before running command checks. A schema
v18 marker (tasks.push_in_progress) is written before the first external
write and cleared only after a read of the branch settles the outcome;
while it exists, merge, runner admission, status changes, rebases and
other pushes refuse. Preparation and a tracked startup job settle an
interrupted push. GitBranchPusher gains an exact expected-head lease and
a branch read; remotePair reports the task PR's branch.

Part of #22 (F6a).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Push settlement reads now abort only on shutdown, so a cancel can no
longer strand the marker; the in-run read is bounded by the remaining
preparation deadline. Startup settlement runs under the verified runner
lock and closes the PRs of a task it closed. After a push, preparation
waits (bounded) until GitHub's PR API reports the pushed head, and a move
seen then is reviewed from the pushed head. Adds regressions for each,
plus production push wiring and server startup wiring tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GitHub can report a moved base while its PR API still shows the pre-push
head. The visibility wait now ends only when the head changes, so the
review is never refreshed back onto a head the branch no longer holds.
The test remote's branch read honours its abort signal, so a cancel that
stopped push settlement would fail a test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The visibility wait starts no inspection after 30 seconds, so it lasts at
most 30 seconds plus one inspection. Say so in the code and the docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A cancel requested while no preparation runs and a push marker remains
now starts the tracked settlement job, so the task and its PRs close
without a restart. After a refused push, preparation waits for GitHub's
PR API to report the branch's new head before reviewing it, as it does
after a push. Renames settleAtStartup to settlePendingPush and corrects
the architecture doc's not-built list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A preparation that fails waiting for GitHub to show the pushed head now
binds its failure to the post-push task state, so it is not reported as
stale at once. The startup-settlement server test now builds the state a
crash leaves (a push marker plus a pending cancel) instead of having the
fake job cancel the task itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Repeating a cancel that waits on an unsettled push now reads the branch
again instead of refusing, so the task can close without a restart.
Adds push-boundary regressions for a pending cancel and a changed
snapshot, and documents the retry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a push or a refusal, a moved PR head is now reviewed by tracing
history from the reviewed head and its rewrite lineage, nearest first,
so an earlier rewrite codeboost already pushed is reviewed against its
own base instead of the original one. Any failure after the push or
refusal is now bound to the post-push task state.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds regressions for a plan revision applied at the push boundary (only
the task state version changes, so that check is load-bearing), for the
visibility wait ending at the preparation deadline, for the settlement
read's deadline bound, and for shutdown aborting a startup settlement.
States in the docs that a killed push may still land later, harmlessly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds regressions for a cancel during the next preparation's push
settlement, attempt admission and reassignment refusing while a push is
in flight, and shutdown starting no branch read. The push-boundary
cancel test now cancels through the coordinator while the push reads the
remote, as production does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A failed push's settlement read can take up to 30 seconds. The failure
now adopts the task state version that clearing the marker produced only
when nothing else wrote during the read; otherwise it stays bound to the
claim's version, so a plan revision applied meanwhile marks it stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Success, refusal and settlement now share one rule: the version clearing
the push marker produced counts only when nothing else wrote since the
claim; otherwise a later failure stays bound to the claim's version, so
a plan revision applied while the push was in flight marks it stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After the pushed head appears, preparation now requires the task state,
review version and snapshot to be exactly what the push left before any
cmd: check runs, so a plan revision or review edit applied during the
push fails it instead of being adopted. The publish guard returns to its
rebase-only form: publishing cannot overlap a push by status, which the
docs now state.

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.

🟡 Changes recommended

Push-boundary version races and an under-reserved settlement deadline can violate the durable lifecycle guarantees.

3 open findings
What changed in this PR

Adds F6a pre-merge publishing: rewritten heads are pushed to the task PR under an exact lease and durable recovery marker.

Changes:

  • Adds schema-v18 push lifecycle persistence and admission guards.
  • Pushes reviewed rewrites before command checks, with startup/cancellation recovery.
  • Adds extensive lifecycle, race, migration, and production-wiring coverage.
File Description
web/​server.ts Settles interrupted pushes during startup and cancellation.
runner/​store.ts Adds schema-v18 push markers and lifecycle guards.
runner/​pre-merge.ts Coordinates guarded pushes, settlement, and visibility polling.
runner/​production.ts Connects preparation to the production branch pusher.
runner/​merge.ts Exposes the task branch and blocks unsettled pushes.
runner/​branch-push.ts Adds exact expected-head leases and branch reads.
test/​pre-merge.test.ts Covers push lifecycle and race scenarios.
test/​runner-lifecycle-store.test.ts Tests marker persistence and admission guards.
test/​runner-publishing.test.ts Tests startup and cancellation settlement.
test/​runner-production.test.ts Tests production push wiring.
test/​runner-branch-push.test.ts Tests exact leases and branch reads.
test/​merge-task-pr.test.ts Tests branch resolution and merge blockers.
test/​store.test.ts Updates migration expectations.
test/​publish.test.ts Updates schema-version expectations.
test/​planning-drafts.test.ts Updates migration expectations.
docs/​implementation/​pre-merge-rebase.md Documents the durable push lifecycle.
docs/​architecture.md Records F6a architecture and remaining work.

🧠 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 runner/pre-merge.ts
Comment thread runner/production.ts Outdated
Comment thread runner/pre-merge.ts
…serve settlement

- The push claim uses the task state version captured before the
  asynchronous trust refresh, so a reassignment during it refuses.
- Production returns the last guard as the pusher's synchronous
  finalizer, closing the gap between validation and the Git push.
- A settlement read keeps the process-settlement reserve inside its
  limit and does not start with less than that reserve left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When the preparation's own deadline ends the push, less than Git's
settlement reserve is left, so no branch read starts and the marker
stays, as for an unreadable branch. The docs, the code comment and the
test now say so.

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 durable external-push lifecycle is high risk, and the documented base advancement requires integration, re-review, and revalidation.

0 open findings

3 resolved since last review

🧠 Review effort: Balanced

mchwang and others added 2 commits October 12, 2026 09:24
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mchwang
mchwang requested a balanced review from Copilot October 12, 2026 18:58

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

Git process settlement can exceed the documented hard deadline and indefinitely block recovery or shutdown.

1 open finding

🧠 Review effort: Balanced

Comment thread runner/pre-merge.ts
Production readBranch ran Git without allowUnsettledReturn, so a process
that left Git's group while holding its output could hold the push
settlement read, startup recovery, a repeated cancel or shutdown without
bound. A read changes nothing, so it now returns failed after the
bounded drain, and the push marker stays.

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

Branch reads can orphan subprocesses, and collaborator moves detected during push read-back are not refreshed for review.

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

In code that hasn't changed since last review

Medium severity Handle moved outcome after push read-back before rethrowing

runner/​pre-merge.ts:336

The settlement result is discarded and the original push error is always rethrown. In production, if git push succeeds and a collaborator advances the branch before GitBranchPusher's mandatory read-back, remote.push throws for the mismatched head; this read then returns moved and clears the marker, but preparation reports failed without refreshing that collaborator head. The afterPush fixture does not reproduce the pusher's read-back, so its review-required regression misses this path. Route a settled moved outcome through the rewrite-lineage refresh path (with an accurate reason) instead of unconditionally rethrowing.

🧠 Review effort: Balanced

Comment thread runner/branch-push.ts Outdated
mchwang and others added 4 commits October 12, 2026 14:12
allowUnsettledReturn also stopped waiting for processes still in Git's
group, which a read has no durable ownership of. A new process-group
option, boundEscapedOutput, bounds only the wait for a process outside
the group that holds the output; group members are still waited for,
fail-closed. Branch reads use it. Tests cover both cases.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cat-file only reads, so a process that left Git's group but holds its
output no longer holds fetchCommits or the push's head check: the call
fails after the pipe drain. The reserve comment now lists the Git calls
that still wait (fetch, update-ref, push). The pre-merge-rebase doc no
longer says no production request starts the rebaser.

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 pushed-head visibility wait can miss cancellation or shutdown signals during listener registration.

0 open findings

1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Handle abort race after checking but before listener registration

runner/​pre-merge.ts:290

An abort can occur after the check on line 281 but before this listener is attached. AbortSignal does not replay that event, so cancellation or shutdown can be ignored until the backoff timer expires (up to 4 seconds). Check signal.aborted immediately after registering the listener, as the command-check path already does, and add the corresponding race regression.

🧠 Review effort: Balanced

…sh outcome

When the push's read-back fails because a collaborator moved the branch,
the settlement read now returns moved and preparation reviews that head
(review-required, attribution and approvals refreshed), as after a
refusal, instead of failing with the read-back error.

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.

2 participants