Skip to content

codex, scripts, docs: carry the PTY pre-reap observer as a recorded patch on the pinned release - #93

Merged
novelKR merged 2 commits into
mainfrom
claude/project-thread-af6ntg
Oct 11, 2026
Merged

novelKR merged 2 commits into
mainfrom
claude/project-thread-af6ntg

Conversation

@novelKR

@novelKR novelKR commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Issue

Scope: the Codex PTY hook of option 3 in the owner's CSRG-U5 decision (2026-10-11): U5 observes PTY executions after Codex's reap, and the pre-reap hook is prepared separately. On 2026-10-11 the owner chose to carry the hook as a patch file on the official pin, not as a fork. The owner also asked for a written policy for fixes that a Codex pin makes unavoidable, with a case study of earlier Codex adapter conflicts. This PR does not close an issue. Tracking: #76 and novelKR/DevGuard#14.

Item Value
Base main at 0c21a7f (#92's merge)
Head a75d030: 374a3fc adds the patch, its scripts and records; a75d030 adds the policy and case study (documentation only)
Pins Codex gitlink 6b9826e3 on openai/codex and DevGuard source 6e7e065, both unchanged

Summary

What this guarantees. Every build that goes through scripts/validate-upstream.py, which is every CI job and the local gates, compiles Codex rust-v0.154.0 plus exactly one recorded patch to codex-utils-pty, and nothing else. The pin check and the validation refuse a checkout whose files are not the recorded patched tree.

The patch. third_party/codex-patches/0001-utils-pty-pre-reap-observation.patch (SHA-256 6f61579830f311e3c259c0642864ea9eee3c653f12c7e838e97e9b1dd35b1fba, patched tree 1999937194e3a68d982be85679557f0e50fe5408):

  • It adds spawn_pty_process_observed and observe::{ExitObserver, ExitOpportunity, ObservationOutcome, OpportunityExpired}. The child's single waiter sees the exit with waitid(WNOWAIT), gives the observer one opportunity for at most its bound, then reaps through the same child object as before. After the reap it reports Completed, TimedOut, Failed or Unavailable; an acknowledgement after the bound returns OpportunityExpired.
  • Without an observer nothing changes. Platforms other than Unix refuse an observed spawn before creating a process.
  • It adds nine tests to the crate, each run on both Unix PTY paths, and a modification notice to each file it changes.
  • It is the observer of W3 experiment B (DevGuard docs/handoff/2026-09-28-w3-decision-packet.md, section 3.2), ported from rust-v0.159.0-alpha.11 to this release and widened from macOS's fd-preserving path to both Unix paths.
  • No CodeSpace code calls it yet.

How it is applied.

  • scripts/codex_patch.py apply|check|revert reads the pinned commit, each patch file with its SHA-256, and the patched tree from docs/upstream-lock.md. It computes the checkout's tree in a scratch index, untracked files included and ignored ones left out. apply brings a clean checkout of the pinned commit to the patched tree, or confirms it is already there. Any other state fails: another commit, a changed patch file, or a stray change.
  • scripts/validate-upstream.py applies the patches before it fingerprints its inputs. Its report gains codex_patches and inputs.codex_tree.
  • scripts/check-upstream-pin.sh runs codex_patch.py check after the commit check.

Documents, in English and Korean:

  • upstream-lock: the patch record (file, digest, patched tree, capability, reason, provenance, verification, upstream status, removal, pin changes, rollback) and the check commands.
  • upstream-update: the one limited adaptation, and reverting the patches before the submodule moves.
  • operations: apply the patch after every submodule update; Python 3 is now needed.
  • NOTICE: the modification.
  • codex-reuse, upstream-lock, upstream-update and the overview link the policy below. The update procedure reviews patches under it instead of the suspended adaptation rule.

Policy and case study. docs/upstream-patches.md, in English and Korean, is a new implementation guide after the update procedure.

  • When the pinned release blocks needed work, the options are weighed in this order: the public API with the gap handled in an adapter, a release that has the capability through a reviewed pin update, a recorded patch, then a CodeSpace-owned replacement. A patch comes before a replacement because the DevGuard integration alone never justifies moving a mechanism CodeSpace delegates to Codex, such as the PTY, into CodeSpace.
  • The form every patch takes is this PR's mechanism: the official submodule, git format-patch files, a checked patched tree, no fork and no copied crate. The page also covers the record, the upstream feature request, pin changes and approval.
  • Earlier cases: the adapter conflicts from the first pin (W06: in-process Codex apply-patch adapter #25) to this patch, grouped by how they were settled, each linked to the PR or issue that decided it. Until this patch, none changed Codex source or moved the pin.
  • Three open points found while collecting them, none changed here: crates/patch has no Rama resolver guard; the conflict recorded for the patch helper (W09-A: patch preflight without writes #27) predates the root's copy of Codex's [patch] entries (P0 filesystem: PathSandbox over isolated Codex LOCAL_FS #51), so whether it still holds is unverified; no reason is recorded for choosing rust-v0.154.0.

Contract changes

None. No tool schema, error code, status value or ID changes.

For developers: after cloning or updating the submodule, run python3 scripts/codex_patch.py apply before building. While the patch is applied, git status shows third_party/codex as modified.

Tests

  • Verified here (Linux x86_64, Rust 1.97.0):
    • python3 -m unittest discover -s scripts/tests: 65 passed, 10 of them new for codex_patch.py.
    • On the real checkout, codex_patch.py check fails before apply. apply gives tree 19999371…, a second apply changes nothing, and PIN_ONLY=1 ./scripts/check-upstream-pin.sh passes. In a scratch clone, git apply -R restores tree 74ba48a4… (the pinned commit's).
    • python3 scripts/validate-upstream.py pin python format unit-pty clippy-adapters passed on 374a3fc with the patch applied: the PTY adapter's 7 tests ran against the patched crate, and clippy with -D warnings passed for the patch, PTY, file-system and DevGuard adapters.
    • On a75d030: python3 -B scripts/check_docs.py verified 18 paired documents after the five changed pairs were recorded, and the documentation site's 5 tests passed. The site build needs Node 24.21.0 and runs in CI.
  • The patch itself, on the commit it was exported from, which has the same tree:
    • Linux: the crate's 36 tests in each of 30 runs; clippy and rustdoc with -D warnings; clippy for macOS and Windows targets.
    • The owner's Mac: 38 tests in each of 30 runs, and W3 experiment B's cases B1–B13 against DevGuard 6e7e065.
  • Full validation on the owner's Mac (macOS, Rust 1.98.0), on 374a3fc with the patch applied (tree 19999371…):
    • validate-upstream.py all passed every stage it ran. linux-isolation needs Linux and did not run. The integration stage ran 653 tests.
    • macos-fork-race passed: 400 runs, 62,602 children, no failure.
    • macos-core failed once, in one test. In devguard::tests::sessions_opened_while_children_spawn_reach_no_child, one of 40 worker children listed descriptor 10, which is not a socket and which the baseline did not have. The stage passed on main (0c21a7f, without the patch), and the test alone passed 20 times on main and 20 times on 374a3fc. The cause is not established. The worker's spawn marks every descriptor above 2 close-on-exec before exec (crates/runner/src/descriptors.rs), and this PR changes neither that path nor how the PTY crate handles descriptors.
    • The patch was then reverted, which restored tree 74ba48a4….
  • CI:

Security scenarios

  • One reaper stays. The observer never receives a PID and cannot reap. The bound limits how long an exited child stays unreaped. Termination during the window still reaches the process group, which the unreaped child keeps.
  • Integrity: every validation and pin check verifies the patch file's SHA-256 and the patched tree, so a modified patch or a stray change in the submodule fails instead of building.
  • No credential, descriptor or authority handling changes in CodeSpace.

Out of scope

  • Connecting the observer to CodeSpace's PTY adapter (U5's ExitPoint::BeforeReap for PTY): a separate PR after this one merges.
  • Submitting the upstream feature request: asked separately.
  • Resolving the three open points the case study lists: proposed separately.
  • The macos-core failure above, and the descriptor tests' failure messages, which print the child's whole environment: a separate PR.
  • Any pin change, DevGuard release, or change to the installed services.

…atch on the pinned release

The pinned Codex PTY spawn (rust-v0.154.0) reaps its child in its own
waiter, so CodeSpace cannot observe a governed PTY execution's scope
before the reap; CSRG-U5 observes PTY executions after it, the interim
state of the owner's option 3. The owner chose to carry the fix as a
patch file on the official release, not a fork.

- third_party/codex-patches/0001-utils-pty-pre-reap-observation.patch:
  one commit, as git format-patch, on rust-v0.154.0 (6b9826e3). It adds
  codex_utils_pty::spawn_pty_process_observed and observe::{ExitObserver,
  ExitOpportunity, ObservationOutcome, OpportunityExpired}. The single
  waiter sees the exit with waitid(WNOWAIT), gives the observer one
  bounded opportunity, then reaps as before and reports the outcome.
  Without an observer nothing changes. Nine tests in the crate, each on
  both Unix PTY paths.
- scripts/codex_patch.py applies, checks and reverts the patches that
  docs/upstream-lock.md records. It requires the recorded patched tree
  (19999371), computed in a scratch index, so a missing patch or any
  other change in the checkout fails. Its tests cover each state.
- scripts/validate-upstream.py applies the patches before it
  fingerprints its inputs, so CI and local validation build the recorded
  source, and its report records the checkout's tree.
  check-upstream-pin.sh also verifies the patched tree.
- docs/upstream-lock.md records the patch: file and SHA-256, patched
  tree, capability, reason, provenance, verification, upstream status,
  removal, pin changes and rollback. The install and update procedures
  apply it, and revert it before the submodule moves. NOTICE states the
  modification. English and Korean.

The submodule stays on openai/codex at 6b9826e3. No CodeSpace code uses
the observer yet; connecting it to the PTY adapter is a separate change.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The owner asked for a written policy for fixes that a Codex pin makes
unavoidable, following the patch-file choice, and for a case study of
how earlier Codex adapter conflicts were handled, so that later choices
stay consistent.

- docs/upstream-patches.md, in English and Korean: the order of options
  (adapter on the public API, pin update, recorded patch, CodeSpace-owned
  replacement), when a patch is chosen and why it comes before a
  CodeSpace-owned replacement, its form (official submodule,
  format-patch files, checked patched tree, no fork, no copied crate),
  its record, the upstream feature request, pin changes and approval.
- Earlier cases: the adapter conflicts from the first pin to the PTY
  observer, grouped by how they were settled, what they show, and three
  open points found while collecting them.
- The page is an implementation guide after the update procedure, and
  Codex reuse, the pin record, the update procedure and the overview
  link to it. The update procedure reviews patches under this policy
  instead of the suspended adaptation rule.

No code, pin or dependency changes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@novelKR
novelKR marked this pull request as ready for review October 11, 2026 18:29
@novelKR
novelKR merged commit 3d051fa into main Oct 11, 2026
19 checks passed
novelKR pushed a commit to novelKR/DevGuard that referenced this pull request Oct 12, 2026
…n note)

CodeSpace merged novelKR/CodeSpace#95 on 2026-10-12 (main 181a020): the
recorded patch on the pinned Codex (novelKR/CodeSpace#93) now gives a
governed PTY root the same observation before the reap as a governed pipe
root, completing the PTY option the owner chose on 2026-10-11. The CS-RG
plan's dated current-state note said that until a separate Codex change
lands, U5 observes PTY executions only after Codex reaps them. Date the
note 2026-10-12 and say what holds now, English and Korean. The units
ledger is unchanged: U5 stays merged-verified, U6 not started, CS-RG
in-progress with qualification not-run. Korean counterpart reviewed;
planning-milestones-cs-rg hashes recorded.

Co-Authored-By: Claude Opus 5.5 (1M context) <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