openai / openai/codex-security
Bulk-scan save fails with "scan.target.kind: must match the workbench target" on clean worktree checkouts
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 10.8k
- Forks
- 801
- Avg merge
- 1d 8h
- Merged PRs (30d)
- 257
Description
Summary
A bulk-scan campaign (CSV inventory, full-repository standard scan, no --path scope) completes its full analysis and authors all canonical artifacts, but the final save/seal step fails with scan.target.kind: must match the workbench target. The scan is left unsealed, report.md is not generated, and the result is recorded as failed even though findings.json, coverage.json, and scan-manifest.json are all present on disk with complete content.
Related to #50 (same error message, different scenario and root cause). #50 is a path-scoped scan where the manifest records the wrong target kind. This issue is a full-repository bulk-scan where the manifest records the correct target kind (git_worktree), but the workbench rejects it because expected_target_kinds is too restrictive for clean worktree checkouts.
Environment
@openai/codex-security0.1.1- macOS (Apple Silicon)
- Node.js 24, Python 3.10+
- Authentication: stored ChatGPT credentials
Reproduction
- Create a CSV inventory with at least one full-repository row (no
scopecolumn,mode=standard), pinning a real 40-character commit SHA. - Run a bulk scan:
npx codex-security bulk-scan repositories.csv --output-dir ./results --workers 1
- Observe that the scan completes (findings and coverage are authored), but the save step fails.
Observed behavior
The scan runs to completion — findings.json, coverage.json, and scan-manifest.json are all written to the attempt artifact directory with complete content. Then:
codex-security: Could not save the Codex Security scan: scan.target.kind: must match the workbench target
The attempt is recorded as status: failed in results.jsonl. report.md is not generated. The scan cannot be exported (codex-security export reports "SARIF projection requires a sealed scan") because the scan was never sealed.
The authored scan-manifest.json records:
{
"scan": {
"target": {
"kind": "git_worktree",
"revision": "<pinned 40-char SHA>",
...
}
}
}
Root cause
_bundled_plugin/scripts/workbench_db.py — expected_target_kinds():
def expected_target_kinds(scan: sqlite3.Row) -> list[str]:
if scan["mode"] == "diff":
return ["git_diff"]
if scan["target_revision"] == "unversioned":
return ["directory_snapshot"]
if scan["target_snapshot_digest"] is None:
return ["git_worktree", "git_revision"]
if scan["target_snapshot_digest"] == clean_worktree_content_digest():
return ["git_revision"] # <-- too restrictive
return ["git_worktree"]
When the worktree content digest matches clean_worktree_content_digest() (i.e., no uncommitted changes — which is the normal case for a bulk-scan checkout), the function returns only ["git_revision"]. But the bulk-scan checkout creates a worktree via git init + git fetch --depth=1 + git checkout --detach, and the scan plugin correctly records target.kind: "git_worktree" in the manifest. The save step then rejects the manifest because "git_worktree" is not in the allowed list ["git_revision"].
Expected behavior
A clean worktree checkout is still a worktree. expected_target_kinds should accept git_worktree alongside git_revision when the worktree content is clean, the same way it already does when target_snapshot_digest is None:
if scan["target_snapshot_digest"] == clean_worktree_content_digest():
return ["git_revision", "git_worktree"]
This matches the existing behavior two branches above (the None digest case returns both kinds). A completed scan with correct artifacts should not fail to save based on a target-kind distinction that doesn't affect the scan's correctness.
Impact
Any bulk-scan campaign that checks out a clean worktree (the default and expected case) can hit this. The scan's full analysis is discarded from the sealed result history, report.md is never generated, and the result cannot be exported — even though all canonical artifacts exist on disk with complete content. The attempt is recorded as failed, which is misleading for CI policy and campaign completion tracking.
Contributor guide
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 in _bundled_plugin/scripts/workbench_db.py at expected_target_kinds() and inspect how clean worktree digests determine accepted target kinds. Reproduce with the documented bulk-scan command, then verify that a clean checkout saves and seals successfully, generates report.md, and remains exportable with its existing artifacts.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- cli, security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100