Import eleven open upstream PRs, fix what they broke, and bring the README up to date - #6
Conversation
When the review gate is enabled, main() ran the stop-time Codex review
and emitted {"decision":"block"} on any non-ok outcome (no output,
timeout, failure, invalid JSON) without checking whether this
invocation was already a forced retry. Claude Code re-invokes Stop
hooks with stop_hook_active: true after a block decision, so a review
that times out or fails would re-run and re-block on every retry,
until the harness's forced-retry cap force-ends the turn -- worst in
exactly the cases where the gate is least useful.
Fix: skip the review and return cleanly when stop_hook_active is set,
matching the fix the issue reporter suggested. The issue also flagged
codex-companion.mjs's `config.stopReviewGate` branches as worth
auditing for the same problem; traced them and confirmed they're only
config get/set/report code, not the review-blocking logic itself
(that lives solely in stop-review-gate-hook.mjs), so no second file
needs the fix.
Added a regression test that snapshots the fake Codex binary's
appServerStarts counter before and after a stop_hook_active retry,
proving the review subprocess is not spawned a second time and no
"decision":"block" is emitted. Confirmed the test fails against the
pre-fix code (git stash isolation) with the exact stale block
decision the issue describes. Full suite: 92/92 passed, no
regressions.
logNote() only writes to stderr, which isn't surfaced for a successful hook run -- a skipped review looked identical to a passed one. Emit a systemMessage instead (no "decision" key, so it still doesn't block) so the skip is visible right when it matters most, since the whole reason there's a retry turn is that the previous review blocked on something. Per review feedback from @Wintersta7e on the PR.
`task` always mapped `--write` to `workspace-write` and everything else to `read-only`, so a rescue run could never ask for `danger-full-access` even when the work needs it (test tooling that writes outside the repository, or network access). Add an explicit `--sandbox <read-only|workspace-write| danger-full-access>` flag: - the flag wins over `--write`; `--write --sandbox read-only` is rejected - the resolved mode is stored in the job request, so the detached worker and `--resume-last` reuse exactly what the caller asked for - jobs started with a writable sandbox keep the review hints that `--write` jobs already get - older stored requests without a sandbox field keep the old mapping The fake app-server fixture now records the `thread/start` and `thread/resume` params so tests can assert what reached Codex. The rescue command, agent and runtime skill forward `--sandbox` as a runtime flag and never add `danger-full-access` on their own. Closes openai#145 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… a stored request without a sandbox field Measured on Codex CLI 0.153.2: the app-server keeps the sandbox a thread was started with when it is resumed, whichever mode the new request names on thread/resume. Say so in the README instead of the opposite, and keep the resume test to what the plugin forwards. Add a task-worker test that replays a job record written before this change, with no sandbox field in the request, and checks it still starts the thread with the --write mapping. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the pair A rescue request such as `--sandbox read-only fix the failing test` reads as write intent, and the agent that forwards it may still add --write; a hard error there returns nothing to the user. The explicit flag now wins: `--write --sandbox read-only` starts a read-only thread. Say so in the README and the runtime skill, and scope `--sandbox` to /codex:rescue in the README, since the review commands stay read-only. The rescue command carries the same "never add danger-full-access yourself" rule as the agent and the skill. The fake app-server records only the thread params the tests assert. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Measured on Codex CLI 0.153.2: the mode named on thread/resume is applied when the app-server loads the thread again from disk, and ignored while the thread is still live in the app-server. The plugin keeps one shared app-server per session, so inside a session a resumed rescue keeps the sandbox it started with. The README says exactly that. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…hat asks for another sandbox Two findings from the Codex review of this branch. The argument parser recognises flags anywhere in the request, so a task whose text merely mentioned `--sandbox danger-full-access` ran without a sandbox and with the words stripped from the prompt. `task` now takes `--sandbox` only from the leading option block; once the task text has begun the same words stay part of the prompt. Other options keep their existing behaviour. A thread the shared app-server still holds keeps its sandbox on resume, so forwarding a different mode on `thread/resume` produced a turn with broader or narrower access than requested, silently. The job record now carries the sandbox a thread was started with, and `--resume-last` refuses a request whose sandbox differs from it, naming `--fresh` and the matching `--sandbox` as the ways out. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ext --sandbox in the prompt, validate the whole inline value Three findings from the second Codex review of this branch. The shared app-server answers thread/resume with the sandbox the thread actually has (measured on Codex CLI 0.153.2: a live danger-full-access thread answers dangerFullAccess to a read-only request). runAppServerTurn now compares that answer with the requested mode before turn/start and refuses a mismatch, so a resume is checked twice: against the job record before connecting, and against the server's own answer when the record is missing or predates the sandbox field. The fake app-server keeps a sandbox per thread and echoes it the same way. The rescue command, agent and skill told the wrapper to treat any --sandbox as a runtime flag, which would have hoisted one out of the task text and defeated the leading-only parser. They now say: forward the request in the user's order; a leading --sandbox is a flag, one inside the task text stays there as prompt text. extractLeadingSandbox split the inline value at the second "=", so --sandbox=danger-full-access=false passed as danger-full-access. It now splits at the first "=" and validates the whole remainder. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… as the first token The runtime skill said "starts with --sandbox", which would have made the forwarder miss `--model spark --sandbox danger-full-access run tests` or a request with --resume prepended, leaving the tokens in the prompt while it added its default --write. It now matches the command, the agent and the parser: any --sandbox before the task text is the control. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`--sandbox=` and a trailing `--sandbox` produced an empty value that normalized to null and silently fell back to the --write mapping. The flag now needs one of the documented modes whenever it is present. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`/codex:adversarial-review --model gpt-6-astra --effort xhigh` silently does
something other than what it says.
`handleReviewCommand` parses valueOptions ["base","scope","model","cwd"], with
no "effort" - only `handleTask` parses it. `parseArgs` then demotes any
unrecognised long option to a positional, and `handleReviewCommand` builds the
review's focus text with `positionals.join(" ")`. So the two tokens `--effort`
and `xhigh` are concatenated into the prompt handed to the reviewer, the run
uses whatever `model_reasoning_effort` the config holds, and nothing warns.
The run looks like it did what was asked.
That is two separate problems, fixed separately.
1. The review commands now accept `--effort`, normalised through the existing
`normalizeReasoningEffort` and threaded into the `runAppServerTurn` call that
the adversarial path already makes. `runAppServerTurn` already accepts an
`effort` option, so this is plumbing rather than new capability. The usage
line is updated to match, including the `--model` flag it already supported
but did not advertise.
2. `parseArgs` now returns `unknownOptions` alongside `options` and
`positionals`, and `parseCommandInput` warns on stderr for each one.
Behaviour is deliberately unchanged - the token still reaches positionals,
because commands such as adversarial-review take free-form focus text and a
hard error would break a prose word that happens to start with two dashes.
The point is only that the demotion stops being silent. This generalises past
`--effort`: any future flag typo on any command currently ends up pasted into
a prompt with no indication.
Tests: five in tests/args.test.mjs, covering the unknown-option report, the
clean case, the `--` passthrough boundary, `--effort` staying out of the focus
text under the real review config, and quoted focus phrases. Mutation-tested by
reverting the `unknownOptions.push` and confirming the first fails, then
restoring it.
Full suite on Windows goes from 79 passing / 12 failing to 84 passing / 12
failing. Those 12 fail identically on clean main - they are platform tests
(Unix sockets, temp-backed state dirs) and are untouched by this change.
…mmary (openai#757) Two gaps combined to make server-side turn failures illegible in status: 1. errorMessage was only written in the catch path, so a turn that failed with exitStatus != 0 but completed normally stored no reason anywhere reachable from 'status'. 2. The summary took the first line of the pretty-printed error body, which is a bare opening brace. Now: - tracked-jobs.mjs reads execution.errorMessage on the success branch and writes it to both the job file and the index when failed. - codex-companion.mjs task/review paths surface result.error.message as errorMessage and prefer it over rawOutput for the summary. - Summary and Codex error: progress line are passed through shorten() so multi-line JSON bodies cannot reduce to punctuation. - Regressions covered by two new state.test.mjs tests.
Upstream PR openai#565 (mittalpk), merged without conflicts. Claude Code re-invokes the Stop hook with stop_hook_active: true after a "block" decision. Without this guard the gate re-runs the review and re-blocks on every non-ok outcome until the harness's forced-retry cap ends the turn anyway. The skip is surfaced through systemMessage rather than logNote(), which is invisible on a successful hook run. Verified: node --check on the hook, tests/runtime.test.mjs 92/92. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
Upstream PR openai#748 (MeGaNeKoS), merged without conflicts. SessionStart appended to CLAUDE_ENV_FILE unconditionally, so the file grew one duplicate export per session and the shell resolved whichever line came last. appendEnvVar() is replaced by setEnv(), which rewrites the file with at most one export per key and swaps it in via a temp file + rename. Verified: node --check on the hook, tests/runtime.test.mjs and tests/state.test.mjs 100/100. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
Upstream PR openai#746 (taur-us), merged without conflicts. - review/adversarial-review now accept --effort and forward it to the turn, which previously only task could do. - parseArgs() collects unrecognised long options and parseCommandInput() warns about them on stderr. They are still treated as positionals (some commands take free-form text), but a mistyped flag no longer disappears silently into a prompt. Verified: node --check on codex-companion.mjs and lib/args.mjs; the new tests/args.test.mjs plus tests/commands.test.mjs and tests/runtime.test.mjs 105/105. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
Upstream PR openai#742 (alirezarzg), one conflict resolved. task and /codex:rescue take --sandbox read-only|workspace-write| danger-full-access; --write stays as the shorthand for workspace-write. A resume whose recorded starting sandbox cannot be compared is refused rather than trusted, and the sandbox is inferred only from records that carry one. Conflict: the usage block in codex-companion.mjs, against openai#746 merged just before. Both sides are additive, so the resolved line keeps openai#746's --model/ --effort on adversarial-review and adds openai#742's --sandbox to task. Verified: node --check on codex-companion.mjs; tests/args.test.mjs, tests/commands.test.mjs and tests/runtime.test.mjs 113/113. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
Upstream PR openai#727 (fscfede-beep), four conflicts resolved. task and /codex:rescue can resume one specific Codex thread by id instead of only the most recent one; --resume-last, --resume-thread and --fresh are mutually exclusive, and an empty id is rejected. Conflicts: all four are the same additive collision with openai#742 (--sandbox), merged just before -- the rescue argument-hint, the rescue agent's routing bullets, the usage block, and buildTaskRequest()'s parameter list plus the task valueOptions. Each is resolved as the union of both sides. Checked afterwards that the two features compose: --resume-thread flows into runAppServerTurn as resumeThreadId, so openai#742's assertResumedSandbox() guard covers an explicit thread id exactly as it covers --resume-last. Verified: node --check on codex-companion.mjs; tests/args.test.mjs, tests/commands.test.mjs and tests/runtime.test.mjs 118/118. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
Upstream PR openai#729 (fscfede-beep), merged without conflicts. transfer only accepted sessions under ~/.claude/projects, so a relocated CLAUDE_CONFIG_DIR made it unusable. The source is now resolved against the configured Claude root, staged under a collision-free lease so concurrent transfers cannot clobber each other, and staging ancestors that escape the expected root are rejected. Verified: node --check on claude-session-transfer.mjs and codex-companion.mjs; the new tests/claude-session-transfer.test.mjs plus tests/runtime.test.mjs and tests/commands.test.mjs 117/117. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
Upstream PR openai#724 (kilhyeonjun), fourteen conflicts resolved. task and /codex:rescue accept repeatable --read-root <directory>, which opts the run into an OS-enforced Codex permission profile: reads are denied outside the approved roots and Codex's minimal runtime paths, and a runtime without permission profiles fails closed instead of running unscoped. Conflicts: all against openai#742 (--sandbox) and openai#727 (--resume-thread), merged just before. The substantive one is ownership of the sandbox mode. openai#724 derived it from --write (`request.write ? "workspace-write" : "read-only"`), while openai#742 makes --sandbox the input and derives write from it. The resolved code keeps openai#742's direction and layers read roots on top: - buildThreadAccessParams() receives the already-resolved sandbox, so a scoped profile and an explicit --sandbox no longer disagree. - the workspace-coverage check now triggers on any write-capable sandbox, not only on a literal --write. - --read-root with --sandbox danger-full-access is rejected: that mode turns the sandbox off, so no read scope would be enforced. This case did not exist in either PR alone. - in runAppServerTurn(), openai#724's try/catch around thread start/resume wraps openai#742's assertResumedSandbox() rather than replacing it; scopedAccessError() passes a sandbox-mismatch error through untouched. - tests/fake-codex-fixture.mjs recorded thread starts twice after the auto-merge (openai#724's raw params, then openai#742's structured record, silently overwriting it). Both handlers now store `{...message.params, threadId}`, which satisfies both PRs' assertions. Not taken: the version bump to 1.0.11 and its five CHANGELOG sections. Those releases do not exist in this fork, so the plugin stays at 1.0.6 and the entries are folded into an Unreleased section (npm run check-version passes). Verified: node --check on every touched script; full npm test 230/232, with the only two failures the ones already failing on the parent commit (tests/broker-lifecycle.test.mjs, pre-existing). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
The landing page still described upstream's command surface, so several flags this fork supports were undocumented. - A "Commands At A Glance" table lists every slash command with its real flags, plus the accepted --effort values and the new warning for an unrecognised option. - The install snippet points at this fork's marketplace, and says how to get upstream instead (the marketplace name is the same, so only one of the two can be added at a time). - /codex:review and /codex:adversarial-review document --model/--effort (openai#746); /codex:rescue documents --resume-thread (openai#727) and the resume flags' exclusivity; /codex:transfer documents CLAUDE_CONFIG_DIR (openai#729); the review gate section explains the stop_hook_active skip (openai#565). - A "Differences From Upstream" section lists every imported PR with a link, grouped into broker lifecycle and command surface, and states that the plugin version stays at the upstream number. Both review commands' argument-hint now name --model/--effort, which the script already accepted. tests/commands.test.mjs asserted the adversarial hint as one adjacent string ("[--scope ...] [focus ...]"); it now asserts the scope, effort and focus fragments separately, so inserting a flag between them no longer fails a documentation test. Verified: full npm test 230/232, the two failures being the pre-existing tests/broker-lifecycle.test.mjs ones. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
shutdownBrokerSessionLocked() treated every error reply to broker/shutdown
as an identity mismatch, including the broker's BUSY refusal. The refusal is
the normal answer when a client connects between the session-end guard check
and the shutdown request, so SessionEnd exited 1 with "broker rejected
shutdown identity" for a broker that had in fact recognized it and was simply
still in use. It now returns { refused: true, exited: false } and leaves the
process and the persisted record alone, which is what the hook's own
pre-check already does with a refusal; a later session end, or the broker's
idle shutdown, retires it. Every outcome carries `refused` so callers can
discriminate without inspecting the error message.
The two tests that had been failing since the earlier imports are both
consequences of this area, and neither was a stale-assertion-only fix:
- "shutdown closes idle half-open broker clients" (from openai#541)
predates the busy check that openai#623 later added, which
refuses shutdown while ANY other client is connected — deliberately,
because a worker between requests has no in-flight request to detect. The
test's own subject is that shutdown destroys a lingering half-open socket
instead of waiting on it, so it now parks that socket as a peer shutdown
requester, which the broker itself defines as "not work". Same coverage,
current contract. It also awaits "end" rather than "close": an
allowHalfOpen client never closes itself, so the old expectation could not
have been met either way.
- "shutdown request always uses a finite deadline" expected null from
sendBrokerShutdown(), which has returned an outcome object since the
lifecycle merges. It now asserts the shape that matters: a broker that
accepts the connection and then says nothing is not delivered, not
refused, and deliberately NOT unreachable, so teardown cannot reap a
broker that may still be serving someone.
Added "a busy broker refuses shutdown instead of failing it", which covers
the fix directly: an ordinary connected client keeps the broker alive and the
record intact without raising, and the same call retires it once that client
goes away.
Verified: full npm test twice, 233/233 both times (was 230/232).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
|
Pushed One of them turned out to be a real defect rather than a stale test. On the tests:
Added Generated by Claude Code |
Upstream PR openai#747 (sylvesterkaczmarek), two conflicts resolved. runCommand() passed options.maxBuffer straight through, so an unset value left Node's 1 MiB spawnSync default in place and a large `git diff` came back truncated with ENOBUFS. It now defaults to an explicit 256 MiB, and an explicit override still wins. Conflicts: both are adjacency, not disagreement. In process.mjs the fork had added `timeout`/`killSignal` on the lines around `maxBuffer`; the resolved call keeps all three. In tests/process.test.mjs the PR's import line is dropped (the fork's import block already names runCommand and terminateProcessTree) and its two tests are appended. Verified: node --check on both files; tests/process.test.mjs 13/13. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
Upstream PR openai#763 (Soumya95), two conflicts resolved. A turn that failed without throwing (a rejected model, an unsupported parameter) stored only a truncated summary, so /codex:result showed that the job failed but not why. The failure text is now persisted as errorMessage on both the job file and the state index, and summaries are shortened to 96 characters instead of carrying a whole error into an index entry. Conflicts: both in tests/state.test.mjs and both unions. The import block keeps the fork's list and adds runTrackedJob. The test block keeps the fork's tests and appends the PR's two; the conflict cut through the fork's last test (its closing `});` was the line both sides shared after the marker), so that brace is restored explicitly in the resolution. Verified: node --check on the three touched scripts; tests/state.test.mjs, tests/tracked-jobs.test.mjs and tests/runtime.test.mjs 139/139. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
Upstream PR openai#731 (fscfede-beep), two conflicts resolved. The review-gate flag lived in the workspace state file under CLAUDE_PLUGIN_DATA, so a different plugin data root (or a cleared state dir) silently reverted the gate to off while /codex:setup still reported it as enabled. It now lives in a durable per-workspace file under CODEX_HOME, with the state copy kept in sync as a cache. Conflicts: both in tests/state.test.mjs, both unions, and the second one cut through the fork's last test again — its closing `});` is restored in the resolution. One change beyond the PR: writeDurableConfig() created the file with fs.writeFileSync and a plain mkdirSync, while every other artifact this module writes goes through ensurePrivateDir()/writeJsonFileAtomic(). It now does too, so the new config file is 0600 like the rest and a torn write cannot silently disable the gate on the next read. Verified: node --check on state.mjs; tests/state.test.mjs, tests/runtime.test.mjs and tests/commands.test.mjs 134/134. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
Upstream PR openai#737 (fscfede-beep), one conflict resolved. Hooks ran `node` directly, so a Node installed through nvm, fnm, asdf, mise, Volta or Homebrew was invisible under the minimal PATH hooks get, and the plugin looked broken for anyone who does not have a system-wide node. The hooks and the node-invoking commands now go through scripts/run-node.sh, which finds a supported (>=18.18) toolchain, prefers one that ships codex next to it, and restores the npm global prefix. Conflict: plugins/codex/hooks/hooks.json, SessionEnd. The launcher command is taken from the PR; the timeout stays at this fork's 30s rather than reverting to upstream's 5s, since the bounded teardown imported earlier needs it. One fix beyond the PR: find_managed_node() listed /home/linuxbrew, /opt/homebrew, /usr/local/bin and /opt/local/bin BEFORE the version-manager roots, so on any machine carrying /usr/local/bin/node (Debian and Ubuntu packages, CI images, this repo's own container) the system install shadowed every managed toolchain — the precise failure the PR set out to fix. Four of its own tests caught it here and passed only where no system node exists. The managed roots now come first; HOMEBREW_PREFIX keeps its place ahead of them because it is configured rather than guessed. Verified: bash -n on run-node.sh; the new tests/node-launcher.test.mjs 14/14; full npm test 254/254; npm run check-version passes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
Adds openai#731, openai#737, openai#747 and openai#763 to "Differences From Upstream", and notes in Requirements that Node no longer has to be on the system PATH now that the hooks go through scripts/run-node.sh (including CODEX_COMPANION_NODE for pinning one). Verified: full npm test 254/254. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
|
Pushed the second batch (
Two places needed more than a merge: openai#737 shipped a defect its own tests caught here. openai#731 wrote its new config file unprotected. Also worth recording: in Not imported: #761 ( Generated by Claude Code |
CI's `npm run build` step rejects six type errors. None of them come from this branch: they reproduce identically on main (7c86c7f), which is where they arrived with the earlier lifecycle imports, merged while Actions was not running on this fork. The PR cannot go green without them, and each is an annotation with no runtime effect: - app-server.mjs:441 reached for `client.proc` on the client union, which only the spawned transport has. The base class now declares `proc` as a nullable ChildProcess, so the shared cleanup path is honest about both transports instead of reading a property half of them never carry. The spawned client still assigns the real child in its own constructor, after super(). - broker-lifecycle.mjs:74 read `error.code` off a socket error typed as plain Error; the callback parameter is annotated NodeJS.ErrnoException. - process.mjs terminateProcessTreeAndExit() took `beforeKill` with no default, so it was absent from the options type TypeScript inferred from `= {}`. A @PARAM now states the shape. Verified: npx tsc -p tsconfig.app-server.json clean (it reported all six before, on this branch and on main alike); full npm test 254/254. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg
|
CI ran for the first time on this fork and failed on They are not this branch's. All six reproduce identically on Since no fix for them exists anywhere and the PR cannot reach green while the base is red,
If you would rather keep this branch strictly to the imports, the commit reverts cleanly on its own — but then Generated by Claude Code |
Imports eleven pull requests that are still open in
openai/codex-plugin-cc, one merge commit each so any of them can be reverted on its own, fixes three defects this surfaced, and updates the README to describe what the fork actually does.Upstream
mainhas not moved since this fork's base (db52e28, 7 Jul), so there is nothing merged to catch up on — only open PRs.Imported
8c30e32stop_hook_activeinstead of re-blocking every continuation turn until the retry cap1f6d4adCLAUDE_ENV_FILEkeeps one export per key instead of growing on every session66c9b24--model/--efforton the review commands, and a warning instead of silence for an unrecognised option196276b--sandbox <mode>ontaskand/codex:rescue4a2ef41--resume-thread <id>to continue one specific Codex thread9e6b38b/codex:transferworks with a relocatedCLAUDE_CONFIG_DIR4cda989--read-root <directory>for OS-enforced scoped readsc4a4c4fmaxBuffer, so a largegit diffstops being truncated at Node's 1 MiB defaultfce431e/codex:resultsays whyed667e1CODEX_HOME62c5832scripts/run-node.sh(nvm, fnm, asdf, mise, Volta, Homebrew)Every conflict came from the imported PRs colliding with each other, not with the fork. Each merge commit records its own resolution.
Not imported: #761 (
max/ultrareasoning efforts) — the same proposal from another author, #648, was closed upstream, so those values look unsupported rather than merely undocumented.Resolutions worth reviewing
#724vs#742: who owns the sandbox mode.#724derived it from--write;#742makes--sandboxthe input and deriveswritefrom it. The resolution keeps#742's direction and layers read roots on top. Three things follow from the combination and exist in neither PR alone:--read-roottogether with--sandbox danger-full-accessis now rejected — that mode turns the sandbox off, so no read scope would be enforced.runAppServerTurn(),#724'stry/catcharound thread start/resume wraps#742'sassertResumedSandbox()rather than replacing it. The plain auto-merge would have dropped the resume check.tests/fake-codex-fixture.mjsrecorded each thread start twice after the auto-merge (#724's raw params, then#742's structured record silently overwriting them). Both handlers now store{...message.params, threadId}, which satisfies both PRs' assertions.Not taken from
#724: its version bump to 1.0.11 and the five CHANGELOG sections that go with it. Those releases do not exist in this fork, so the plugin stays at 1.0.6 (npm run check-versionpasses) and the entries are folded into anUnreleasedsection.In
hooks.jsonthe SessionEnd timeout stays at this fork's 30s rather than reverting to upstream's 5s, since the bounded teardown imported earlier needs it.Defects fixed along the way
dd70d87— a busy broker refusing shutdown is not an identity rejection.shutdownBrokerSessionLocked()treated every error reply tobroker/shutdownas an identity mismatch, the BUSY refusal included, so when a client connected between the session-end guard check and the shutdown request — the normal race — SessionEnd exited 1 blaming the instance token for a broker that was simply still in use. It now returns{ refused: true, exited: false }and leaves the process and the record alone. This also retires the two tests that had been failing since the earlier imports: one predated #623's busy check and now parks its lingering socket as a peer shutdown requester (what the broker itself calls "not work"); the other expectednullfromsendBrokerShutdown()and now asserts the outcome shape that matters.62c5832—#737shipped with its precedence inverted.find_managed_node()listed/home/linuxbrew,/opt/homebrew,/usr/local/binand/opt/local/binbefore the version-manager roots, so on any machine carrying/usr/local/bin/nodethe system install shadowed every nvm/fnm/asdf/mise toolchain — the precise failure the PR set out to fix. Four of its own tests catch it. The managed roots now come first;HOMEBREW_PREFIXkeeps its place ahead of them because it is configured rather than guessed.ed667e1—#731wrote its new config file unprotected.writeDurableConfig()usedfs.writeFileSyncand a plainmkdirSync, while every other artifactstate.mjscreates goes throughensurePrivateDir()/writeJsonFileAtomic(). It now does too: 0600 like the rest, and a torn write can no longer makereadDurableConfig()fall back and silently disable the review gate.4f5c23e— the typecheck was already red onmain. CI'snpm run buildstep rejected sixTS2339errors that reproduce identically on the base commit (7c86c7f); they arrived with the earlier lifecycle imports, merged while Actions was not running on this fork. Three annotations with no runtime effect:procdeclared on the client base class, a socket error typed asNodeJS.ErrnoException, and a@paramforterminateProcessTreeAndExit's options.README
The landing page still described upstream's command surface, so several flags this fork supports were undocumented:
--effortvalues, and the new unrecognised-option warning;--model/--efforton the review commands,--resume-threadand the resume flags' exclusivity,CLAUDE_CONFIG_DIRon/codex:transfer, the review gate's continuation-turn skip, and a note that Node no longer has to be on the system PATH;Both review commands'
argument-hintnow name--model/--effort, which the script already accepted.tests/commands.test.mjsasserted the adversarial hint as one adjacent string ([--scope ...] [focus ...]); it now asserts the scope, effort and focus fragments separately, so inserting a flag between them no longer fails a documentation test.Testing
npm test: 254/254, green on repeated full runs, and CI is green on the head.npx tsc -p tsconfig.app-server.jsonis clean andnpm run check-versionpasses.Authorship
The commits carried in by the merges keep their original authors and hashes, so they still match the upstream PRs and can be compared against them later. GitHub marks them Unverified, as it does for any third-party work merged into a fork.
🤖 Generated with Claude Code
https://claude.ai/code/session_01UXfvnjSC72HsM6EEPVt2Tg