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
6 changes: 4 additions & 2 deletions src/__tests__/integration/group-chat-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,15 @@ const AGENTS: AgentConfig[] = [
* Codex does NOT support --input-format stream-json (supportsStreamJsonInput: false)
*/
buildArgs: (prompt: string, options?: { images?: string[] }) => {
// `-C` precedes `exec` because Codex treats it as a root-level global
// flag — placing it after the subcommand makes it silently ignored (#959).
const args = [
'-C',
TEST_CWD,
'exec',
'--dangerously-bypass-approvals-and-sandbox',
'--skip-git-repo-check',
'--json',
'-C',
TEST_CWD,
];

// IMPORTANT: This mirrors process-manager.ts logic
Expand Down
31 changes: 16 additions & 15 deletions src/__tests__/integration/provider-integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -372,20 +372,21 @@ const PROVIDERS: ProviderConfig[] = [
*/
buildInitialArgs: (prompt: string, options?: { images?: string[] }) => {
// Codex arg order from process.ts IPC handler:
// 1. batchModePrefix: ['exec']
// 2. base args: [] (empty for Codex)
// 3. batchModeArgs: ['--dangerously-bypass-approvals-and-sandbox', '--skip-git-repo-check']
// 4. jsonOutputArgs: ['--json']
// 5. workingDirArgs: ['-C', dir]
// 1. workingDirArgs: ['-C', dir] (must precede the `exec` subcommand
// because Codex treats `-C` as a root-level global flag — see #959)
// 2. batchModePrefix: ['exec']
// 3. base args: [] (empty for Codex)
// 4. batchModeArgs: ['--dangerously-bypass-approvals-and-sandbox', '--skip-git-repo-check']
// 5. jsonOutputArgs: ['--json']
// 6. prompt via '--' separator (process-manager.ts)

const args = [
'-C',
TEST_CWD,
'exec',
'--dangerously-bypass-approvals-and-sandbox',
'--skip-git-repo-check',
'--json',
'-C',
TEST_CWD,
];

// IMPORTANT: This mirrors process-manager.ts logic
Expand All @@ -404,12 +405,12 @@ const PROVIDERS: ProviderConfig[] = [
return [...args, '--', prompt];
},
buildResumeArgs: (sessionId: string, prompt: string) => [
'-C',
TEST_CWD,
'exec',
'--dangerously-bypass-approvals-and-sandbox',
'--skip-git-repo-check',
'--json',
'-C',
TEST_CWD,
'resume',
sessionId,
'--',
Expand All @@ -420,20 +421,20 @@ const PROVIDERS: ProviderConfig[] = [
* Codex uses file-based image args (-i) - images decoded on remote via script.
*/
buildSshArgs: () => [
'-C',
TEST_CWD,
'exec',
'--dangerously-bypass-approvals-and-sandbox',
'--skip-git-repo-check',
'--json',
'-C',
TEST_CWD,
],
buildSshResumeArgs: (sessionId: string) => [
'-C',
TEST_CWD,
'exec',
'--dangerously-bypass-approvals-and-sandbox',
'--skip-git-repo-check',
'--json',
'-C',
TEST_CWD,
'resume',
sessionId,
],
Expand Down Expand Up @@ -487,12 +488,12 @@ const PROVIDERS: ProviderConfig[] = [
* Mirrors agent-detector.ts: imageArgs: (imagePath) => ['-i', imagePath]
*/
buildImageArgs: (prompt: string, imagePath: string) => [
'-C',
TEST_CWD,
'exec',
'--dangerously-bypass-approvals-and-sandbox',
'--skip-git-repo-check',
'--json',
'-C',
TEST_CWD,
'-i',
imagePath,
'--',
Expand Down
28 changes: 23 additions & 5 deletions src/__tests__/main/utils/agent-args.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,31 @@ describe('buildAgentArgs', () => {
});

// -- workingDirArgs --
it('adds workingDirArgs when cwd provided', () => {
it('prepends workingDirArgs when cwd provided', () => {
// Codex treats `-C` as a root-level global flag — it must appear before
// any subcommand (e.g. `exec`) or it is silently ignored (#959).
const agent = makeAgent({
workingDirArgs: (dir: string) => ['-C', dir],
});
const result = buildAgentArgs(agent, {
baseArgs: ['--print'],
cwd: '/home/user/project',
});
expect(result).toEqual(['--print', '-C', '/home/user/project']);
expect(result).toEqual(['-C', '/home/user/project', '--print']);
});

it('places workingDirArgs before batchModePrefix subcommand', () => {
// Regression: -C must land before `exec` so Codex picks up the cwd.
const agent = makeAgent({
batchModePrefix: ['exec'],
workingDirArgs: (dir: string) => ['-C', dir],
});
const result = buildAgentArgs(agent, {
baseArgs: ['--json'],
prompt: 'do stuff',
cwd: '/home/user/project',
});
expect(result).toEqual(['-C', '/home/user/project', 'exec', '--json']);
});

it('does not add workingDirArgs when cwd is not provided', () => {
Expand Down Expand Up @@ -253,14 +269,16 @@ describe('buildAgentArgs', () => {
});

// batchModeArgs (--skip-git) is omitted when readOnlyMode is true —
// batch mode args grant write/approval permissions that conflict with read-only
// batch mode args grant write/approval permissions that conflict with read-only.
// workingDirArgs (-C /tmp) is prepended so the directory flag lands before
// the batchModePrefix subcommand (#959).
expect(result).toEqual([
'-C',
'/tmp',
'run',
'--print',
'--format',
'json',
'-C',
'/tmp',
'--agent',
'plan',
'--model',
Expand Down
11 changes: 6 additions & 5 deletions src/cli/services/agent-spawner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,12 @@ async function spawnJsonLineAgent(

// Build args from agent definition
const args: string[] = [];
// Codex requires explicit working directory arg (other agents use process cwd).
// Must come before batchModePrefix because Codex treats `-C` as a root-level
// global flag that's silently ignored if it appears after the `exec` subcommand (#959).
if (toolType === 'codex' && def?.workingDirArgs) {
args.push(...def.workingDirArgs(cwd));
}
if (def?.batchModePrefix) args.push(...def.batchModePrefix);
if (def?.batchModeArgs) args.push(...def.batchModeArgs);
if (def?.jsonOutputArgs) args.push(...def.jsonOutputArgs);
Expand All @@ -365,11 +371,6 @@ async function spawnJsonLineAgent(
args.push(...def.resumeArgs(agentSessionId));
}

// Codex requires explicit working directory arg (other agents use process cwd)
if (toolType === 'codex' && def?.workingDirArgs) {
args.push(...def.workingDirArgs(cwd));
}

// Add prompt (with or without '--' separator depending on agent)
if (!def?.noPromptSeparator) {
args.push('--');
Expand Down
4 changes: 3 additions & 1 deletion src/main/agents/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,9 @@ export const AGENT_DEFINITIONS: AgentDefinition[] = [
// Base args for interactive mode (no flags that are exec-only)
args: [],
// Codex CLI argument builders
// Batch mode: codex exec --json --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check [--sandbox read-only] [-C dir] [resume <id>] -- "prompt"
// Batch mode: codex [-C dir] exec --json --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check [--sandbox read-only] [resume <id>] -- "prompt"
// `-C` is a root-level global flag and MUST appear before the `exec` subcommand
// or Codex silently ignores it (see #959). buildAgentArgs prepends workingDirArgs accordingly.
// Sandbox modes:
// - Default (YOLO): --dangerously-bypass-approvals-and-sandbox (full system access, required by Maestro)
// - Read-only: --sandbox read-only (can only read files, overrides YOLO)
Expand Down
5 changes: 4 additions & 1 deletion src/main/utils/agent-args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@ export function buildAgentArgs(
}

if (agent.workingDirArgs && options.cwd) {
finalArgs = [...finalArgs, ...agent.workingDirArgs(options.cwd)];
// Prepend so the directory flag lands before any subcommand (e.g. Codex
// `exec`). Codex treats `-C` as a root-level global flag — placing it
// after the subcommand makes it silently ignored (#959).
finalArgs = [...agent.workingDirArgs(options.cwd), ...finalArgs];
}

if (options.readOnlyMode && agent.readOnlyArgs) {
Expand Down
Loading