Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -5,13 +5,13 @@
},
"metadata": {
"description": "Codex plugins to use in Claude Code for delegation and code review.",
"version": "1.0.6-fork.1"
"version": "1.0.6-fork.2"
},
"plugins": [
{
"name": "codex",
"description": "Use Codex from Claude Code to review code or delegate tasks.",
"version": "1.0.6-fork.1",
"version": "1.0.6-fork.2",
"author": {
"name": "OpenAI"
},
Expand Down
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,15 +86,15 @@ Use it when you want:
- a review of your current uncommitted changes
- a review of your branch compared to a base branch like `main`

Use `--base <ref>` for branch review. It also supports `--wait`, `--background`, `--model <model|spark>`, and `--effort <none|minimal|low|medium|high|xhigh>`. It is not steerable and does not take custom focus text. Use [`/codex:adversarial-review`](#codexadversarial-review) when you want to challenge a specific decision or risk area.
Use `--base <ref>` for branch review. It also supports `--wait`, `--background`, `--model`, and `--effort`. It is not steerable and does not take custom focus text. Use [`/codex:adversarial-review`](#codexadversarial-review) when you want to challenge a specific decision or risk area.

Examples:

```bash
/codex:review
/codex:review --base main
/codex:review --model spark --effort medium
/codex:review --background
/codex:review --model gpt-5.6-sol --effort max
```

This command is read-only and will not perform any changes. When run in the background you can use [`/codex:status`](#codexstatus) to check on the progress and [`/codex:cancel`](#codexcancel) to cancel the ongoing task.
Expand All @@ -106,7 +106,7 @@ Runs a **steerable** review that questions the chosen implementation and design.
It can be used to pressure-test assumptions, tradeoffs, failure modes, and whether a different approach would have been safer or simpler.

It uses the same review target selection as `/codex:review`, including `--base <ref>` for branch review.
It also supports `--wait`, `--background`, `--model <model|spark>`, and `--effort <none|minimal|low|medium|high|xhigh>`. Unlike `/codex:review`, it can take extra focus text after the flags.
It also supports `--wait`, `--background`, `--model`, and `--effort`. Unlike `/codex:review`, it can take extra focus text after the flags.

Use it when you want:

Expand All @@ -119,8 +119,8 @@ Examples:
```bash
/codex:adversarial-review
/codex:adversarial-review --base main challenge whether this was the right caching and retry design
/codex:adversarial-review --model gpt-5.4-mini --effort high look for race conditions and question the chosen approach
/codex:adversarial-review --background look for race conditions and question the chosen approach
/codex:adversarial-review --model gpt-5.6-terra --effort xhigh challenge the retry design
```

This command is read-only. It does not fix code.
Expand Down Expand Up @@ -162,6 +162,8 @@ Ask Codex to redesign the database connection to be more resilient.

- if you do not pass `--model` or `--effort`, Codex chooses its own defaults.
- if you say `spark`, the plugin maps that to `gpt-5.3-codex-spark`
- reasoning efforts are `none`, `minimal`, `low`, `medium`, `high`, `xhigh`, `max`, and `ultra`; the current Codex model catalog is used to reject unsupported combinations such as `gpt-5.6-luna` with `ultra`
- model names are otherwise passed through, so custom providers and newly released models are not blocked by a plugin allowlist
- follow-up rescue requests can continue the latest Codex task in the repo

### `/codex:transfer`
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@openai/codex-plugin-cc",
"version": "1.0.6",
"version": "1.0.7",
"private": true,
"type": "module",
"description": "Use Codex from Claude Code to review code or delegate tasks.",
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codex",
"version": "1.0.6-fork.1",
"version": "1.0.6-fork.2",
"description": "Use Codex from Claude Code to review code or delegate tasks.",
"author": {
"name": "OpenAI"
Expand Down
5 changes: 5 additions & 0 deletions plugins/codex/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## Unreleased

- Stop an orphaned shared Codex app-server broker after 15 minutes with no connected clients. Active foreground and background jobs keep their broker connection open, and the timeout can be configured with `CODEX_COMPANION_BROKER_IDLE_TIMEOUT_MS` (`0` disables the safety timer).
- Close the broker listener before asynchronous child cleanup and safely reject reconnects already queued during shutdown.

## 1.0.0

- Initial version of the Codex plugin for Claude Code
4 changes: 2 additions & 2 deletions plugins/codex/commands/adversarial-review.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Run a Codex review that challenges the implementation approach and design choices
argument-hint: '[--wait|--background] [--base <ref>] [--scope auto|working-tree|branch] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh>] [focus ...]'
argument-hint: '[--wait|--background] [--base <ref>] [--scope auto|working-tree|branch] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh|max|ultra>] [focus ...]'
disable-model-invocation: true
allowed-tools: Read, Glob, Grep, Bash(node:*), Bash(git:*), AskUserQuestion
---
Expand Down Expand Up @@ -36,9 +36,9 @@ Execution mode rules:

Argument handling:
- Preserve the user's arguments exactly.
- `--model` and `--effort` select the Codex runtime and must not become part of the focus text.
- Do not strip `--wait` or `--background` yourself.
- Do not weaken the adversarial framing or rewrite the user's focus text.
- `--model` and `--effort` are runtime-selection flags. Preserve them for the forwarded `adversarial-review` call, but do not treat them as part of the review instructions.
- The companion script parses `--wait` and `--background`, but Claude Code's `Bash(..., run_in_background: true)` is what actually detaches the run.
- `/codex:adversarial-review` uses the same review target selection as `/codex:review`.
- It supports working-tree review, branch review, and `--base <ref>`.
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex/commands/rescue.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Delegate investigation, an explicit fix request, or follow-up rescue work to the Codex rescue subagent
argument-hint: "[--background|--wait] [--resume|--fresh] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh>] [what Codex should investigate, solve, or continue]"
argument-hint: "[--background|--wait] [--resume|--fresh] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh|max|ultra>] [what Codex should investigate, solve, or continue]"
allowed-tools: Bash(node:*), AskUserQuestion, Agent
---

Expand Down
4 changes: 2 additions & 2 deletions plugins/codex/commands/review.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
description: Run a Codex code review against local git state
argument-hint: '[--wait|--background] [--base <ref>] [--scope auto|working-tree|branch] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh>]'
argument-hint: '[--wait|--background] [--base <ref>] [--scope auto|working-tree|branch] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh|max|ultra>]'
disable-model-invocation: true
allowed-tools: Read, Glob, Grep, Bash(node:*), Bash(git:*), AskUserQuestion
---
Expand Down Expand Up @@ -33,9 +33,9 @@ Execution mode rules:

Argument handling:
- Preserve the user's arguments exactly.
- `--model` and `--effort` select the Codex runtime for the review and are not focus text.
- Do not strip `--wait` or `--background` yourself.
- Do not add extra review instructions or rewrite the user's intent.
- `--model` and `--effort` are runtime-selection flags. Preserve them for the forwarded `review` call, but do not treat them as part of the review instructions.
- The companion script parses `--wait` and `--background`, but Claude Code's `Bash(..., run_in_background: true)` is what actually detaches the run.
- `/codex:review` is native-review only. It does not support staged-only review, unstaged-only review, or extra focus text.
- If the user needs custom review instructions or more adversarial framing, they should use `/codex:adversarial-review`.
Expand Down
73 changes: 71 additions & 2 deletions plugins/codex/scripts/app-server-broker.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ import { BROKER_BUSY_RPC_CODE, CodexAppServerClient } from "./lib/app-server.mjs
import { parseBrokerEndpoint } from "./lib/broker-endpoint.mjs";

const STREAMING_METHODS = new Set(["turn/start", "review/start", "thread/compact/start"]);
const BROKER_IDLE_TIMEOUT_ENV = "CODEX_COMPANION_BROKER_IDLE_TIMEOUT_MS";
const DEFAULT_BROKER_IDLE_TIMEOUT_MS = 15 * 60 * 1000;

function resolveIdleTimeoutMs(env = process.env) {
const rawValue = env[BROKER_IDLE_TIMEOUT_ENV];
if (rawValue == null || rawValue === "") {
return DEFAULT_BROKER_IDLE_TIMEOUT_MS;
}

const parsed = Number(rawValue);
if (!Number.isFinite(parsed) || parsed < 0) {
return DEFAULT_BROKER_IDLE_TIMEOUT_MS;
}
return Math.floor(parsed);
}

function buildStreamThreadIds(method, params, result) {
const threadIds = new Set();
Expand Down Expand Up @@ -70,6 +85,16 @@ async function main() {
let activeStreamSocket = null;
let activeStreamThreadIds = null;
const sockets = new Set();
const idleTimeoutMs = resolveIdleTimeoutMs();
let idleTimer = null;
let shuttingDown = false;

function cancelIdleShutdown() {
if (idleTimer) {
clearTimeout(idleTimer);
idleTimer = null;
}
}

function clearSocketOwnership(socket) {
if (activeRequestSocket === socket) {
Expand Down Expand Up @@ -100,11 +125,20 @@ async function main() {
}

async function shutdown(server) {
if (shuttingDown) {
return;
}
shuttingDown = true;
cancelIdleShutdown();
// Stop accepting connections before awaiting child cleanup. Otherwise a
// reconnect can slip into the async shutdown window and inherit a closing
// app-server client.
const serverClosed = new Promise((resolve) => server.close(resolve));
for (const socket of sockets) {
socket.end();
}
await appClient.close().catch(() => {});
await new Promise((resolve) => server.close(resolve));
await serverClosed;
if (listenTarget.kind === "unix" && fs.existsSync(listenTarget.path)) {
fs.unlinkSync(listenTarget.path);
}
Expand All @@ -113,9 +147,33 @@ async function main() {
}
}

function scheduleIdleShutdown(server) {
cancelIdleShutdown();
if (shuttingDown || idleTimeoutMs === 0 || sockets.size > 0 || activeRequestSocket || activeStreamSocket) {
return;
}

idleTimer = setTimeout(async () => {
idleTimer = null;
if (sockets.size > 0 || activeRequestSocket || activeStreamSocket) {
return;
}
await shutdown(server);
process.exit(0);
}, idleTimeoutMs);
}

appClient.setNotificationHandler(routeNotification);

const server = net.createServer((socket) => {
if (shuttingDown) {
// An already-accepted connection event can be delivered after
// server.close(). Reject it decisively and absorb a simultaneous reset.
socket.on("error", () => {});
socket.destroy();
return;
}
cancelIdleShutdown();
sockets.add(socket);
socket.setEncoding("utf8");
let buffer = "";
Expand Down Expand Up @@ -158,6 +216,13 @@ async function main() {
}

if (message.id !== undefined && message.method === "broker/shutdown") {
if (activeRequestSocket || activeStreamSocket) {
send(socket, {
id: message.id,
error: buildJsonRpcError(BROKER_BUSY_RPC_CODE, "Shared Codex broker is busy.")
});
continue;
}
send(socket, { id: message.id, result: {} });
await shutdown(server);
process.exit(0);
Expand Down Expand Up @@ -225,11 +290,13 @@ async function main() {
socket.on("close", () => {
sockets.delete(socket);
clearSocketOwnership(socket);
scheduleIdleShutdown(server);
});

socket.on("error", () => {
sockets.delete(socket);
clearSocketOwnership(socket);
scheduleIdleShutdown(server);
});
});

Expand All @@ -243,7 +310,9 @@ async function main() {
process.exit(0);
});

server.listen(listenTarget.path);
server.listen(listenTarget.path, () => {
scheduleIdleShutdown(server);
});
}

main().catch((error) => {
Expand Down
38 changes: 20 additions & 18 deletions plugins/codex/scripts/codex-companion.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,17 @@ import { fileURLToPath } from "node:url";

import { parseArgs, splitRawArgumentString } from "./lib/args.mjs";
import {
buildPersistentTaskThreadName,
DEFAULT_CONTINUE_PROMPT,
findLatestTaskThread,
getCodexAuthStatus,
getCodexAvailability,
getSessionRuntimeStatus,
importExternalAgentSession,
interruptAppServerTurn,
parseStructuredOutput,
readOutputSchema,
runAppServerReview,
runAppServerTurn
} from "./lib/codex.mjs";
findLatestTaskThread,
getCodexAuthStatus,
getCodexAvailability,
getSessionRuntimeStatus,
importExternalAgentSession,
interruptAppServerTurn,
runAppServerReview,
runAppServerTurn
} from "./lib/codex.mjs";
import { parseStructuredOutput, readOutputSchema } from "./lib/structured-output.mjs";
import { buildPersistentTaskThreadName, DEFAULT_CONTINUE_PROMPT } from "./lib/task-thread.mjs";
import { resolveClaudeSessionPath } from "./lib/claude-session-transfer.mjs";
import { readStdinIfPiped } from "./lib/fs.mjs";
import { collectReviewContext, ensureGitRepository, resolveReviewTarget } from "./lib/git.mjs";
Expand Down Expand Up @@ -68,7 +66,8 @@ const ROOT_DIR = path.resolve(fileURLToPath(new URL("..", import.meta.url)));
const REVIEW_SCHEMA = path.join(ROOT_DIR, "schemas", "review-output.schema.json");
const DEFAULT_STATUS_WAIT_TIMEOUT_MS = 240000;
const DEFAULT_STATUS_POLL_INTERVAL_MS = 2000;
const VALID_REASONING_EFFORTS = new Set(["none", "minimal", "low", "medium", "high", "xhigh"]);
const REASONING_EFFORTS = ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"];
const VALID_REASONING_EFFORTS = new Set(REASONING_EFFORTS);
const MODEL_ALIASES = new Map([["spark", "gpt-5.3-codex-spark"]]);
const STOP_REVIEW_TASK_MARKER = "Run a stop-gate review of the previous Claude turn.";

Expand All @@ -77,9 +76,9 @@ function printUsage() {
[
"Usage:",
" node scripts/codex-companion.mjs setup [--enable-review-gate|--disable-review-gate] [--json]",
" node scripts/codex-companion.mjs review [--wait|--background] [--base <ref>] [--scope <auto|working-tree|branch>]",
" node scripts/codex-companion.mjs adversarial-review [--wait|--background] [--base <ref>] [--scope <auto|working-tree|branch>] [focus text]",
" node scripts/codex-companion.mjs task [--background] [--write] [--resume-last|--resume|--fresh] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh>] [prompt]",
" node scripts/codex-companion.mjs review [--wait|--background] [--base <ref>] [--scope <auto|working-tree|branch>] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh|max|ultra>]",
" node scripts/codex-companion.mjs adversarial-review [--wait|--background] [--base <ref>] [--scope <auto|working-tree|branch>] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh|max|ultra>] [focus text]",
" node scripts/codex-companion.mjs task [--background] [--write] [--resume-last|--resume|--fresh] [--model <model|spark>] [--effort <none|minimal|low|medium|high|xhigh|max|ultra>] [prompt]",
" node scripts/codex-companion.mjs transfer [--source <claude-jsonl>] [--json]",
" node scripts/codex-companion.mjs status [job-id] [--all] [--json]",
" node scripts/codex-companion.mjs result [job-id] [--json]",
Expand Down Expand Up @@ -121,7 +120,7 @@ function normalizeReasoningEffort(effort) {
}
if (!VALID_REASONING_EFFORTS.has(normalized)) {
throw new Error(
`Unsupported reasoning effort "${effort}". Use one of: none, minimal, low, medium, high, xhigh.`
`Unsupported reasoning effort "${effort}". Use one of: ${REASONING_EFFORTS.join(", ")}.`
);
}
return normalized;
Expand Down Expand Up @@ -398,6 +397,7 @@ async function executeReviewRun(request) {
exitStatus: result.status,
threadId: result.threadId,
turnId: result.turnId,
resolved: result.resolved,
payload,
rendered,
summary: firstMeaningfulLine(result.reviewText, `${reviewName} completed.`),
Expand Down Expand Up @@ -446,6 +446,7 @@ async function executeReviewRun(request) {
exitStatus: result.status,
threadId: result.threadId,
turnId: result.turnId,
resolved: result.resolved,
payload,
rendered: renderReviewResult(parsed, {
reviewLabel: reviewName,
Expand Down Expand Up @@ -522,6 +523,7 @@ async function executeTaskRun(request) {
exitStatus: result.status,
threadId: result.threadId,
turnId: result.turnId,
resolved: result.resolved,
payload,
rendered,
summary: firstMeaningfulLine(rawOutput, firstMeaningfulLine(failureMessage, `${taskMetadata.title} finished.`)),
Expand Down
Loading