Summary
bulk-scan never observes a repository's run warnings. runMultiscan calls security.run() without an onWarning observer, and the JSONL ledger receipt records status, attempt, outputDir, cost and error but no warnings. A repository whose target drifted mid-run is therefore recorded as status: "completed" with the warning nowhere in the campaign output.
This is the multiscan counterpart of #195. That issue is about a single scan exiting 0 and reporting nothing in --json; the same information is simply absent from bulk-scan, which is the mode where a human is least likely to be watching individual repositories.
Environment
@openai/codex-security 0.1.5, current main at ea19f24
- macOS 15 (Darwin 25.5.0), Node.js 24.11.1, Bun 1.3.14
Where it is
sdk/typescript/src/multiscan.ts — the security.run() call passes no onWarning, and the receipt written for each attempt has no warnings field. Nothing else on the multiscan path carries them: the ledger receipt is the only per-repository record, and the campaign summary aggregates completed/failed/skipped counts only.
Impact
A drifted target is the motivating case, because coverage.completeness stays complete and the attempt genuinely succeeds — so there is no failure, no non-zero status, and no ledger field that would let a campaign consumer notice the results describe a stale tree. The same applies to the other warning producers (a cost limit that could not be verified, and cleanup failures).
Suggested direction
Two parts, both small:
- Pass an
onWarning observer through to security.run() so multiscan sees them, and record them on the attempt receipt alongside error.
- Surface them in the campaign summary the way failures already are, so a resumed or scripted campaign can act on them.
On the receipt field's shape: scans show and scans list already emit their stored warnings key only when non-empty, so matching that — and matching how the receipt's own error and cost fields are omitted when absent — keeps the ledger predictable and keeps an older ledger readable.
For the record, ScanResult carries no warnings field on main today, so the observer is the only available channel; #195 and PR #220 concern surfacing warnings for a single scan, and this issue is about the multiscan ledger regardless of how that lands.
Summary
bulk-scannever observes a repository's run warnings.runMultiscancallssecurity.run()without anonWarningobserver, and the JSONL ledger receipt recordsstatus,attempt,outputDir,costanderrorbut no warnings. A repository whose target drifted mid-run is therefore recorded asstatus: "completed"with the warning nowhere in the campaign output.This is the multiscan counterpart of #195. That issue is about a single
scanexiting 0 and reporting nothing in--json; the same information is simply absent frombulk-scan, which is the mode where a human is least likely to be watching individual repositories.Environment
@openai/codex-security0.1.5, currentmainatea19f24Where it is
sdk/typescript/src/multiscan.ts— thesecurity.run()call passes noonWarning, and the receipt written for each attempt has no warnings field. Nothing else on the multiscan path carries them: the ledger receipt is the only per-repository record, and the campaign summary aggregatescompleted/failed/skippedcounts only.Impact
A drifted target is the motivating case, because
coverage.completenessstayscompleteand the attempt genuinely succeeds — so there is no failure, no non-zero status, and no ledger field that would let a campaign consumer notice the results describe a stale tree. The same applies to the other warning producers (a cost limit that could not be verified, and cleanup failures).Suggested direction
Two parts, both small:
onWarningobserver through tosecurity.run()so multiscan sees them, and record them on the attempt receipt alongsideerror.On the receipt field's shape:
scans showandscans listalready emit their storedwarningskey only when non-empty, so matching that — and matching how the receipt's ownerrorandcostfields are omitted when absent — keeps the ledger predictable and keeps an older ledger readable.For the record,
ScanResultcarries nowarningsfield onmaintoday, so the observer is the only available channel; #195 and PR #220 concern surfacing warnings for a singlescan, and this issue is about the multiscan ledger regardless of how that lands.