Skip to content
Open
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
20 changes: 20 additions & 0 deletions plugins/codex/commands/auto-calling.md
Original file line number Diff line number Diff line change
@@ -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.
34 changes: 27 additions & 7 deletions plugins/codex/scripts/codex-companion.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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 = {
Expand Down Expand Up @@ -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
});
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
};
Expand Down Expand Up @@ -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"
}
});

Expand Down Expand Up @@ -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
Expand All @@ -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"
}
});

Expand All @@ -755,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);
Expand All @@ -766,6 +784,7 @@ async function handleTask(argv) {
effort,
prompt,
write,
sandbox,
resumeLast,
jobId: job.id
});
Expand All @@ -784,6 +803,7 @@ async function handleTask(argv) {
effort,
prompt,
write,
sandbox,
resumeLast,
jobId: job.id,
onProgress: progress
Expand Down
2 changes: 1 addition & 1 deletion plugins/codex/scripts/lib/codex.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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
});
Expand Down