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

Open
#512 0 comments 0 reactions 0 assignees View on GitHub

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:

  1. splitRawArgumentString treats every backslash as an escape and every quote as a shell quote, so Windows paths collapse (C:\Users\me\project becomes C:Usersmeproject) and quoted strings lose their quotes. Newlines collapse to spaces.
  2. parseArgs then 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 sets options.model = "claude-haiku" and deletes those words from the prompt. A prose --write flips the sandbox from read-only to workspace-write. --cwd, --effort, --prompt-file, --resume-last, and --fresh are equally hijackable; --prompt-file in prose makes the run crash with ENOENT on whatever word follows.
  3. 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.mjs normalizeArgv (~line 130): argv.length === 1 triggers splitRawArgumentString(raw).
  • scripts/lib/args.mjs splitRawArgumentString: shell-style unescaping of content that is not shell input.
  • scripts/lib/args.mjs parseArgs: 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

  1. No turn timeout: runAppServerTurn/captureTurn (scripts/lib/codex.mjs) wait on state.completion indefinitely; only the session-import path has a timeout. We observed a foreground task freeze 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.
  2. --write false is a footgun: boolean flags are presence-only, so --write false enables write mode AND appends the stray token "false" to the prompt. Either support the false value or error on it.
  3. Job-state races: scripts/lib/state.mjs does unlocked read-modify-write with writeFileSync; two concurrent companion processes can clobber each other's job state.
  4. Docs: the task usage text omits the accepted --prompt-file option.

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.