From 60b6f2714ba10a2e737071606c83e80cf5229054 Mon Sep 17 00:00:00 2001 From: Bry Date: Tue, 14 Apr 2026 14:19:44 +0000 Subject: [PATCH 1/3] feat: add --sandbox flag and CODEX_SANDBOX env var for review and task commands (#167) Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/codex/scripts/codex-companion.mjs | 32 ++++++++++++++++++----- plugins/codex/scripts/lib/codex.mjs | 2 +- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/plugins/codex/scripts/codex-companion.mjs b/plugins/codex/scripts/codex-companion.mjs index 35222fd5..47b8579c 100644 --- a/plugins/codex/scripts/codex-companion.mjs +++ b/plugins/codex/scripts/codex-companion.mjs @@ -246,6 +246,16 @@ function buildAdversarialReviewPrompt(context, focusText) { }); } +const VALID_SANDBOX_MODES = new Set(["read-only", "workspace-write", "danger-full-access"]); + +function resolveSandbox(explicit, fallback = "read-only") { + const value = explicit ?? process.env.CODEX_SANDBOX ?? fallback; + if (!VALID_SANDBOX_MODES.has(value)) { + throw new Error(`Invalid sandbox mode "${value}". Must be one of: ${[...VALID_SANDBOX_MODES].join(", ")}`); + } + return value; +} + function ensureCodexAvailable(cwd) { const availability = getCodexAvailability(cwd); if (!availability.available) { @@ -356,6 +366,7 @@ async function executeReviewRun(request) { ensureCodexAvailable(request.cwd); ensureGitRepository(request.cwd); + const sandbox = resolveSandbox(request.sandbox); const target = resolveReviewTarget(request.cwd, { base: request.base, scope: request.scope @@ -367,6 +378,7 @@ async function executeReviewRun(request) { const result = await runAppServerReview(request.cwd, { target: reviewTarget, model: request.model, + sandbox, onProgress: request.onProgress }); const payload = { @@ -408,7 +420,7 @@ async function executeReviewRun(request) { const result = await runAppServerTurn(context.repoRoot, { prompt, model: request.model, - sandbox: "read-only", + sandbox, outputSchema: readOutputSchema(REVIEW_SCHEMA), onProgress: request.onProgress }); @@ -485,7 +497,7 @@ async function executeTaskRun(request) { defaultPrompt: resumeThreadId ? DEFAULT_CONTINUE_PROMPT : "", model: request.model, effort: request.effort, - sandbox: request.write ? "workspace-write" : "read-only", + sandbox: resolveSandbox(request.sandbox, request.write ? "workspace-write" : "read-only"), onProgress: request.onProgress, persistThread: true, threadName: resumeThreadId ? null : buildPersistentTaskThreadName(request.prompt || DEFAULT_CONTINUE_PROMPT) @@ -598,13 +610,14 @@ function buildTaskJob(workspaceRoot, taskMetadata, write) { }); } -function buildTaskRequest({ cwd, model, effort, prompt, write, resumeLast, jobId }) { +function buildTaskRequest({ cwd, model, effort, prompt, write, sandbox, resumeLast, jobId }) { return { cwd, model, effort, prompt, write, + sandbox, resumeLast, jobId }; @@ -681,10 +694,11 @@ function enqueueBackgroundTask(cwd, job, request) { async function handleReviewCommand(argv, config) { const { options, positionals } = parseCommandInput(argv, { - valueOptions: ["base", "scope", "model", "cwd"], + valueOptions: ["base", "scope", "model", "sandbox", "cwd"], booleanOptions: ["json", "background", "wait"], aliasMap: { - m: "model" + m: "model", + s: "sandbox" } }); @@ -714,6 +728,7 @@ async function handleReviewCommand(argv, config) { base: options.base, scope: options.scope, model: options.model, + sandbox: options.sandbox, focusText, reviewName: config.reviewName, onProgress: progress @@ -731,10 +746,11 @@ async function handleReview(argv) { async function handleTask(argv) { const { options, positionals } = parseCommandInput(argv, { - valueOptions: ["model", "effort", "cwd", "prompt-file"], + valueOptions: ["model", "effort", "sandbox", "cwd", "prompt-file"], booleanOptions: ["json", "write", "resume-last", "resume", "fresh", "background"], aliasMap: { - m: "model" + m: "model", + s: "sandbox" } }); @@ -766,6 +782,7 @@ async function handleTask(argv) { effort, prompt, write, + sandbox: options.sandbox, resumeLast, jobId: job.id }); @@ -784,6 +801,7 @@ async function handleTask(argv) { effort, prompt, write, + sandbox: options.sandbox, resumeLast, jobId: job.id, onProgress: progress diff --git a/plugins/codex/scripts/lib/codex.mjs b/plugins/codex/scripts/lib/codex.mjs index f2fe88bd..4be01971 100644 --- a/plugins/codex/scripts/lib/codex.mjs +++ b/plugins/codex/scripts/lib/codex.mjs @@ -915,7 +915,7 @@ export async function runAppServerReview(cwd, options = {}) { emitProgress(options.onProgress, "Starting Codex review thread.", "starting"); const thread = await startThread(client, cwd, { model: options.model, - sandbox: "read-only", + sandbox: options.sandbox ?? "read-only", ephemeral: true, threadName: options.threadName }); From 9074d537ee25bde22278eeeed670e7212cd14464 Mon Sep 17 00:00:00 2001 From: Bry Date: Tue, 14 Apr 2026 14:40:54 +0000 Subject: [PATCH 2/3] feat: add /codex:auto-calling toggle for model invocation (#167) Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/codex/commands/auto-calling.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 plugins/codex/commands/auto-calling.md diff --git a/plugins/codex/commands/auto-calling.md b/plugins/codex/commands/auto-calling.md new file mode 100644 index 00000000..a9ce8c10 --- /dev/null +++ b/plugins/codex/commands/auto-calling.md @@ -0,0 +1,20 @@ +--- +description: Toggle whether Claude can invoke Codex commands programmatically (e.g. from /loop or automated workflows) +argument-hint: '[enable|disable]' +disable-model-invocation: true +allowed-tools: Bash, Glob, Grep, Read +--- + +Toggle `disable-model-invocation` on Codex plugin commands. + +The argument is `$ARGUMENTS`. If empty, check current state and report it. + +Target files: all `.md` files in `${CLAUDE_PLUGIN_ROOT}/commands/` that have `disable-model-invocation: true`: `review.md`, `adversarial-review.md`, `cancel.md`, `result.md`, `status.md`. + +## Rules + +- **`enable`**: Remove the line `disable-model-invocation: true` from the YAML frontmatter of each target file. This allows Claude to invoke these commands programmatically. +- **`disable`**: Add `disable-model-invocation: true` back to the YAML frontmatter (after the `description:` line) of each target file. This restores the default behavior. +- **No argument**: Read the target files and report whether auto-calling is currently enabled or disabled. + +After making changes, report which files were modified. From 38cbe4bbb5184d4aa298fdd0e49882e99f611bd7 Mon Sep 17 00:00:00 2001 From: Bry Date: Tue, 14 Apr 2026 15:01:46 +0000 Subject: [PATCH 3/3] fix: validate sandbox mode before enqueuing background tasks Co-Authored-By: Claude Opus 4.6 (1M context) --- plugins/codex/scripts/codex-companion.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/codex/scripts/codex-companion.mjs b/plugins/codex/scripts/codex-companion.mjs index 47b8579c..d9d8afd6 100644 --- a/plugins/codex/scripts/codex-companion.mjs +++ b/plugins/codex/scripts/codex-companion.mjs @@ -771,6 +771,8 @@ async function handleTask(argv) { resumeLast }); + const sandbox = resolveSandbox(options.sandbox, write ? "workspace-write" : "read-only"); + if (options.background) { ensureCodexAvailable(cwd); requireTaskRequest(prompt, resumeLast); @@ -782,7 +784,7 @@ async function handleTask(argv) { effort, prompt, write, - sandbox: options.sandbox, + sandbox, resumeLast, jobId: job.id }); @@ -801,7 +803,7 @@ async function handleTask(argv) { effort, prompt, write, - sandbox: options.sandbox, + sandbox, resumeLast, jobId: job.id, onProgress: progress