Skip to content

Wire production foreign-conflict resolver for rebases - #140

Merged
mchwang merged 21 commits into
mainfrom
codex/issue22-rebase-fix-adapter
Oct 7, 2026
Merged

mchwang merged 21 commits into
mainfrom
codex/issue22-rebase-fix-adapter

Conversation

@mchwang

@mchwang mchwang commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Implements issue #22 lanes F3-F4 by assembling the foreign-conflict resolver inside the production rebaser and its recovery boundary.

  • records a unique conflict child plus distinct storage/network allocations before clone or Docker work
  • exposes only bounded, exact conflict paths to the lane-D task storage and fix adapter
  • audits the complete result and copies back only approved conflict leaves
  • retains durable ownership until subprocess, agent, storage, network, and staging cleanup settle
  • recovers interrupted conflict children before clearing their parent rebase
  • fails closed on live recovered numeric process groups instead of risking PGID-reuse signals

F5-F6 remain follow-up work in issue #22: add the production admission that refreshes the current base/head and invokes this rebaser, then push the rewritten head, run plan checks, wait for GitHub checks, and hand the validated pair to the merge coordinator. No production request starts a rebase in this branch; exposing the raw engine before that coordinator exists would bypass those required guards.

Validation

Original implementation baseline: 6dd57c884a932d2c25aaf9e487751bc77cdcee09.
Current integration base: d463cdab8b988ebca52326cb3555816882adc4a2 (origin/main, after PR #139).
Validated/pushed head: fb8aa50f409321818d33a632b11cf3aa0a23086f.

Current exact-head evidence:

  • npm run typecheck — passed
  • git diff --check — passed
  • post-integration overlap/lifecycle suite — 9 files, 292 passed
  • post-integration full non-Docker CI-equivalent suite — 71 files, 1,876 passed
  • independent exact-head full-diff review — CLEAN; 15 focused files, 584 passed; typecheck and diff check passed
  • recovered-process crash-window regression — failed before (startup cleared the owner), passed after
  • post-fix recovery/lifecycle suite — 7 files, 255 passed
  • post-fix full non-Docker suite — 71 files, 1,877 passed
  • post-fix independent exact-head review — CLEAN; 5 focused files, 159 passed; typecheck and diff check passed
  • crash-before-spawn recovery regression — failed before (invalid-marker dead end), passed after
  • latest recovery/lifecycle suite — 7 files, 255 passed
  • latest full non-Docker suite — 71 files, 1,877 passed
  • latest independent exact-head review — CLEAN; 5 focused files, 159 passed; typecheck and diff check passed
  • release-ancestor and adapter wall-step regressions — both failed before, passed after
  • current recovery/adapter/lifecycle suite — 10 files, 290 passed
  • current full non-Docker suite — 71 files, 1,882 passed
  • configured-timeout regression — failed before (the carried budget kept setup alive past 1 second), passed after
  • final recovery/adapter/lifecycle suite — 10 files, 292 passed
  • final full non-Docker suite — 71 files, 1,884 passed
  • final independent exact-head full-diff review — CLEAN; 6 focused files, 133 passed; typecheck and diff check passed
  • container-launch wall-step and retained-cleanup-cause regressions — failed before, passed after
  • latest recovery/adapter/lifecycle suite — 10 files, 293 passed
  • latest full non-Docker suite — 71 files, 1,885 passed
  • real-Docker carried-budget launch regression — 1 passed, 150 skipped
  • latest independent exact-head full-diff review — CLEAN; 6 focused files, 134 passed; typecheck and diff check passed
  • orchestration-boundary documentation validation — production suite, 22 passed; typecheck and diff check passed
  • final independent exact-head full-diff review — CLEAN; 5 focused files, 233 passed; typecheck and diff check passed
  • exact-head GitHub CI — both test jobs passed; real-Docker passed in 17m05s
  • focused deadline/process/recovery lifecycle suite — 4 files, 79 passed at c544ac6
  • exact-head affected process/clone/storage/rebase/push/repository suite — 7 files, 145 passed
  • independent focused non-Docker suite — 8 files, 239 passed at ff084f4; exact-head process-group rereview, 22 passed
  • exact-head conflict lifecycle suite — 3 files, 115 passed; independent review suite — 6 files, 174 passed
  • exact-head monotonic deadline regression — 22 passed for both backward and forward 60-second wall-clock steps
  • exact-head conflict import/lifecycle/recovery suite — 6 files, 206 passed
  • exact-head production real-Docker conflict import regression — 1 passed, 147 skipped
  • independent exact-head full-diff suite — 7 files, 219 passed; typecheck and diff check passed
  • exact-head escaped-process/recovery lifecycle suite — 8 files, 221 passed
  • final independent exact-head full-diff suite — 10 files, 304 passed; typecheck and diff check passed
  • exact-head real-Docker container/profile suite — 149 passed, 1 skipped; final targeted canonical cleanup suite — 3 passed
  • latest independent exact-head review — Docker regressions 2 passed; 9 non-Docker files / 286 tests passed; typecheck and diff check passed
  • pre-integration exact-head GitHub CI — both test jobs passed; real-Docker passed in 19m53s at ccacc56

PR #139 advanced main after the previous review round. It was merged into this branch without conflicts. The five overlapping files (runner/production.ts, runner/store.ts, test/cli.test.ts, test/runner-lifecycle-store.test.ts, and web/cli.ts) were reread in full and exercised by the post-integration validation above. The independent exact-head review traced the combined trust and rebase lifecycles and returned CLEAN. No finding was declined. Fresh exact-head GitHub CI and Copilot review are recorded below when complete.

Earlier checkpoint d68f0f2 evidence:

  • complete real-Docker suite — 7 files, 245 passed, 2 skipped
  • full parallel suite — 1,800 passed; 7 unrelated host-contention failures
  • exact affected parallel files rerun in isolation — 3 files, 33 passed

The earlier Docker failures were reproduced as daemon/resource contamination: deterministic stale test resources and one Docker Desktop daemon outage. After exact test-namespace cleanup and daemon relaunch, the complete Docker suite passed. Exact-head CI is the final validation authority.

Review loop

Fourteen independent review rounds were run across the evolving full diff. Valid findings were fixed with regressions, including exact child/allocation ownership, bounded path export, nested conflict parents, cleanup retention, process settlement, canonical resource paths, recovery validation, and PGID reuse.

The fourteenth independent review found one TOCTOU: delayed or post-exit negative-PGID signals could target a reused numeric group. The branch now disables delayed signals synchronously on leader exit, observes post-exit groups without signalling, and makes host restart recovery fail closed while a numeric group remains live. Its follow-up rereview returned CLEAN.

Copilot round 1 found two valid instances where a synchronous lifecycle hook could block past a deadline before the timer callback ran. Head c544ac6 now arms the tracked-process deadline before the durable starting() write, passes only its remaining budget, settles without spawning when exhausted, and synchronously rechecks the supervisor deadline at the attach spawn boundary. Both findings have failing-before/passing-after regressions; both threads were replied to and resolved. No finding was declined.

Copilot round 2 confirmed those two fixes and found one valid high-severity ownership gap: the shared process primitive could return EGROUPALIVE to callers without durable recovery ownership. Head 05aabc7 now waits fail-closed by default for both in-group descendants and escaped inherited pipe holders. Bounded unsettled returns require an explicit opt-in and are limited to the durable tracked-Docker and rebase lifecycles. Regressions cover both default wait paths across the former drain boundaries. The finding was fixed, replied to, and resolved; no finding was declined.

Copilot round 3 confirmed that ownership fix and found three valid lifecycle gaps: one threaded release-transition guard plus summary-only settlement-reserve and pre-aborted-admission concerns. Head 2fcf3e5 refuses to clear a conflict child while any process owner remains, derives a 17-second reserve from the actual default stop/drain/pipe bounds plus durable-write margin, and checks cancellation immediately before its first child claim. Each concern has a regression that asserts the disputed intermediate durable state or budget. The thread was replied to/resolved and the summary-only evidence was posted; no finding was declined.

Copilot round 4 had no inline findings and confirmed the release fix, but raised one valid summary-only concern: repeated Date.now() calculations could stretch or prematurely expire the resolver after a clock step. Head 42f2543 converts the incoming wall deadline once, uses one monotonic boundary for every stage, and reconstructs wall time only at the child invocation boundary. The regression covers both backward and forward 60-second jumps. No finding was declined.

The post-fix independent exact-head rereview returned CLEAN after tracing all deadline consumers and verifying both clock directions; the exact regression file passed 22 tests plus typecheck and diff check.

Copilot round 5 found two valid production-path gaps: the resolver dirtied its clone before the clean-only storage seeder, and a configured root with symlinked ancestors produced a noncanonical profile cleanup path. Head aaef036 now allocates from the untouched base clone, imports the exact bounded conflict snapshot through stdin into only the writable work volume, recovers interrupted import containers, and canonicalizes the newly created owner-only staging directory. The allocator-order and macOS /var alias regressions failed on the prior behavior; the production real-Docker import/export/tree-check regression passes. The inline finding was replied to and resolved, and summary evidence was posted. No finding was declined.

The independent exact-head review of aaef036 found one valid follow-up deadline-admission gap: synchronous base64/JSON serialization could consume a stale caller budget before the first Docker probe. Head 238afc9 establishes the importer's monotonic deadline before encoding and rechecks it immediately before resource work. Its regression advances the clock during encoding and proves that neither a Docker call nor the durable lifecycle starting() hook occurs after expiry. The follow-up independent full-diff rereview returned CLEAN after 7 files / 219 tests plus typecheck and diff check. No finding was declined.

Copilot round 6 found two valid exact-lifecycle gaps: startup could clear an escaped pipe-holder owner based only on workspace open files, and a settlement-callback failure could replace the original timeout/cancellation/nonzero-exit reason. Head 0aae7b5 keeps an escaped process fail-closed as unsettled until the operator explicitly releases that exact attempt under the runner lock, and aggregates settlement failures without discarding the original cause or code. The inline finding was replied to and resolved, and summary evidence was posted. No finding was declined.

The independent exact-head review of 0aae7b5 found one valid recovery-ordering gap: startup checked an unsettled rebase only after it could terminate an interrupted preparation or settle an earlier rebase owner. Head d541259 validates all rebase markers and conflict-child identities, then rejects any unsettled marker before every process, Docker, filesystem, or durable-state action. The failing-before regression observed preparation group 4242 being terminated; the passing regression proves its persisted owner and all recovery dependencies remain untouched. The follow-up independent full-diff rereview returned CLEAN after 10 files / 304 tests plus typecheck and diff check. No finding was declined.

Copilot round 7 returned no inline findings and two valid summary-only concerns: freshly created profile staging directories were registered only after fallible setup, and the persisted RebaseMarker documentation omitted F4 conflict fields. Head ccacc56 records each raw mkdtempSync path immediately, replaces only that ownership slot after canonicalization, and uses the same list for immediate cleanup, retry cleanup, and unreleased-resource reporting. Deterministic input-chmod and auth-canonicalization failures leaked on the prior head and now leave the cleanup root empty. The lifecycle document now includes resolvedConflicts and the exact active conflict child shape and ordering. The follow-up independent full-diff rereview returned CLEAN after the exact Docker regressions plus 9 non-Docker files / 286 tests, typecheck, and diff check. No finding was declined.

Copilot round 8 reviewed exact head ccacc5672b58e4e8fd5cdc5c60c5d42479cd245b and returned Findings: None. It confirmed the round-7 findings as resolved and raised no new inline or summary issue.

After PR #139 moved the base, the branch integrated current main at exact head 19e03d844757171c2958ebf3dd8132d0f880d0a3. A new independent full-diff review against base d463cdab8b988ebca52326cb3555816882adc4a2 returned CLEAN after rereading every changed production function, including all five overlap files, and passing 584 focused non-Docker tests plus typecheck and diff check. No finding was declined.

Copilot round 9 found one valid high-severity crash window: startup could clear an exact process owner after its group disappeared but before an escaped descendant was durably marked unsettled. Head cccabbd now converts every recovered exact owner to durable unsettled ownership before any Docker, filesystem, or rebase cleanup; a still-live exact group is first handed to the identity-bound process controller, and a controller that cannot safely signal it remains fail-closed. Explicit release remains required and still refuses visible workspace users. The regression failed on the prior head by completing recovery and clearing ownership, then passed after the fix. No finding was declined.

The independent exact-head rereview of cccabbd returned CLEAN after tracing disappeared groups, live-group termination refusal, multiple markers, explicit release, and every cleanup boundary. Its focused suite passed 159 tests plus typecheck and diff check. No finding was declined.

Copilot round 10 confirmed the round-9 fix and found one valid adjacent crash state: a durable spawning owner was rejected as malformed before it could enter explicit release, leaving no supported recovery path. Head a68d371 accepts only the known spawning transient (unknown strings still fail closed), converts it to unsettled before cleanup, and requires the same explicit release. The regression failed on the prior head with the invalid-marker dead end and now passes. No finding was declined.

The independent exact-head rereview of a68d371 returned CLEAN after tracing durable spawning, malformed owners, live and disappeared exact groups, CAS failures, multiple markers, explicit release, and every cleanup boundary. Its focused suite passed 159 tests plus typecheck and diff check. No finding was declined.

Copilot round 11 confirmed the round-10 fix and found two valid issues: manual release validated only the attempt leaf and could follow a replaced rebases ancestor, while the real conflict-adapter path re-read wall time during asynchronous network/profile/supervisor setup despite the monotonic-deadline contract. Head 053dd7d revalidates the full owner-only, no-symlink chain for both rebase and preparation release, and carries the resolver's existing monotonic budget through adapter admission, vendor-network creation, profile validation, launch, and supervision. The release-ancestor and +60-second wall-step regressions both failed on the prior head and now pass, including the real adapter setup and supervisor paths. No finding was declined.

The independent review of 053dd7d found one valid follow-up: a supplied monotonic budget replaced, rather than composed with, a shorter configured adapter timeout. Head f25f7da now caps the carried budget with the configured timeout in both Claude and Codex paths without re-reading wall time. A hanging real network-setup regression failed on the prior head by remaining unsettled after the one-second timeout and now settles as timed out. The focused suite passes 292 tests, the full non-Docker suite passes 1,884 tests, and typecheck plus diff check pass. No finding was declined.

The independent exact-head rereview of f25f7da returned CLEAN after rereading all 37 changed files and tracing the composed deadline through both adapter paths, setup, supervision, output capture, abort, settlement, and cleanup ownership. Its focused suite passed 133 tests plus typecheck and diff check. No finding was declined.

Copilot round 12 confirmed the ancestry fix and found two valid issues: the trusted profile identity did not retain the carried monotonic budget for real container validation/launch, and a primary resolver failure caused a later cleanup failure to be omitted when ownership was retained. Head 162ffcb carries the budget through every profile timeout and aggregates the primary plus all cleanup failures while preserving the primary cause. The cleanup-cause regression failed on the prior head and now passes; the real-Docker regression advances wall time by ten minutes after profile creation and still launches within the unchanged monotonic budget. The inline finding was replied to and resolved. No finding was declined.

The independent exact-head rereview of 162ffcb returned CLEAN after rereading the complete diff and tracing both fixes through every profile create/validate/start/run path and the retained-resource transition. Its focused suite passed 134 tests plus typecheck and diff check. No finding was declined.

Copilot round 13 confirmed both round-12 fixes and raised one reachability concern: no production request currently invokes the assembled rebaser. The implementation is intentionally the F3-F4 trusted engine and recovery boundary; issue #22 F5-F6 owns the missing admission, current base/head refresh, checks, push, and merge handoff. Exposing the raw rebaser from this branch would bypass those required guards. The finding was therefore declined as already-tracked later-lane scope, and the implementation document, production comment, and PR summary were corrected to state that boundary explicitly rather than claiming a reachable production action.

The independent exact-head rereview of fb8aa50 returned CLEAN after checking the complete diff and the corrected scope boundary. It confirmed that only startup recovery can instantiate the private rebaser today, that no raw action bypass exists, and that F5-F6 explicitly owns every prerequisite needed before live admission. Its focused suite passed 233 tests plus typecheck and diff check.

Copilot round 14 reviewed exact head fb8aa503e0a41b8eee48f90294f6b1197cea1419 and returned 0 open findings. It marked the round-13 reachability concern resolved after the scope and safety boundary were made explicit. No new inline or summary issue was raised.

Review lessons

  • Empty/unreleased allocations, cleanup ownership, client settlement, and retained resources are covered by the existing owned-resource and process-settlement rules in AGENTS.md.
  • .gitmodules/nested-repository handling is covered by the existing trust-root and nested-repository rules.
  • Prompt and export bounds are covered by the existing stdin/transport-budget rules.
  • Shared deadline reserves, including synchronous lifecycle hooks, are covered by the existing overall multi-stage deadline and hook-arming rules.
  • Pre-aborted conflict admission and release-state guards are covered by the existing cancellation-before-first-write and source-state-precondition rules.
  • PGID reuse and live-group restart behavior are covered by the exact-identity/fail-closed lifecycle rules; this branch implements the stronger no-post-exit-signal interpretation.
  • Returning while descendants or inherited pipe holders remain is covered by the existing ownership-until-settlement rule; untracked callers now wait fail-closed.
  • Clean-before-allocation state is covered by the existing rule that a regression fixture must leave the state the production path would.
  • Canonical cleanup roots are covered by the existing exact-location validation rule for durable or discovered paths.
  • Import serialization admission is covered by the existing overall multi-stage deadline rule; a stage owns its deadline before synchronous user-sized work.
  • Escaped pipe-holder ownership and explicit release are covered by the existing rule to retain cleanup handles and fail closed until removal is confirmed.
  • Settlement error aggregation is covered by the existing rule to preserve the original timeout, cancellation, and shutdown reason through every layer.
  • Recovery-wide blocker preflight is covered by the existing source-state-precondition rule; a refused startup must not first mutate earlier ownership.
  • Immediate staging ownership is covered by the existing rule that cleanup handles become owned state at acquisition and remain recorded until release is confirmed.
  • The complete conflict-child marker shape is documented in this branch; the omission was documentation drift, not a new policy category.
  • Recovered exact-group ambiguity is covered by the existing ownership-until-settlement and fail-closed recovery rules; this branch now requires explicit release whenever restart cannot prove the durable settled transition occurred.
  • Crash-before-spawn ownership is covered by the existing rule that an already-aborted or synchronous lifecycle edge must still durably settle; spawning is now a recognized recoverable transient rather than malformed data.
  • Manual-release ancestor replacement is covered by the existing exact-location/no-link validation rule for durable paths; both release commands now revalidate their complete owner-only chain.
  • Downstream wall-clock rechecks are covered by the existing overall multi-stage deadline rule; the resolver now carries one monotonic budget through every adapter setup and execution stage.
  • Composing a carried deadline with every shorter stage timeout is covered by the existing overall multi-stage deadline rule; neither bound may replace or extend the other.
  • Real container launch must use the same carried budget, and retained ownership must preserve cleanup diagnostics; both are covered by the existing multi-stage deadline and original-reason preservation rules.
  • Production reachability is a deferred F5-F6 orchestration concern already tracked in issue Add pre-merge rebase and head-bound command checks #22; this branch deliberately exposes no raw rebase action that could bypass admission and head refresh.
  • Nested missing conflict parents are a one-off Git conflict semantic, documented and regression-tested here.

No additional AGENTS.md rule is needed.

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.

Copilot review overview

🟡 Changes recommended

Synchronous lifecycle hooks can consume the deadline and still permit tracked subprocesses to spawn afterward.

Review effort: Balanced
Findings: 2 Medium severity

Open (2)
What changed in this PR

Wires the production sandboxed foreign-conflict resolver into pre-merge rebases with durable resource ownership and restart recovery.

Changes:

  • Adds bounded conflict snapshot, resolution, audit, and copyback.
  • Tracks Git/Docker process groups and conflict-child resources durably.
  • Extends recovery and regression coverage for unsettled resources and PGID reuse.
File Description
runner/​rebase-conflict.ts Implements the production conflict resolver.
runner/​rebase.ts Integrates resolver deadlines and retained-resource handling.
runner/​store.ts Persists conflict-child ownership.
runner/​recovery.ts Recovers interrupted rebases and child resources.
runner/​production.ts Wires the resolver into production setup.
git/​clone.ts Adds tracked cloning and safe partial-clone retention.
agents/​tracked-docker.ts Adds durable subprocess lifecycle tracking.
agents/​process-group.ts Prevents post-exit PGID signalling.
agents/​network/​network.ts Tracks network Docker clients.
agents/​container/​storage.ts Adds tracked storage operations and exact-path export.
agents/​container/​run.ts Propagates process lifecycle tracking.
agents/​container/​profile.ts Supports durable cleanup roots.
agents/​container/​changes.ts Adds dirty-conflict tree validation.
agents/​adapters/​types.ts Extends adapter request lifecycle fields.
agents/​adapters/​supervisor.ts Tracks attach-client ownership and settlement.
agents/​adapters/​setup.ts Propagates tracking through adapter setup.
agents/​adapters/​claude.ts Passes lifecycle tracking to the supervisor.
docs/​implementation/​pre-merge-rebase.md Documents the production resolver lifecycle.
test/​tracked-process-unsettled.test.ts Tests unsettled outcome preservation.
test/​runner-recovery.test.ts Tests conflict-child and process recovery.
test/​runner-rebase.test.ts Tests retained parent workspaces.
test/​runner-rebase-conflict.test.ts Tests resolver security and cleanup behavior.
test/​runner-lifecycle-store.test.ts Tests durable conflict ownership.
test/​agent-supervisor.test.ts Tests serialized Docker-client ownership.
test/​agent-process-group.test.ts Tests post-exit signal suppression.
test/​agent-container.test.ts Tests cleanup roots and conflict exports.
test/​agent-clone-async.test.ts Tests partial-clone retention.

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

Comment thread agents/adapters/supervisor.ts Outdated
Comment thread agents/tracked-docker.ts

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.

Copilot review overview

🟡 Changes recommended

Shared process-group handling can release preparation ownership while descendants remain alive.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (2)

Comment thread agents/process-group.ts

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.

Copilot review overview

🟡 Changes recommended

Conflict ownership transitions and settlement reserves do not fully enforce the intended lifecycle guarantees.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Resolved since last review (1)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Tracked process timeout reserve is shorter than the actual worst case

runner/​rebase-conflict.ts:24

This reserve assumes a 1-second SIGTERM grace, but runTrackedProcess does not set graceMs, so its clone, storage, network, and staging calls inherit runInProcessGroup's 5-second default. Combined with the 10-second group drain and 1-second pipe drain, a timed-out child can need about 16 seconds and overrun this boundary by roughly 3 seconds. Either force a 1-second grace for these tracked calls or increase the reserve (and the matching documentation) to cover the actual worst case.

Medium severity Aborted requests can record conflict children before cancellation is checked

runner/​rebase-conflict.ts:198

An already-aborted request still records the conflict child here; the first cancellation check is only after the image lookup at line 256. That makes a cancelled no-op perform durable writes and cleanup, and a cleanup failure can leave a recovery marker even though no child resource should have been admitted. Check the signal immediately before generating/claiming these identities, while retaining the later check for cancellation during image setup.

Comment thread runner/store.ts
@mchwang

mchwang commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Copilot round 3 summary-only findings were also reproduced and fixed in 2fcf3e5:

  • The conflict child reserve now derives from the shared default process settlement bound: 5 s SIGTERM grace + 10 s group drain + 1 s pipe drain + 1 s durable-write margin = 17 s. A regression asserts that intermediate budget relationship, and the implementation documentation now states it.
  • The resolver now checks an already-aborted signal immediately before generating and durably claiming child identities. A regression asserts no conflict marker, process owner, image lookup, or dependency event is created.

Focused evidence on this head before push: 3 files, 115 passed; typecheck and diff check passed. No round-3 finding was declined.

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.

Copilot review overview

🔵 Needs a closer look

Resolver budgeting uses wall-clock time after receiving a monotonic deadline, allowing clock adjustments to overrun the cleanup boundary.

Review effort: Balanced
Findings: None

Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Use one monotonic deadline for all operation budget calculations

runner/​rebase-conflict.ts:236

This reconstructs every stage budget from Date.now(), so a backward wall-clock adjustment after the rebaser converted its monotonic deadline can extend bounded() and the uncancelled storage/staging cleanup past the parent work deadline. That contradicts the monotonic whole-operation boundary and can consume the reserve intended for parent cleanup. Capture one performance.now() deadline when the resolver starts and use it for operationBudget, bounded, and copy-back checks; keep the wall-clock value only where the invocation contract requires it.

@mchwang

mchwang commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Copilot round 4 had no inline findings, but its summary-only monotonic-deadline concern reproduced and is fixed in f97de8e. The resolver now converts the incoming wall-clock transport deadline once at admission, uses one performance.now() boundary for every operation budget, bounded wait, and copy-back check, and reconstructs wall time only for the child invocation contract. The regression moves Date.now() backward by 60 seconds between stages and asserts neither the next helper budget nor the child invocation budget grows. Focused evidence before push: 2 files, 82 passed; typecheck and diff check passed. The concern was accepted; none was declined.

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.

Copilot review overview

🟡 Changes recommended

The production path dirties a clone before clean-only storage allocation, and noncanonical configured roots can also prevent profile creation.

Review effort: Balanced
Findings: 1 High severity

Open (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Noncanonical cleanup root causes production conflict profile creation failure

runner/​rebase-conflict.ts:292

cleanupRoot is passed using the configured path spelling, but createContainerProfile requires it to equal realpathSync(cleanupRoot). parseRunnerConfig accepts any absolute runner root and ownerOnlyDirectory does not canonicalize symlinked ancestors, so valid roots such as macOS /var/... (whose real path is /private/var/...) make every production conflict fail during profile creation. Canonicalize the newly created staging directory before passing it here, or canonicalize/enforce the runner root at configuration time.

Comment thread runner/rebase-conflict.ts Outdated
@mchwang

mchwang commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Copilot round 5 summary concern fixed in aaef036: the resolver canonicalizes the newly created owner-only staging directory before passing it as cleanupRoot, so configured roots with symlinked ancestors (including macOS /var → /private/var) no longer fail profile creation. The regression exercises that actual alias and asserts the profile receives the canonical path. Validation at this head before push: typecheck and diff check passed; focused lifecycle/recovery suite 205/205; production real-Docker conflict import regression 1/1.

@mchwang

mchwang commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Independent exact-head follow-up on 238afc9d2b493012cf38192810a7b0b09ed8ef40 returned CLEAN after a complete current-diff rereview. The prior finding is covered by a deterministic regression that advances the monotonic clock during snapshot encoding and asserts no Docker call or lifecycle starting() hook occurs. Reviewer validation: 7 files, 219 passed; typecheck and diff check passed.

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.

Copilot review overview

🔵 Needs a closer look

Cross-cutting subprocess, Docker, storage, and crash-recovery semantics require final human review despite comprehensive regressions.

Review effort: Balanced
Findings: None

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

Recovery can clear an exact process owner during the crash window before an escaped descendant is durably marked unsettled.

1 open finding

🧠 Review effort: Balanced

Comment thread runner/recovery.ts Outdated

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 crash with a durable spawning owner leaves the runner unable to recover or use the manual release workflow.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

Comment thread runner/recovery.ts Outdated

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

Downstream wall-clock deadline rechecks can prematurely expire conflict resolution, and manual release does not validate workspace ancestors.

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

In code that hasn't changed since last review

Medium severity Preserve monotonic deadline across profile setup and invocation

runner/​rebase-conflict.ts:299

The child is not actually governed by one monotonic deadline after this handoff. createContainerProfile and startProfileInvocation both recompute invocation.deadline - Date.now() after asynchronous setup, so a forward wall-clock step during network/profile creation rejects the child early even though this resolver's monotonic deadline still has time remaining. Carry the already-created monotonic budget through those stages (and add a clock-step regression that uses the real adapter path) rather than re-reading the transported wall deadline.

🧠 Review effort: Balanced

Comment thread runner/recovery.ts Outdated

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

Real profile launch still re-reads wall time despite a carried monotonic budget, and retained cleanup errors can be discarded.

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

In code that hasn't changed since last review

Medium severity Preserve cleanup errors when retaining ownership

runner/​rebase-conflict.ts:364

When conflict resolution fails and subsequent storage cleanup also fails, retainOwnership is true but this cause expression keeps only primary and drops every cleanup error. That hides the failure which explains why resources were retained. Aggregate primary with cleanup while preserving the primary cause.

🧠 Review effort: Balanced

Comment thread agents/container/profile.ts

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

The production assembly constructs the resolver but never exposes or invokes the rebase execution path.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

Comment thread runner/production.ts

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 substantially changes subprocess, Docker, storage, deadline, and crash-recovery lifecycles across many security-sensitive boundaries.

0 open findings

1 resolved since last review

🧠 Review effort: Balanced

@mchwang
mchwang merged commit cb69e50 into main Oct 7, 2026
4 checks passed
@mchwang
mchwang deleted the codex/issue22-rebase-fix-adapter branch October 7, 2026 17:28
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