Skip to content

fix(cost): enforce verifiable scan spending limits - #465

Draft
mldangelo-oai wants to merge 17 commits into
mainfrom
mdangelo/codex/enforce-verifiable-scan-budgets
Draft

fix(cost): enforce verifiable scan spending limits#465
mldangelo-oai wants to merge 17 commits into
mainfrom
mdangelo/codex/enforce-verifiable-scan-budgets

Conversation

@mldangelo-oai

@mldangelo-oai mldangelo-oai commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

Enforce explicitly requested scan spending limits when final root or delegated-worker usage cannot be verified.

Fixes #223. Related prior work: #224.

Changes

  • Reconcile completed root usage with directly tracked root and worker subtotals. Keep provisional cleanup refreshable and cache only verified final accounting.
  • Recheck strict final accounting once when an initial budgeted completion check fails. Continue normal completion only after that fresh check succeeds; otherwise suppress the post-scan continuation and preserve cancellation, known overages, and the original verification error.
  • Require completed root evidence when a successful turn omits usage. Require attributable, complete worker usage for successful budgeted scans.
  • Give known parent ownership precedence over reused output directories. Exclude known pre-scan cwd-only history before ownership ambiguity, infer independent workers only from contained parentless roots, retain their descendants, and keep relevant unresolved ownership unverified.
  • Reject missing, unreadable, disappearing, incomplete, ambiguous, or unpriceable cost evidence when a spending limit was requested.
  • Report retained and freshly readable cost before rejecting a disappeared included session. Keep disappearance strict-fail, including when only the root session disappears and completed-turn usage is available.
  • Retain per-session owned-record parse-error evidence while continuing later metadata, attribution, usage, and completion parsing. Ignore diagnostics only during established inherited replay, and report known cost before rejecting included budgeted-session errors.
  • Preserve each session's highest priced whole own-usage snapshot after inherited usage is subtracted. Keep later unpriceable evidence unverified without discarding an already-known priced lower bound.
  • Keep recognized owned accounting unverified when totals cannot be normalized or inherited usage cannot be subtracted. Preserve the priced lower bound and original error while continuing later metadata; inherited replay and info-only events retain their existing behavior.
  • Preserve definitive overage classification and matching usage across cleanup, history, and budget recovery. Recombine later worker usage without double counting, and preserve explicit user cancellation.
  • Keep scans without an explicit limit best-effort. Preserve unrelated-session isolation, authoritative root-only recovery, and known active-worker costs during failure cleanup.
  • Preserve valid completed SDK usage for optional unpriced models, including known worker usage after a failed final refresh. Compare unpriced whole snapshots by exact input-plus-output totals, keeping the larger snapshot and preferring newer usage on a tie. Keep an actual priced floor when available and retain prior usage when no valid replacement exists.
  • Treat invalid session timestamps as missing attribution evidence. Merge current main additively.

Testing

Final local checks on the frozen follow-up head 392a1952:

  • Fixed-seed full SDK suite with Bun 1.3.14: bun --no-env-file test --randomize --seed 12345 --timeout 30000, covering all of ./tests-ts with JUnit reporting — 1,376 passed, 11 skipped, 0 failed, and 9,428 assertions across 63 files. Ambient provider credentials and runtime/state overrides were removed; both live-test opt-ins were disabled.
  • pnpm run build, pnpm pack, pnpm run check:package, and a separate pnpm run test:package against the resulting archive — all passed sequentially once, with the repository's original timeout limits. The package checks covered the public import, CLI, 106 bundled plugin files, bundled Codex version, and an actual nested worker without global Codex.
  • Three fresh native review passes and independent verification — clean on 392a1952, with no candidates. The exact-head receipt check passed. Source remained clean and unchanged before and after every full-suite and package process.

Focused checks on the same head:

  • bun --no-env-file test --timeout 30000 tests-ts/cost.test.ts --test-name-pattern 'keeps mocked unusable own usage scoped to accounting' — the isolated mock passed; its dedicated child-mode run passed 20 assertions. Coverage includes unavailable normalization/subtraction, retained worker cost, first-error precedence, and optional/unrelated/root-only/replay/info-null controls. No actual session or filesystem failure was induced.
  • bun --no-env-file test --timeout 30000 tests-ts/cost.test.ts --test-name-pattern 'mocked .*sessions|mocked per-session|mocked unverified evidence|resolves mocked worker ancestry|limits mocked worker-directory|keeps mocked replay errors|keeps mocked unusable own usage|prefers mocked completed usage' — 9 isolated mock tests passed. The dedicated child-mode run passed the same 9 tests with 153 assertions.
  • bun --no-env-file test --timeout 30000 tests-ts/cost.test.ts --test-name-pattern 'counts the scan and delegated workers|counts independent Deep workers|retains the larger observed cost|verifies independent Deep worker ownership|keeps final budget enforcement|uses completed-turn usage when the final session refresh fails|requires completed root-session evidence|preserves observed active-worker costs|verifies final delegated-worker completion|ignores replayed parent history|uses each session.s final cumulative usage|preserves higher observed root usage|adds observed worker usage to the completed root after a failed refresh|rejects a budgeted scan when the completed model cannot be priced|allows unavailable completed-turn usage' — 31 passed, 63 assertions, 0 failures.
  • bun --no-env-file test --timeout 30000 tests-ts/api.test.ts --test-name-pattern 'handles a session-tracking failure|stops and records a scan|recovers exhausted deep-scan budget|fails a budgeted scan|rechecks strict final accounting|runs post-scan instructions after' — 26 passed, 198 assertions, 0 failures, including strict-finalization, cancellation, known-overage, and ordinary follow-up controls.
  • pnpm run types — passed.
  • pnpm exec prettier --check src/cost.ts tests-ts/cost.test.ts — passed.
  • git diff HEAD^ HEAD --check — passed.
  • Independent precommit and exact-head source rereview — the owned-accounting review finding is resolved; prior accounting, worker-attribution, and strict-finalization fixes are retained; no open code blockers.
  • Historical published head b6f45b6e: fixed-seed full SDK suite passed 1,375 tests with 11 skips, 0 failures, and 9,427 assertions. Standard build, pack, package check, and separate installed-package smoke passed. Three native review passes and independent verification were clean; pushed CI and Security Review were green. The later Code Review identified the pre-existing owned-accounting gap addressed in 392a1952. Those results do not establish the successor head's full-suite, package, native-review, or pushed-CI status.
  • Earlier published and intermediate evidence remains bound to its original heads. Final publication review, pushed current-head standard/cross-platform/package CI, and repository-supported reviews remain required.

Risk and rollout

Scans with an explicit spending limit fail when final cost cannot be verified instead of succeeding with understated or unknown usage. One accounting-only strict recheck permits normal completion after a late flush; a cleanup snapshot alone is never proof of final completeness. An exhausted verification failure cannot start a post-scan follow-up. Known parent ownership prevents older scans from contaminating reused output paths; genuinely unresolved worker ownership still fails closed. Missing-session failures retain freshly readable cost without becoming eligible for root-only recovery. A known overage retains the existing limit-exceeded classification even when other usage is incomplete. Recognized unusable owned accounting stays unverified while previously known cost is retained. Per-session high-water accounting preserves a priced lower bound; it does not claim complete reset-delta accumulation. Scans without explicit limits retain best-effort tracking and whole valid usage even when pricing is unavailable. No price, component-wise usage maximum, model-turn retry, delay, polling loop, input-size limit, or session-count limit is invented.

Public disclosure review

Newly authored material was reviewed for public disclosure. Existing automated review comments contain access-restricted report references; maintainer cleanup remains outstanding.

  • No customer, partner, prospect, or user identities, data, or identifying details are included.
  • No credentials, personal data, private source, scan findings, or nonpublic links or tickets are included.
  • I reviewed the branch name, title, description, commits, changes, comments, logs, screenshots, attachments, and links for public disclosure.

@mldangelo-oai

Copy link
Copy Markdown
Collaborator Author

@codex review

@github-actions github-actions Bot added the bug Something isn't working label Aug 15, 2026
@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: a147e46ca9

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a147e46ca9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sdk/typescript/tests-ts/cost.test.ts Outdated
Comment thread sdk/typescript/src/cost.ts Outdated
@mldangelo-oai

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: 95b3782aef

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 95b3782aef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sdk/typescript/src/cost.ts Outdated
Comment thread sdk/typescript/src/cost.ts Outdated
@mldangelo-oai

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: ce460fe36a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: ce460fe36a

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Copy link
Copy Markdown
Collaborator Author

@codex review

Please review the current head, b9f37a9b17ed5f3c9122b25c6316ba01cc2ffde0.

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: b9f37a9b17

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b9f37a9b17

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

for (const session of this.#sessions.values()) {
if (session.threadId !== null && included.has(session.threadId)) {
if (session.threadId !== this.#threadId) {
if (session.pendingLineBytes > 0) observed.unverified = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Mark malformed tracked records as unverified

When a tracked root or worker log contains a newline-terminated malformed JSON record, readSessionEvent silently ignores it and pendingLineBytes remains zero, so this verification loop can accept an earlier token count as final. If the corrupt record is the worker's last, higher token_count before a valid completion event, a budgeted scan can finish above --max-cost; record parse failures for included sessions must make budget verification fail closed.

AGENTS.md reference: sdk/typescript/AGENTS.md:L20-L22

Useful? React with 👍 / 👎.

},
);

testPosix.each(["parented", "independent"] as const)(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid calling each through the skipped-test alias

On Windows or when the tests run as root, testPosix is test.skip, but Bun does not define test.skip.each; evaluating this line throws TypeError: testPosix.each is not a function before the suite runs. I reproduced this with bun test tests-ts/cost.test.ts as root, where it prevents all subsequent cost tests from loading, so parameterize with test.each while applying the platform skip separately.

AGENTS.md reference: sdk/typescript/AGENTS.md:L36-L36

Useful? React with 👍 / 👎.

Comment thread sdk/typescript/src/cost.ts Outdated
Comment on lines +430 to +433
this.#snapshot.cost === null ||
(cost !== null && cost.estimatedUsd >= this.#snapshot.cost.estimatedUsd)
) {
this.#snapshot = { usage, cost };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Retain high-water usage per session

This retains only the highest aggregate snapshot, so token-counter resets can discard spend when another session grows later. For example, after observing root usage 100 plus worker usage 1,000, if the worker counter resets to 100 and final root usage reaches 500, the new aggregate is 600 and this code keeps the old 1,100 snapshot, even though the per-session high-water total is 1,500; that undercount can let the scan exceed --max-cost. Preserve each tracked session's highest usage before summing the final snapshot.

AGENTS.md reference: sdk/typescript/AGENTS.md:L20-L22

Useful? React with 👍 / 👎.

@mldangelo-oai

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: b6f45b6ee5

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b6f45b6ee5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

? usage
: subtractTokenUsage(usage, session.inheritedUsage);
if (ownUsage !== null) session.usage = ownUsage;
if (ownUsage !== null) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reject invalid token-count payloads

When a tracked root or worker emits an earlier valid token_count, followed by a valid JSON token_count whose totals fail normalization (for example, missing output tokens, impossible cache totals, or an inherited-usage subtraction that underflows), this branch silently retains the earlier accounting while a subsequent task_complete marks the session final. stop() can then accept the stale lower usage and let the scan exceed --max-cost. Unlike the existing JSON parse-failure guard, the fresh evidence here is that a successfully parsed but invalid accounting event never sets accountingError; treat usage === null or ownUsage === null as unverified accounting.

AGENTS.md reference: sdk/typescript/AGENTS.md:L20-L22

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: 392a1952c2

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 392a1952c2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +926 to +930
session.accounting?.cost == null ||
(cost !== null &&
cost.estimatedUsd >= session.accounting.cost.estimatedUsd)
) {
session.accounting = { usage: ownUsage, cost };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Accumulate usage across per-session counter resets

Fresh evidence beyond the earlier aggregate high-water case is a reset followed by growth within one session: snapshots 1,000 → 100 → 600 represent 1,600 cumulative tokens—the existing rollout delta reader handles a decrease as a reset at sdk/typescript/_bundled_plugin/scripts/workbench_scan_usage.py:474-476—but this comparison retains only the 1,000-token snapshot. A scan whose counter resets and then continues can therefore exceed --max-cost while reporting the lower high-water cost; accumulate deltas across resets rather than selecting one maximum snapshot.

AGENTS.md reference: sdk/typescript/AGENTS.md:L20-L22

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

An unreadable Codex session log fails every cost poll for the rest of the scan, and can fail a scan that succeeded

1 participant