Skip to content

Commit 06930d6

Browse files
committed
fix(mcp-bridge): escape codex positional prompt arg with --
Codex interprets file content starting with --- as flags. Add -- separator before the prompt arg to prevent parsing. Also refine long prompt handling: codex uses stdin but claude/gemini/grok require prompt as -p argument.
1 parent e28b6a7 commit 06930d6

1 file changed

Lines changed: 28 additions & 8 deletions

File tree

src/mcp/bridge/backends.rs

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ pub struct CommandSpec {
146146
pub stdin_prompt: Option<String>,
147147
}
148148

149-
/// Byte threshold above which prompts are delivered via stdin instead of CLI
150-
/// arg to avoid OS `ARG_MAX` limits. ~100K chars ≈ 100 KB.
149+
/// Byte threshold above which prompts are delivered via stdin for backends
150+
/// that support stdin prompt input (currently codex). ~100K chars ≈ 100 KB.
151151
const STDIN_PROMPT_THRESHOLD: usize = 100_000;
152152

153153
/// Build the command arguments for invoking a backend with a prompt.
@@ -210,6 +210,9 @@ pub fn build_command_args(
210210
if final_prompt.len() > STDIN_PROMPT_THRESHOLD {
211211
stdin_prompt = Some(final_prompt);
212212
} else {
213+
// Use `--` to prevent prompt content from being parsed as flags
214+
// (e.g. file content starting with `---`)
215+
args.push("--".to_string());
213216
args.push(final_prompt);
214217
}
215218
} else {
@@ -263,9 +266,14 @@ pub fn build_command_args(
263266
prompt.to_string()
264267
};
265268
if final_prompt.len() > STDIN_PROMPT_THRESHOLD {
266-
// Placeholder arg for -p; actual prompt comes via stdin
267-
args.push("(prompt via stdin)".to_string());
268-
stdin_prompt = Some(final_prompt);
269+
// claude/gemini/grok require a positional value for -p. Feeding stdin
270+
// here produces incorrect results because the backend reads the -p arg.
271+
// Keep the prompt as an argument for correctness.
272+
tracing::warn!(
273+
"prompt exceeds threshold for backend '{}'; passing via argv",
274+
backend.name
275+
);
276+
args.push(final_prompt);
269277
} else if final_prompt.starts_with('-') {
270278
args.push(format!(" {}", final_prompt));
271279
} else {
@@ -374,7 +382,10 @@ mod tests {
374382
let backend = make_backend("codex");
375383
let spec = build_command_args(&backend, "hello", None, None, true, None);
376384
assert_eq!(spec.binary, "/usr/bin/codex");
377-
assert_eq!(spec.args, vec!["exec", "--full-auto", "--json", "hello"]);
385+
assert_eq!(
386+
spec.args,
387+
vec!["exec", "--full-auto", "--json", "--", "hello"]
388+
);
378389
}
379390

380391
#[test]
@@ -441,14 +452,23 @@ mod tests {
441452
}
442453

443454
#[test]
444-
fn test_build_command_args_stdin_for_long_prompt() {
445-
let backend = make_backend("claude");
455+
fn test_build_command_args_stdin_for_long_prompt_codex() {
456+
let backend = make_backend("codex");
446457
let long_prompt = "x".repeat(STDIN_PROMPT_THRESHOLD + 1);
447458
let spec = build_command_args(&backend, &long_prompt, None, None, true, None);
448459
assert!(spec.stdin_prompt.is_some());
449460
assert_eq!(spec.stdin_prompt.as_ref().unwrap().len(), long_prompt.len());
450461
}
451462

463+
#[test]
464+
fn test_build_command_args_no_stdin_for_long_prompt_claude() {
465+
let backend = make_backend("claude");
466+
let long_prompt = "x".repeat(STDIN_PROMPT_THRESHOLD + 1);
467+
let spec = build_command_args(&backend, &long_prompt, None, None, true, None);
468+
assert!(spec.stdin_prompt.is_none());
469+
assert_eq!(spec.args.last(), Some(&long_prompt));
470+
}
471+
452472
#[test]
453473
fn test_all_backend_specs_returns_all() {
454474
let specs = all_backend_specs();

0 commit comments

Comments
 (0)