Summary
A standard repository scan completed threat modeling, all-file review, finding generation, validation, coverage, manifest sealing, and report generation, but the CLI exited 2 during final workbench save:
Could not save the Codex Security scan: manifest.scan.id: must match the workbench scan
This appears related to #50 (same completed-analysis / failed-save family), but the contract mismatch here is the scan ID rather than target kind.
Environment
@openai/codex-security: 0.1.7 (npm latest at time of test)
- Bundled plugin:
0.1.15
- Bundled Codex SDK:
0.144.6
- Node:
v22.22.2
- Python:
3.10.12 with tomli in an isolated copied venv
- OS: WSL2 Linux
- Mode:
standard
- Target: clean 3-file local Git repository
- External provider: OpenRouter with
deepseek/deepseek-v4-pro
No API key is included in this report.
Reproduction
Use a new output directory outside a clean Git repository:
npx -y @openai/codex-security scan /path/to/repository \
--provider openrouter \
--model deepseek/deepseek-v4-pro \
--mode standard \
--headless \
--output-dir /tmp/codex-security-output \
--python /path/to/copied-venv/bin/python \
--format json
The fixture contains a Flask route where request.args.get("host") is concatenated into os.popen("ping -c 1 " + host).
Observed behavior
The scan ran for about 8 minutes. CLI progress reached:
Scan phase: preflight (0/3 files)
Preflight: worker delegation supported (up to 6 worker slots)
Scan phase: reviewing files (3/3 files)
The output directory contained:
artifacts/01_context/threat_model.md
scan-manifest.json
findings.json
coverage.json
report.md
The manifest had status: "completed"; coverage was complete; the findings file contained one reportable high-confidence CWE-78 command-injection finding with static validation and attack-path fields. All canonical files used the same generated scan ID (77fbdc60-bb3a-4aaf-ba4b-b2e3ea6ffad6).
Despite those artifacts, finalization failed with the error above and exit code 2.
Root-cause evidence
The SDK builds basePrompt before it registers the workbench scan. After registration it has the authoritative scanId, but the prompt sent to the model still says:
Use exactly "$CODEX_SECURITY_SCAN_ID" as the scan ID in the manifest, findings, and coverage.
The SDK exposes CODEX_SECURITY_SCAN_ID only in the runtime environment. In this standard scan, the model completed the scan semantics but authored a different UUID in the canonical artifacts, which the workbench rejected during complete-scan.
A robust fix is to interpolate the registered scanId (and similarly authoritative target values) into the post-registration prompt before thread.runStreamed(prompt), rather than relying on an LLM to resolve shell-style placeholders. This is analogous to the literal placeholder issue described in #290 for deep scans.
Expected behavior
The agent-authored manifest scan ID must match the scan ID registered by the workbench, or the finalizer should reconcile the value safely. A scan with complete canonical artifacts and coverage should save successfully and return a success exit code.
Impact
This prevents use of the CLI exit code as a formal evaluation/CI gate. The on-disk artifacts are useful for diagnosis, but the run cannot be treated as a clean completed scan while workbench persistence fails.
Summary
A
standardrepository scan completed threat modeling, all-file review, finding generation, validation, coverage, manifest sealing, and report generation, but the CLI exited2during final workbench save:This appears related to #50 (same completed-analysis / failed-save family), but the contract mismatch here is the scan ID rather than target kind.
Environment
@openai/codex-security:0.1.7(npmlatestat time of test)0.1.150.144.6v22.22.23.10.12withtomliin an isolated copied venvstandarddeepseek/deepseek-v4-proNo API key is included in this report.
Reproduction
Use a new output directory outside a clean Git repository:
The fixture contains a Flask route where
request.args.get("host")is concatenated intoos.popen("ping -c 1 " + host).Observed behavior
The scan ran for about 8 minutes. CLI progress reached:
The output directory contained:
The manifest had
status: "completed"; coverage wascomplete; the findings file contained one reportable high-confidenceCWE-78command-injection finding with static validation and attack-path fields. All canonical files used the same generated scan ID (77fbdc60-bb3a-4aaf-ba4b-b2e3ea6ffad6).Despite those artifacts, finalization failed with the error above and exit code
2.Root-cause evidence
The SDK builds
basePromptbefore it registers the workbench scan. After registration it has the authoritativescanId, but the prompt sent to the model still says:The SDK exposes
CODEX_SECURITY_SCAN_IDonly in the runtime environment. In this standard scan, the model completed the scan semantics but authored a different UUID in the canonical artifacts, which the workbench rejected duringcomplete-scan.A robust fix is to interpolate the registered
scanId(and similarly authoritative target values) into the post-registration prompt beforethread.runStreamed(prompt), rather than relying on an LLM to resolve shell-style placeholders. This is analogous to the literal placeholder issue described in #290 for deep scans.Expected behavior
The agent-authored manifest scan ID must match the scan ID registered by the workbench, or the finalizer should reconcile the value safely. A scan with complete canonical artifacts and coverage should save successfully and return a success exit code.
Impact
This prevents use of the CLI exit code as a formal evaluation/CI gate. The on-disk artifacts are useful for diagnosis, but the run cannot be treated as a clean completed scan while workbench persistence fails.