Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
e494a8e
fix: keep background jobs alive across SessionEnd
jnkliberty May 28, 2026
7580728
fix: reap ghost jobs whose worker died without recording a result
Greenbauer Jul 4, 2026
954af53
fix: refresh updatedAt when reaping a dead job so it sorts newest-first
Greenbauer Jul 9, 2026
0cfbff8
fix: do not catch teardown signals in the worker crash guard
Greenbauer Jul 10, 2026
f9dff6a
fix: reap dead jobs before selecting task resume candidates
Greenbauer Jul 10, 2026
d7aec13
docs: v1.2.0 plan
CBEPX Aug 27, 2026
a9eff00
feat(task): --await and --prompt-stdin; result --wait
CBEPX Aug 27, 2026
93ca039
docs(plan): v1.2.0 amendments after Codex plan review (11 findings)
CBEPX Aug 27, 2026
834a27f
fix(tests): interruptible turn-delay fixture; document worker-surviva…
CBEPX Aug 27, 2026
47d92c3
feat(rescue): single node call via task --await; resume decision befo…
CBEPX Aug 28, 2026
9b9cc59
fix(rescue): align agent/skill on follow-up calls; correct exit-3 ret…
CBEPX Aug 28, 2026
2f9c557
Merge branch 'pr/355' into release/v1.2.0
CBEPX Aug 28, 2026
2b21a55
Merge branch 'pr/425' into release/v1.2.0
CBEPX Aug 28, 2026
b2ee190
fix(lifecycle): own-jobs-only SessionEnd (#355), pid-liveness reaper …
CBEPX Aug 28, 2026
0708ea4
fix(lifecycle): reap dead workers before the SessionEnd active-backgr…
CBEPX Aug 28, 2026
d08e578
chore(release): v1.2.0
CBEPX Aug 28, 2026
9a66fea
fix(docs,cli): flag hygiene for the single-call rescue body; stale SK…
CBEPX Aug 28, 2026
529afaa
fix(lifecycle): never shut the shared broker while any workspace job …
CBEPX Aug 28, 2026
b9bc439
fix(turns): wait for the terminal notification after interrupt; repor…
CBEPX Aug 28, 2026
8346bfb
fix(state): never store --config values in public job records
CBEPX Aug 28, 2026
22cfd04
docs(readme): separate task --await and result exit-code contracts
CBEPX Aug 28, 2026
cd71057
fix(state): store the worker pid in an atomic sidecar instead of rewr…
CBEPX Aug 28, 2026
5963c70
fix(reaper): reconcile terminal job files into the state index; resum…
CBEPX Aug 28, 2026
134a6c7
fix(state): atomic state and job-file writes (temp + rename) so reade…
CBEPX Aug 28, 2026
e86528e
fix(broker): refuse shutdown while another client is active (shutdown…
CBEPX Aug 28, 2026
70841ac
fix(state): cross-process lock around state read-modify-write; prune …
CBEPX Aug 28, 2026
de8ed9a
fix(reaper): reconcile from the authoritative job file before pid liv…
CBEPX Aug 28, 2026
4c18a91
fix(turns): referenced ack timer, terminal record on transport exit, …
CBEPX Aug 28, 2026
c0ec4cf
fix(state): redact legacy --config values at load and at the output b…
CBEPX Aug 28, 2026
97eaa6d
fix(state): atomic lock acquisition with holder info; holder-less loc…
CBEPX Aug 28, 2026
4f79b6e
fix(broker): frame the shutdown reply by newline, match ids, bound th…
CBEPX Aug 28, 2026
a4e8cd3
fix(state): fenced lock takeover with ownership token; owner-checked …
CBEPX Aug 28, 2026
d6018e1
fix(app-server): idempotent bounded close()
CBEPX Aug 28, 2026
47333bf
fix(state): migrate legacy raw requests into the private payload file…
CBEPX Aug 28, 2026
d3ab1ad
fix(state): stale-lock breaker re-checks the holder token before rena…
CBEPX Aug 28, 2026
ddf33f6
fix(state): stage a legacy payload only for queued jobs; remove reque…
CBEPX Aug 28, 2026
39201b0
fix(state): fence stale-lock takeover with a per-generation tombstone
CBEPX Aug 28, 2026
746637c
fix(broker): count connected-but-silent clients as busy for shutdown-…
CBEPX Aug 28, 2026
972ec17
docs: reconcile the lock staleness contract
CBEPX Aug 28, 2026
508cb43
fix(state): owner-aware tombstone reclaim; never unfence a live breaker
CBEPX Aug 28, 2026
09ed7be
docs: state the exact lock staleness contract
CBEPX Aug 28, 2026
07375d3
fix(state): replace-free ticket lock (bakery on unique immutable file…
CBEPX Aug 28, 2026
1c79e98
docs: describe the ticket lock
CBEPX Aug 28, 2026
57668d1
fix(state): ticket lock fails closed on listing errors
CBEPX Aug 28, 2026
56c65c3
fix(state): ticket lock fails closed on stat errors; ENOENT means van…
CBEPX Aug 28, 2026
159480b
fix(state): judge all blockers before unlinking any; read errors fail…
CBEPX Aug 28, 2026
74f2b4d
fix(state): skip the poll sleep only after an actual eviction
CBEPX Aug 28, 2026
eb4f635
ci: verify-on-release workflow and RELEASING.md (same release shape a…
CBEPX Aug 28, 2026
e570275
fix(broker): bound shutdown so a client that never answers cannot mak…
CBEPX Aug 28, 2026
f880a77
fix(broker): one memoized shutdown; exit only after cleanup completes
CBEPX Aug 28, 2026
482291a
fix(hook): one absolute SessionEnd budget; hooks.json timeout above t…
CBEPX Aug 28, 2026
9ad7437
fix(hook): SessionEnd budget override can only shorten the default
CBEPX Aug 28, 2026
ae32d99
fix(hook): catch only the typed state-lock timeout
CBEPX Aug 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
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.1.1"
"version": "1.2.0"
},
"plugins": [
{
"name": "codex",
"description": "Use Codex from Claude Code to review code or delegate tasks.",
"version": "1.1.1",
"version": "1.2.0",
"author": {
"name": "OpenAI"
},
Expand Down
66 changes: 66 additions & 0 deletions .github/workflows/release-verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Verify release

# Mirrors the release gate of CBEPX/cc-plugin-codex (publish.yml) minus the npm
# publish step: this plugin is installed from the repository through
# .claude-plugin/marketplace.json, so a release only needs to be verified.

on:
release:
types:
- published
workflow_dispatch:
inputs:
ref:
description: Git ref to verify
required: false
default: main

permissions:
contents: read

jobs:
verify:
name: Verify
runs-on: ubuntu-latest
timeout-minutes: 15

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: 22
cache: npm

- name: Install dependencies
run: npm ci

- 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

- name: No leaked test processes
run: |
sleep 10
if pgrep -f codex-plugin-test- ; then echo "leaked test processes" >&2; exit 1; fi

- name: Run build
run: npm run build

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

- name: Pack (dry run)
run: npm pack --dry-run
48 changes: 48 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

33 changes: 32 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,14 @@ Ask Codex to redesign the database connection to be more resilient.
- if you do not pass `--model` or `--effort`, Codex chooses its own defaults.
- `--effort` accepts `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, and `ultra`. Which of those a given model actually supports is decided by Codex, not by the plugin — run `codex debug models` to see the reasoning levels each model advertises.
- model aliases: `spark` -> `gpt-5.3-codex-spark`, `sol` -> `gpt-5.6-sol`, `luna` -> `gpt-5.6-luna`, `terra` -> `gpt-5.6-terra`, `mini` -> `gpt-5.4-mini`
- `--config key=value` (repeatable, also on `/codex:review` and `/codex:adversarial-review`) forwards a `config.toml` override to the Codex thread, e.g. `--config model_provider=ollama`. On `--resume-last` the plugin opens a fresh app-server session (cold resume) so `--config` overrides, sandbox and approval policy take effect; model and effort for the resumed turn are sent on the turn, never on the resume request.
- `--config key=value` (repeatable, also on `/codex:review` and `/codex:adversarial-review`) forwards a `config.toml` override to the Codex thread, e.g. `--config model_provider=ollama`. On `--resume-last` the plugin opens a fresh app-server session (cold resume) so `--config` overrides, sandbox and approval policy take effect; model and effort for the resumed turn are sent on the turn, never on the resume request. In a `--background`/`--await` job record the config **keys** are recorded and the **values** are never stored (they read back as `[redacted]` in `status`/`result`): the real values live only in the job's private 0600 `jobs/<id>.request.json`, which the worker consumes and deletes.
- follow-up rescue requests can continue the latest Codex task in the repo
- under the hood, `/codex:rescue` and the `codex-rescue` agent are each a single `scripts/codex-companion.mjs task --await --prompt-stdin <flags>` call: `--await [--await-timeout-ms <ms>]` launches the same tracked background job as `--background`, then waits for it (default 540000 ms), and `--prompt-stdin` reads the prompt as stdin verbatim (so it cannot be combined with `--args-stdin`, `--prompt-file`, or prompt text on the command line). Exit code is 0 when the job completed, 1 when it failed or was cancelled, and 3 when the wait times out while the job is still queued or running — exit 3 prints a `Re-run: node "<abs>" result <id> --wait --timeout-ms 540000` hint, which is the only follow-up call the rescue flow makes.
- `result <id> [--wait [--timeout-ms <ms>]]` answers a different question, so it has its own contract: `result` exits 0 for any terminal record (completed, failed or cancelled) and 3 while the job is still active. Its exit code means "a result was retrieved", not "the job succeeded" — unlike `task --await` it never returns 1 for a failed job, so read the rendered record for the outcome. A plain `result <id>` on a still-running job prints the same `--wait` hint and exits 3 instead of failing (fixes upstream #498/#524, which reported "No job found" for a running job). `--json` on either returns `{ job, storedJob }` (or, on a timeout, the `status --json` snapshot plus a `resumeCommand` field).
- The detached worker outlives the companion only when the companion returns on its own (exit 3); a host process-tree kill — e.g. Claude Code's Bash timeout — also kills the worker, so keep `--await-timeout-ms` below the host limit (default 540000 < 600000).
- `--turn-timeout-ms <ms>` (or `CODEX_TURN_TIMEOUT_MS`, also on `/codex:review` and `/codex:adversarial-review`) bounds a single Codex turn: on expiry it interrupts the turn and returns a structured failed result ("turn timed out after `<ms>` ms") instead of hanging. Default is `0` (unbounded). The budget travels with a `--background`/`--await` job, so a detached worker enforces it too. The interrupt is not trusted on its own: the run waits up to 10 s for the turn's terminal notification, and if none arrives the failure says so ("interrupt not acknowledged — the turn may still be running in the shared runtime, check status or cancel"), because a shared broker runtime can keep executing a turn nobody is listening to any more. A run that owns its own app-server (a cold `--resume-last`) closes it in that case, which does stop the turn (stdin EOF, then `SIGTERM`, then `SIGKILL`, so the close is bounded too). Partial output on a timed-out turn is best-effort: only whole items Codex had already completed are kept, so a turn interrupted mid-message reports less text than Codex had produced.
- the `SessionEnd` hook works to one absolute budget (`SESSION_END_BUDGET_MS`, 12 s; `CODEX_COMPANION_SESSION_END_BUDGET_MS` can only *shorten* it — a larger value is ignored with a note, since the hook timeout is fixed), and every bounded step inside it — the workspace state lock, each broker handshake, the busy retries, the teardown probe — is clamped to what is left of that budget. `hooks/hooks.json` gives `SessionEnd` a 15 s timeout, which must stay **above** the budget: below it Claude Code would kill the hook mid-decision instead of letting it report one. A test asserts the pair, so the two numbers cannot drift apart.
- if a background job's session ends while `CODEX_COMPANION_BROKER_IDLE_TIMEOUT_MS=0`, the shared broker that keeps running for that job never self-terminates on its own — its normal idle exit is disabled in that configuration, so the broker only goes away once the job finishes (or is reaped as dead) and a later `SessionEnd` runs.

### `/codex:transfer`

Expand Down Expand Up @@ -208,8 +214,12 @@ Examples:
```bash
/codex:result
/codex:result task-abc123
/codex:result task-abc123 --wait
/codex:result task-abc123 --wait --timeout-ms 60000
```

On a job that already has a terminal record (completed, failed, or cancelled), `/codex:result` exits 0 and shows it — the exit code reports that a result was retrieved, not whether the job succeeded. On a job that is still queued or running, a plain `/codex:result <id>` prints a `Re-run: … result <id> --wait` hint and exits 3 instead of failing; add `--wait [--timeout-ms <ms>]` (default 540000 ms) to block until the job reaches a terminal status instead of returning immediately. `--json` returns `{ job, storedJob }` (or, on a `--wait` timeout, the `status --json` snapshot plus a `resumeCommand` field).

### `/codex:cancel`

Cancels an active background Codex job.
Expand Down Expand Up @@ -328,6 +338,27 @@ That means:

Yes. If you already use Codex, the plugin picks up the same [configuration](#common-configurations).

### A command failed with "Timed out … waiting for the Codex state lock"

Every write to this workspace's job state is serialized by a ticket lock: each
command takes a numbered ticket in `state.lock.d/` and waits for the tickets ahead
of it. A ticket whose process is gone is cleared automatically, so a crash never
wedges the workspace. A ticket whose process is still *running* is never taken
away — a slow writer and a stuck one look the same from outside, and taking the
lock from a process that is mid-write is how state gets corrupted — so the error
names that PID and the exact ticket file. If that process really is stuck, stop it
and the next command goes through; if the PID belongs to something unrelated (PID
reuse), delete the ticket file the error names.

### A command failed with a raw `EACCES` or `EIO` from the state directory

The same lock refuses to guess. If a ticket in `state.lock.d/` cannot be listed,
read or `stat`ed, the command fails with that error instead of assuming the entry
is absent or abandoned — guessing there is what would let two commands write the
job state at once. Fix the permissions on the state directory (or remove the entry
the error names, once you know no Codex command is using it) and the next command
goes through.

### Can I keep using my current API key or base URL setup?

Yes. Because the plugin uses your local Codex CLI, your existing sign-in method and config still apply.
Expand Down
76 changes: 76 additions & 0 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Releasing

Releases follow the same shape as [CBEPX/cc-plugin-codex](https://github.com/CBEPX/cc-plugin-codex/releases):
a git tag, a GitHub Release with hand-written notes, and the `npm pack` tarball (plus its
SHA-256) attached as the release artifact. There is no npm publish (`package.json` is
`private`); users install from this repository through `.claude-plugin/marketplace.json`.

## 1. Prepare the release branch

```bash
git checkout -b release/vX.Y.Z main
npm run bump-version -- X.Y.Z # package.json, plugin.json, marketplace.json
npm run check-version # all version metadata matches
```

Add a `## X.Y.Z — YYYY-MM-DD` section at the top of `CHANGELOG.md`.

## 2. Gate (local, then CI)

```bash
npm run build && npm run check-version && claude plugin validate . --strict
npm test; sleep 10; pgrep -f codex-plugin-test- | wc -l # must print 0
npm audit --omit=dev
```

Open a pull request against `main`; `Pull Request CI` must be green. Merge with a merge
commit (`gh pr merge N --merge`).

## 3. Tag and build the artifact

```bash
git checkout main && git pull
git tag -a vX.Y.Z -m "vX.Y.Z" && git push origin vX.Y.Z
npm pack # cbepx-codex-plugin-cc-X.Y.Z.tgz
shasum -a 256 cbepx-codex-plugin-cc-X.Y.Z.tgz > cbepx-codex-plugin-cc-X.Y.Z.tgz.sha256
```

## 4. Publish the GitHub Release

```bash
gh release create vX.Y.Z \
--title "codex-plugin-cc vX.Y.Z" \
--notes-file notes.md \
cbepx-codex-plugin-cc-X.Y.Z.tgz cbepx-codex-plugin-cc-X.Y.Z.tgz.sha256
```

`notes.md` template:

```markdown
One-line summary of the release.

### Highlights
- …

### Compatibility
- CLI / hook / state-format changes callers must know about (omit if none).

### Validation
- Exact tag target: `<full sha>`
- Local gate: N/N tests, 0 leaked test processes, `npm run build`, `npm run check-version`, `claude plugin validate . --strict`
- GitHub CI: <run link>
- Review: Codex adversarial review (verdict), Claude review (verdict)
- Runtime dependency audit: `npm audit --omit=dev` reports 0 vulnerabilities

### Artifact
`<sha256> cbepx-codex-plugin-cc-X.Y.Z.tgz`
```

Publishing the release triggers `.github/workflows/release-verify.yml`, which re-runs the
gate on the tag (tests, leak check, build, version check, audit, pack dry run).

## 5. Update local installs

```bash
claude plugin marketplace update cbepx && claude plugin update codex@cbepx
```
Loading