openai / openai/codex-plugin-cc
`task` prompts passed as a single argument are re-tokenized: quotes/backslashes stripped, prose `--model`/`--write` hijacked as real options
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 33.3k
- Forks
- 2.3k
- PR merge metrics
- No merged PRs in 30d
Description
Environment
- Plugin: codex-plugin-cc v1.0.6 (Claude Code marketplace
openai-codex) - Codex CLI: codex-cli 0.144.1
- Claude Code: 2.1.206
- OS: Windows 11 (the backslash stripping makes this especially destructive on Windows, but the defect is cross-platform)
Summary
When codex-companion.mjs task receives the prompt as a single argv token (the natural shape when an agent runs node codex-companion.mjs task "<prompt>"), normalizeArgv re-tokenizes the whole prompt through splitRawArgumentString and re-parses it with parseArgs. Three corruptions result:
splitRawArgumentStringtreats every backslash as an escape and every quote as a shell quote, so Windows paths collapse (C:\Users\me\projectbecomesC:Usersmeproject) and quoted strings lose their quotes. Newlines collapse to spaces.parseArgsthen consumes flag-looking words inside the prose as real options. A prompt containing the sentence "swap to claude -p --model claude-haiku --output-format json" silently setsoptions.model = "claude-haiku"and deletes those words from the prompt. A prose--writeflips the sandbox from read-only to workspace-write.--cwd,--effort,--prompt-file,--resume-last, and--freshare equally hijackable;--prompt-filein prose makes the run crash with ENOENT on whatever word follows.- The corrupted remainder is re-joined and sent to Codex, so the model receives silently mangled instructions.
Because none of this happens when extra argv tokens follow the prompt (argv.length > 1 skips the split), failures depend on invocation shape and prompt content. From the user side it presents as "Codex only works sometimes," which took a while to root-cause.
Prompts about CLI work naturally contain flag strings and file paths, so the hijack conditions are common in exactly the prompts this plugin exists to forward.
Reproduction (no Codex call needed, parser only)
// repro.mjs -- run: node repro.mjs
import { parseArgs, splitRawArgumentString } from "./scripts/lib/args.mjs";
const prompt = `Review the plan: swap to claude -p --model claude-haiku --output-format json.
Config lives at C:\\Users\\me\\Projects\\my-app\\config\\app-config.json.
The log line was "RUN ERROR: exit 1: Not logged in" and then --write the summary.`;
// Exactly what normalizeArgv does when the prompt is the sole argv token:
const tokens = splitRawArgumentString(prompt);
const parsed = parseArgs(tokens, {
valueOptions: ["model", "effort", "cwd", "prompt-file"],
booleanOptions: ["json", "write", "resume-last", "resume", "fresh", "background"],
aliasMap: { m: "model" }
});
console.log("OPTIONS HIJACKED:", JSON.stringify(parsed.options));
console.log("PROMPT CODEX GETS:", parsed.positionals.join(" "));
Observed output:
OPTIONS HIJACKED: {"model":"claude-haiku","write":true}
PROMPT CODEX GETS: Review the plan: swap to claude -p --output-format json. Config lives at C:UsersmeProjectsmy-appconfigapp-config.json. The log line was RUN ERROR: exit 1: Not logged in and then the summary.
Note the hijacked model, the silent write-mode escalation, the destroyed Windows path, and the stripped quotes.
Root cause
scripts/codex-companion.mjsnormalizeArgv(~line 130):argv.length === 1triggerssplitRawArgumentString(raw).scripts/lib/args.mjssplitRawArgumentString: shell-style unescaping of content that is not shell input.scripts/lib/args.mjsparseArgs: no positional/flag boundary, so option parsing continues into prose.
Suggested fix
Retire the single-argument re-tokenization heuristic for free-text payloads, or gate it behind an explicit raw-mode flag. At minimum, stop option parsing at the first positional token so prose can never be consumed as options. --prompt-file is a workable workaround for task, but review/adversarial-review focus text (positionals in handleReviewCommand) flows through the same path and has no file-based escape hatch.
Related findings from the same debugging session
- No turn timeout:
runAppServerTurn/captureTurn(scripts/lib/codex.mjs) wait onstate.completionindefinitely; only the session-import path has a timeout. We observed a foregroundtaskfreeze mid-turn and sit silent for 45+ minutes with no error and no job-state update. A configurable turn timeout that interrupts via the live client and records a terminal failure would make hangs diagnosable. --write falseis a footgun: boolean flags are presence-only, so--write falseenables write mode AND appends the stray token "false" to the prompt. Either support thefalsevalue or error on it.- Job-state races:
scripts/lib/state.mjsdoes unlocked read-modify-write withwriteFileSync; two concurrent companion processes can clobber each other's job state. - Docs: the
taskusage text omits the accepted--prompt-fileoption.
Happy to provide the full debugging transcript details if useful. (Diagnosis was cross-checked by Codex itself, gpt-5.6-sol at high reasoning effort, reviewing the plugin source through a patched invocation rail; it independently confirmed the mangling path and located the .trim() and timeout behaviors cited above.)
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with normalizeArgv in scripts/codex-companion.mjs and splitRawArgumentString and parseArgs in scripts/lib/args.mjs. Run the issue's repro.mjs to observe quote, backslash, and prose-flag corruption, then trace task and review argument handling. Done means single-argument prompts preserve their text and prose flags are not treated as options, with coverage for the reported cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript, node.js
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100