openai / openai/codex-security

Bulk-scan save fails with "scan.target.kind: must match the workbench target" on clean worktree checkouts

Open Beginner friendly
#62 0 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

area:bulk-scan area:cost area:python area:reports bug priority:p1
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-security 0.1.1
  • macOS (Apple Silicon)
  • Node.js 24, Python 3.10+
  • Authentication: stored ChatGPT credentials

Reproduction

  1. Create a CSV inventory with at least one full-repository row (no scope column, mode = standard), pinning a real 40-character commit SHA.
  2. Run a bulk scan:
npx codex-security bulk-scan repositories.csv --output-dir ./results --workers 1
  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.pyexpected_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

Open the contributing guide

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 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.