You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
scan --headless prints periodic progress lines carrying phase, file counts, token counts, and a running cost estimate. scan-components --headless prints only component lifecycle transitions. During a long multi-component run there is no phase, no file coverage, no token counts, and no cost until the whole run ends.
Reproduction
Plan components for a repository (scan-components <repo> --auto --plan-only --headless).
Run scan-components headless in CI with the resulting components.json.
Watch the job log.
Actual behavior
The log holds one line per component transition and nothing else:
codex-security: Web application started
(… hours of silence …)
codex-security: Matching component findings by root cause...
Component scans: 12 complete, 0 incomplete, 0 failed.
In our case one component held ~96% of a ~3,200-file repository, so a single started line was followed by silence for the whole run. Compare scan --headless on the same host, which prints:
dist/component-scan.js — the per-component observers (onProgress, onActivity, onSessionEvent, onCost, onWorkerStatus, onWarning) are constructed only when options.onScanEvent !== undefined, so each security.run(...) runs with no observers at all in headless mode.
dist/cli.js:2457-2461 — the dashboard is gated on !options.headless && !options.planOnly && errorOutput.isTTY === true && environment["CI"] === undefined && environment["TERM"] !== "dumb", so a CI runner can never take the dashboard path.
dist/cli.js:2515-2519 — onProgress is the only non-dashboard writer, and it reports component status transitions only.
Net: the only surface that consumes cost and progress events for component scans is the one surface CI cannot use.
No way to observe spend against --max-cost while it accrues. The per-component cost reaches summary.json only when the run completes, so a run cancelled midway leaves no cost record at all even though the spend happened.
Possible fix
When dashboard === null, wire onScanEvent to a line writer rather than leaving it undefined — at minimum forwarding cost and progress events prefixed with the component name, rate-limited the way scan --headless already does:
codex-security: Web application | reviewing files 18/211 | tokens 7,253,536 in / 63,967 out | cost $7.69
An explicit --progress flag would work equally well if silence is the intended default for headless.
Happy to test a patch against a real multi-component run.
Environment
@openai/codex-security0.1.25 (same behavior on 0.1.24)ubuntu-latest, Node 22.13.0, non-interactive (CI=true, no TTY)Summary
scan --headlessprints periodic progress lines carrying phase, file counts, token counts, and a running cost estimate.scan-components --headlessprints only component lifecycle transitions. During a long multi-component run there is no phase, no file coverage, no token counts, and no cost until the whole run ends.Reproduction
scan-components <repo> --auto --plan-only --headless).scan-componentsheadless in CI with the resultingcomponents.json.Actual behavior
The log holds one line per component transition and nothing else:
In our case one component held ~96% of a ~3,200-file repository, so a single
startedline was followed by silence for the whole run. Comparescan --headlesson the same host, which prints:Expected behavior
Per-component progress and running cost in headless mode, comparable to what
scan --headlessalready provides.Where it comes from (0.1.25
dist)dist/cli.js:2504— the scan-event observer is wired to the dashboard or to nothing:dist/component-scan.js— the per-component observers (onProgress,onActivity,onSessionEvent,onCost,onWorkerStatus,onWarning) are constructed only whenoptions.onScanEvent !== undefined, so eachsecurity.run(...)runs with no observers at all in headless mode.dist/cli.js:2457-2461— the dashboard is gated on!options.headless && !options.planOnly && errorOutput.isTTY === true && environment["CI"] === undefined && environment["TERM"] !== "dumb", so a CI runner can never take the dashboard path.dist/cli.js:2515-2519—onProgressis the only non-dashboard writer, and it reports component status transitions only.Net: the only surface that consumes cost and progress events for component scans is the one surface CI cannot use.
Impact
scan-components.--max-costwhile it accrues. The per-componentcostreachessummary.jsononly when the run completes, so a run cancelled midway leaves no cost record at all even though the spend happened.Possible fix
When
dashboard === null, wireonScanEventto a line writer rather than leaving itundefined— at minimum forwardingcostandprogressevents prefixed with the component name, rate-limited the wayscan --headlessalready does:An explicit
--progressflag would work equally well if silence is the intended default for headless.Happy to test a patch against a real multi-component run.