Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
489d971
docs(plan): v1.4.0 implementation plan (tooling, Windows CI, small Wi…
CBEPX Sep 27, 2026
fb1dd14
docs(plan): v1.4.0 — cross-version test runner instead of directory d…
CBEPX Sep 27, 2026
39507d0
docs(plan): v1.4.0 — fold in Codex plan review (cmd.exe quoting, stdi…
CBEPX Sep 27, 2026
11f6652
test: make the suite runnable on Windows (LF, explicit runner, env/mo…
CBEPX Sep 27, 2026
a99878c
test: never throw from process-group cleanup in tests
CBEPX Sep 27, 2026
96696ba
test: reconcile Windows expectations with the v1.3.0 documented refusals
CBEPX Sep 27, 2026
3458349
docs(plan): v1.4.0 Task 7 — Windows EPERM on atomic rename over an op…
CBEPX Sep 27, 2026
f85a3fb
test: skip the open-reader rename test on Windows; exact SessionEnd e…
CBEPX Sep 27, 2026
4f78a80
ci: one run per SHA, quality job on node 24, wider broker busy-retry
CBEPX Sep 27, 2026
27dc9b1
chore: lint, typecheck for tests, coverage, mutation (critical), depe…
CBEPX Sep 27, 2026
74b2390
test(tooling): tighten catalogue fallback tests, keep worktrees out o…
CBEPX Sep 28, 2026
7145254
fix(args): keep backslashes that escape nothing in --args-stdin text
CBEPX Sep 28, 2026
0cccebd
refactor(args): drop the unreachable trailing-backslash branch
CBEPX Sep 28, 2026
6af7267
fix(windows): spawn without $SHELL — where.exe resolution, quoted cmd…
CBEPX Sep 28, 2026
75d9684
fix(windows): harden the cmd.exe launch after the Codex second pass
CBEPX Sep 28, 2026
03151c9
fix(hooks): bounded stdin read that never drops a complete payload; r…
CBEPX Sep 28, 2026
a70ac41
fix(hooks): stream errors and decoder tails never pass as complete in…
CBEPX Sep 28, 2026
dfd941d
ci: report leaked test processes after the suite
CBEPX Sep 28, 2026
35f6bed
fix(state): per-user fallback state root under %LOCALAPPDATA% and bou…
CBEPX Sep 28, 2026
1657916
fix(state): loadState also retries EPERM on Windows
CBEPX Sep 28, 2026
cc15e07
chore: fold in deferred v1.3.0 review minors
CBEPX Sep 28, 2026
005d8eb
docs(state): torn-state reader retries EPERM too; README wording for …
CBEPX Sep 28, 2026
26a5c3a
ci: one leak step for every OS (Windows reported, posix enforced)
CBEPX Sep 28, 2026
904ab24
test(catalog): tie-break test proves newest-family rule; reason-table…
CBEPX Sep 28, 2026
cb7d7dd
test(ci): tolerate slow Windows runners
CBEPX Sep 28, 2026
451a33a
test(state): fallback-root test expects %LOCALAPPDATA% on Windows
CBEPX Sep 28, 2026
a8bf858
docs: Windows support notes and 1.4.0 changelog
CBEPX Sep 28, 2026
ba478ca
test: deterministic EAGAIN refusal; shared-broker test outlives a slo…
CBEPX Sep 28, 2026
8d8ffb5
fix(windows): spawn in-box tools by absolute System32 path
CBEPX Sep 28, 2026
f70fb29
fix(windows,hooks): never spawn an unresolved name; unreadable state …
CBEPX Sep 28, 2026
155db5a
release: v1.4.0 version metadata
CBEPX Sep 28, 2026
3f1128e
fix(windows): cmd.exe shim launches never resolve the interpreter fro…
CBEPX Sep 28, 2026
29d2342
docs(triage): v1.4.0 upstream comment drafts and status updates
CBEPX Sep 28, 2026
3c41a52
ci: Windows matrix jobs are required
CBEPX Sep 28, 2026
c33e6e4
test: waitFor defaults to 30 s for slow hosted VMs
CBEPX Sep 28, 2026
e88a4f1
docs(triage): re-plan the Windows issues that did not ship in v1.4.0
CBEPX Sep 28, 2026
ab45f30
fix(windows): resolve executables with fs over PATH x PATHEXT, not wh…
CBEPX Sep 28, 2026
088ada2
fix(windows): cmd.exe shim launches see only absolute PATH entries
CBEPX Sep 28, 2026
049690e
fix(runtime): infer turn completion only once a subagent has joined t…
CBEPX Sep 28, 2026
0552ea0
fix(runtime): declare sawSubagents on TurnCaptureState
CBEPX Sep 28, 2026
58e7a59
fix(runtime): a transport that dies before turn/completed ends the tu…
CBEPX Sep 28, 2026
9dc9390
test(broker): show the broker log tail when SessionEnd leaves a dead …
CBEPX Sep 28, 2026
5b6bb46
test(broker): read the broker log tail only when the assertion fails
CBEPX Sep 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .c8rc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"all": true,
"include": ["plugins/codex/scripts/**/*.mjs", "scripts/**/*.mjs"],
"exclude": ["plugins/codex/.generated/**", "scripts/run-tests.mjs"],
"reporter": ["text", "json-summary", "lcov"],
"reports-dir": "reports/coverage",
"check-coverage": true,
"lines": 88,
"statements": 88,
"branches": 78,
"functions": 95
}
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
},
"metadata": {
"description": "CBEPX fork of the OpenAI Codex plugin for Claude Code: max/ultra effort, per-thread config overrides, gpt-5.6 aliases, rescue agent fixes.",
"version": "1.3.0"
"version": "1.4.0"
},
"plugins": [
{
"name": "codex",
"description": "Use Codex from Claude Code to review code or delegate tasks.",
"version": "1.3.0",
"version": "1.4.0",
"author": {
"name": "OpenAI"
},
Expand Down
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text=auto eol=lf
8 changes: 8 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/sh
set -eu

echo "[pre-commit] lint"
npm run lint

echo "[pre-commit] typecheck"
npm run typecheck
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
39 changes: 39 additions & 0 deletions .github/workflows/mutation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Mutation

permissions:
contents: read

on:
workflow_dispatch:
schedule:
- cron: "0 3 * * 0"

jobs:
critical:
name: Critical mutation (ubuntu, node 24)
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 24
cache: npm

- name: Install dependencies
run: npm ci

- name: Run critical mutation shard
run: npm run test:mutation:critical:force

- name: Upload mutation report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: mutation-critical
path: reports/mutation/
if-no-files-found: error
retention-days: 14
72 changes: 64 additions & 8 deletions .github/workflows/pull-request-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@ name: Pull Request CI
on:
pull_request:
push:
branches: [main, "release/**", "ci/**"]
branches: [main]
workflow_dispatch:

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read
Expand All @@ -12,10 +17,8 @@ jobs:
ci:
name: CI (${{ matrix.os }}, node ${{ matrix.node }})
runs-on: ${{ matrix.os }}
timeout-minutes: 20
# Spike: Windows is exploratory until the test harness runs there
# (fake codex is a shebang script; see docs/superpowers/plans step 0).
continue-on-error: ${{ startsWith(matrix.os, 'windows') }}
# Windows runners have been seen 2-3x slower for hours at a time.
timeout-minutes: 40
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -45,7 +48,7 @@ jobs:
npm test 2>&1 | tee test-output.log
status=${PIPESTATUS[0]}
echo "## Test summary (${{ matrix.os }}, node ${{ matrix.node }})" >> "$GITHUB_STEP_SUMMARY"
{ rg -e 'ℹ (tests|pass|fail)' -e '^not ok' test-output.log || grep -E 'ℹ (tests|pass|fail)|^not ok' test-output.log; } >> "$GITHUB_STEP_SUMMARY" || true
{ rg -e 'ℹ (tests|pass|fail)' -e '^# (tests|pass|fail)' -e '^not ok' -e '^✖' test-output.log || grep -E 'ℹ (tests|pass|fail)|^# (tests|pass|fail)|^not ok|^✖' test-output.log; } >> "$GITHUB_STEP_SUMMARY" || true
exit "$status"

- name: Upload test log
Expand All @@ -56,11 +59,64 @@ jobs:
path: test-output.log
retention-days: 7

# Same check as the local gate. Windows is reported, not enforced, until
# the Windows kill path lands (v1.4.1); it runs even after a red suite so
# the leak list is always available.
- name: No leaked test processes
if: runner.os != 'Windows'
if: always()
shell: bash
run: |
sleep 10
if pgrep -f codex-plugin-test- ; then echo "leaked test processes" >&2; exit 1; fi
if [ "$RUNNER_OS" = "Windows" ]; then
powershell -NoProfile -Command "\$p = @(Get-CimInstance Win32_Process | Where-Object { \$_.CommandLine -match 'codex-plugin-test-' }); Write-Output ('Leaked test processes after 10 s: ' + \$p.Count); \$p | Select-Object ProcessId,ParentProcessId,Name,@{n='Cmd';e={\$_.CommandLine.Substring(0,[Math]::Min(160,\$_.CommandLine.Length))}} | Format-Table -AutoSize | Out-String -Width 220" | tee -a "$GITHUB_STEP_SUMMARY"
elif pgrep -af codex-plugin-test- ; then echo "leaked test processes" >&2; exit 1; fi

- name: Run build
run: npm run build

quality:
name: Quality (ubuntu, node 24)
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 24
cache: npm

- name: Install dependencies
run: npm ci

- name: Install Codex CLI
run: npm install -g @openai/codex

- name: Version metadata matches the package
run: npm run check-version

- name: Changelog has a section for the version
run: npm run check:changelog

- name: Lint
run: npm run lint

- name: Run build
run: npm run build

- name: Typecheck tests and scripts
run: npm run typecheck:tests

- name: Test coverage
run: npm run test:coverage

- name: Upload coverage report
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: coverage
path: reports/coverage/
retention-days: 14
91 changes: 77 additions & 14 deletions .github/workflows/release-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,16 @@ permissions:
contents: read

jobs:
verify:
name: Verify
runs-on: ubuntu-latest
timeout-minutes: 15
ci:
name: CI (${{ matrix.os }}, node ${{ matrix.node }})
runs-on: ${{ matrix.os }}
# Windows runners have been seen 2-3x slower for hours at a time.
timeout-minutes: 40
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [18, 22, 24]

steps:
- name: Check out release ref
Expand All @@ -33,7 +39,7 @@ jobs:
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 22
node-version: ${{ matrix.node }}
cache: npm

- name: Install dependencies
Expand All @@ -42,25 +48,82 @@ jobs:
- name: Install Codex CLI
run: npm install -g @openai/codex

- name: Codex version
run: codex --version

- name: Version metadata matches the tag
run: npm run check-version

- name: Run test suite
run: npm test
shell: bash
run: |
set +e
npm test 2>&1 | tee test-output.log
status=${PIPESTATUS[0]}
echo "## Test summary (${{ matrix.os }}, node ${{ matrix.node }})" >> "$GITHUB_STEP_SUMMARY"
{ rg -e 'ℹ (tests|pass|fail)' -e '^# (tests|pass|fail)' -e '^not ok' -e '^✖' test-output.log || grep -E 'ℹ (tests|pass|fail)|^# (tests|pass|fail)|^not ok|^✖' test-output.log; } >> "$GITHUB_STEP_SUMMARY" || true
exit "$status"

- name: Upload test log
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: test-log-${{ matrix.os }}-node${{ matrix.node }}
path: test-output.log
retention-days: 7

# Same check as the local gate. Windows is reported, not enforced, until
# the Windows kill path lands (v1.4.1); it runs even after a red suite so
# the leak list is always available.
- name: No leaked test processes
if: always()
shell: bash
run: |
sleep 10
if pgrep -f codex-plugin-test- ; then echo "leaked test processes" >&2; exit 1; fi
if [ "$RUNNER_OS" = "Windows" ]; then
powershell -NoProfile -Command "\$p = @(Get-CimInstance Win32_Process | Where-Object { \$_.CommandLine -match 'codex-plugin-test-' }); Write-Output ('Leaked test processes after 10 s: ' + \$p.Count); \$p | Select-Object ProcessId,ParentProcessId,Name,@{n='Cmd';e={\$_.CommandLine.Substring(0,[Math]::Min(160,\$_.CommandLine.Length))}} | Format-Table -AutoSize | Out-String -Width 220" | tee -a "$GITHUB_STEP_SUMMARY"
elif pgrep -af codex-plugin-test- ; then echo "leaked test processes" >&2; exit 1; fi


- name: Run build
run: npm run build

quality:
name: Quality (ubuntu, node 24)
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Check out release ref
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.release.tag_name || inputs.ref || github.ref_name }}

- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: 24
cache: npm

- name: Install dependencies
run: npm ci

- name: Install Codex CLI
run: npm install -g @openai/codex

- name: Version metadata matches the tag
run: npm run check-version

- name: Changelog has a section for the version
run: npm run check:changelog

- name: Lint
run: npm run lint

- name: Run build
run: npm run build

- name: Typecheck tests and scripts
run: npm run typecheck:tests

- name: Runtime dependency audit
run: npm audit --omit=dev

- name: Pack (dry run)
run: npm pack --dry-run
run: |
npm pack --dry-run
rm -f ./*.tgz
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,5 @@ plugins/codex/.generated/

# git worktrees (project-local)
.worktrees/
reports/
.stryker-tmp/
28 changes: 27 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Changelog

## 1.4.0 — 2026-09-28

### Fixed
- Windows: commands are no longer spawned through `$SHELL` (usually Git Bash under Claude Code, which mangled `taskkill /PID /T /F` and other arguments); executables are resolved with `where.exe`, `.exe`/`.com` files run directly, and `.cmd`/`.bat` shims (including a global `npm install -g @openai/codex`) run through `cmd.exe` with every argument escaped for both `cmd /c` and the shim's own `%*` re-parse (`%VAR:a=b%` substitution is the one documented ceiling; an argument containing CR/LF is refused) (#525, #647, #656, #669, #708, #287, #409, #735).
- Windows: the broker and app-server child processes no longer flash a visible console window on spawn (`windowsHide: true`) (#440, #451).
- The `Stop`, `SessionStart` and `SessionEnd` hooks read stdin with a bounded deadline (2 s / 5 s / 1 s respectively) and a 1 MiB limit instead of a blocking `readFileSync(0)`; a disabled Stop review gate no longer hangs until the 900 s hook timeout when stdin never arrives, and the companion no longer crashes with `EAGAIN` reading a non-blocking stdin under concurrent sessions (#530, #544, #120, #247, #123, #150, #165).
- `--args-stdin` (and `$ARGUMENTS`) no longer eats a backslash that does not escape a quote, another backslash or whitespace, so Windows paths such as `C:\Users\me\project\file.mjs` survive; `\\server\share` is a documented limitation, and `--prompt-stdin` remains available for byte-exact text.
- A turn without subagents no longer has its completion inferred 250 ms after the final answer: on a slow host a delayed `turn/completed` with status `failed` was being recorded as `completed` (seen twice on hosted CI). Inference now applies only once a subagent thread has joined the turn, the case it was built for.
- A Codex app-server that exits after the final answer but before `turn/completed` now ends the turn as `failed` with the captured output (previously the completion never settled: a foreground run could exit silently and a background job stayed `running` until reaped). The shared broker shuts down when its app-server dies, so every connected client sees the same outcome instead of waiting on a runtime that no longer exists.
- Windows: `state.json` reads and writes and lock-ticket reads retry briefly (bounded, roughly 300 ms worst case) on `EPERM`/`EBUSY`/`EACCES` instead of failing outright when another process holds the file open.

### Added
- `SECURITY.md` (supported versions, GitHub Security Advisories reporting) (#326).
- Node 24 development tooling: ESLint, a `typecheck:tests` script over `tests/**` and `scripts/**` (`checkJs` is off for now — turning it on surfaced 54 pre-existing findings, deferred), `c8` coverage (`npm run test:coverage`, thresholds in `.c8rc.json`), Stryker mutation testing over the critical `args.mjs`/`model-catalog.mjs` pair (`npm run test:mutation:critical`), a `check:changelog` gate that keeps `CHANGELOG.md` and `plugins/codex/CHANGELOG.md` byte-identical, Dependabot, and `npm run setup:git-hooks` (pre-commit lint + typecheck).
- CI: one workflow run per SHA (push limited to `main`, release branches checked via manual `workflow_dispatch`, with a `concurrency` group cancelling superseded runs) and a new `quality` job on Node 24 alongside the existing OS × Node matrix.

### Changed
- The per-user fallback state root used when `CLAUDE_PLUGIN_DATA` is unset is now `%LOCALAPPDATA%\codex-companion` on Windows. A pre-1.4.0 root under `%TEMP%\codex-companion-user` keeps being used, with a one-line stderr notice, until it is removed by hand — nothing is migrated automatically. This transitional notice only applies to an unversioned/dev checkout: a marketplace install's state-root hash is derived from `CLAUDE_PLUGIN_ROOT`, which already includes the plugin version in the marketplace cache, so a normal upgrade never sees the legacy root.
- `BROKER_BUSY_RETRY_MS` widened from 1000 to 3000 ms; the "teardown only after the broker confirmed idle" invariant is unchanged.
- Deferred v1.3.0 review minors folded in: `CODEX_REVIEW_GATE_MAX_ROUNDS` rejects a non-integer, negative or otherwise malformed value (falls back to 3 with a warning) instead of misreading it; `setup --review-gate-model ""` / `--review-gate-effort ""` now fails with `--<flag> needs a value; use inherit to clear it.` instead of writing anything; a job id used to build the `workerCommandLine` match is now regex-escaped; the `kill-failed` broker-teardown reason is documented in the README's reason table. Also added (test coverage only, no behavior change): a catalogue-only model alias resolving correctly on a priority tie, and the fallback-root refusal covering a non-standard directory mode.

### Known limitations
- Kills issued from stored process records (`/codex:cancel`, `SessionEnd` cleanup, stale-broker replacement, broker teardown) are still refused on Windows (`identity-unavailable`); process identity verification is now targeted for v1.4.1, not v1.4.0 as previously stated. `/codex:cancel` still sends the turn interrupt on a best-effort basis; `SessionEnd` only refuses. A worker or broker left behind exits when its turn ends and, for the broker, once every client has disconnected and its idle timeout elapses — an unbounded turn is not reaped on Windows until v1.4.1.

Ported with reference to upstream PRs by mohammad-malik, mittalpk, stantheman0128, tmchow, D2758695161, ikbear, e345ee, tanakauo.

## 1.3.0 — 2026-09-27

### Fixed
Expand All @@ -26,7 +52,7 @@
- `runCommand` reports `status: null`, not `0`, for a subprocess that timed out.

### Known limitations
- On Windows, kills issued from stored process records (cancel worker, `SessionEnd` cleanup, stale-broker replacement, broker teardown) are refused until process identity lands in v1.4.0; leaks are bounded by the broker idle timeout, and the turn interrupt is still sent regardless.
- On Windows, kills issued from stored process records (cancel worker, `SessionEnd` cleanup, stale-broker replacement, broker teardown) are refused until process identity lands in v1.4.0; a leaked broker exits on its idle timeout once every client has disconnected, and `cancel` still sends the turn interrupt on a best-effort basis (`SessionEnd` does not).
- Foreground job records written by v1.2.x (no `pidIdentity`) are not killed at `SessionEnd` after upgrading to v1.3.0 (one-off).
- When `CLAUDE_PLUGIN_DATA` is unset (inside Claude Code the SessionStart hook normally sets it), the fallback state root under `os.tmpdir()` hashes `CLAUDE_PLUGIN_ROOT`, whose path includes the plugin version: job and broker state is orphaned on every plugin update, not only when upgrading from v1.2.x.
- Darwin identity checks use `ps lstart`, which has 1 s resolution.
Expand Down
Loading
Loading