Skip to content

Fix nested compact Codex subagent attribution#2396

Open
hhh2210 wants to merge 2 commits into
steipete:mainfrom
hhh2210:codex/fix-nested-fork-attribution
Open

Fix nested compact Codex subagent attribution#2396
hhh2210 wants to merge 2 commits into
steipete:mainfrom
hhh2210:codex/fix-nested-fork-attribution

Conversation

@hhh2210

@hhh2210 hhh2210 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix nested compact Codex subagent attribution when a child copies its parent's raw cumulative counter at the fork boundary.
  • Keep raw boundary proof separate from owned/deduplicated parent totals used for normal subtraction and invalidation.
  • Selectively repair only legacy parent-dependent cache rows, including rows already carrying a known model label.
  • Keep every cache-backed daily/project/session surface on the same provenance quarantine boundary.
  • Refuse to reuse a growing file's cache entry until parsedBytes == size, so an appended first-turn marker can reclassify a provisional compact child.

Refs #2037, #2108, #2118, #2228, and #2285.

Evidence

This was reproduced from a real July 21 nested child/parent rollout. The UI symptom before the fix was a 409M-token Unknown model bucket:

jul21-unknown-model-before.png

The child copied this exact raw cumulative boundary from its parent:

Dimension Tokens
Input 407,555,823
Cached input 399,890,176
Output 1,094,182

The prior comparison used the parent's owned/deduplicated total. That value is correct for ordinary fork subtraction, but it is the wrong accounting domain for proving that a compact child's prefix was copied.

Frozen-oracle results on the same rollout pair:

Result Baseline cc8da27c This patch
Total tokens 1,088,729,514 680,079,509
Unknown model 408,650,005 0
gpt-5.6-sol 668,439,167 668,439,167
codex-auto-review 11,640,342 11,640,342
Cost $543.622452 $543.622452

Cold, warm, and forced scans produced the same patched result.

Changes

  • Add a raw-cumulative snapshot view to CodexInheritedTotalsResolver.
    • It is sourced only from cumulative total_token_usage records.
    • A later last_token_usage-only record cannot erase that proof.
  • Use exact raw equality only for copied-prefix detection.
  • Preserve owned/deduplicated totals for normal parent subtraction and parent dependency invalidation.
  • Keep the previous parser producer readable, but quarantine and reparse a row only when all provenance evidence says it was parent-dependent:
    • the file has forkedFromId;
    • its fork-attribution marker is old or absent;
    • its dependency key is not the parent context not required sentinel.
  • Apply that projection once at the shared report-building boundary used by cached daily, project, and session views.
  • Treat matching mtime and size as insufficient freshness when parsedBytes proves the prior parse was incomplete.
  • Remove an out-of-window legacy suspect instead of certifying an unreparsed projection; a later wider request reparses the source file.
  • Use the cache-level migration version as the cheap fast path before scanning per-file migration provenance.

Maintainer invariants

Fork-cache migration is keyed by persisted provenance and dependency semantics, never by the user-visible Unknown model label. A copied prefix can already have a known model.

File freshness means both stable filesystem metadata and a complete parse (parsedBytes == size). A parser that retained a valid prefix after a later read failure must not certify the full observed file as parsed.

Parser behavior rotates the producer key; the cache artifact version is reserved for incompatible stored schema/layout changes. This avoids another cycle of model-label heuristics or blind codex-vN.json bumps.

Safety

  • Suppression requires exact raw parent-boundary evidence.
  • Missing, unstable, or mismatched parents remain conservatively counted.
  • Current sentinel-owned unknown usage remains visible.
  • Known-model legacy artifacts are repaired instead of being silently trusted.
  • Mixed-cache regression coverage proves a legacy candidate cannot leak into daily, project, or session presentation while a current file remains visible.
  • Growing-file regression coverage proves a newly appended first-turn marker invalidates a partial cached prefix and changes its ownership classification.

Validation

  • make check — passed at 766a8d83; parser hash 60c110df71a146ea, SwiftFormat clean, SwiftLint 0 violations.
  • Focused fork/cache suites — 122/122 tests passed across CodexForkAttributionMigrationTests, CostUsageScannerBreakdownTests, CodexCompactSubagentAccountingTests, CodexSubagentAccountingIntegrationTests, and CostUsageFetcherCacheSnapshotTests.
  • Growing-file oracle — raw parent boundary 408,649 tokens versus owned parent total 128; final report 1,859 tokens with zero unattributed usage.
  • Mixed-cache oracle — current 42-token session remains in daily/project/session results; known-model 900-token legacy candidate is absent from all three.
  • p48 migration oracle — 170 seeded files, 8 repaired/current files, 162 retained unaffected files, 0 residual legacy suspects.
  • make test reached group 12/61 after 11 successful groups, then hit the unrelated ClaudeOAuthCredentialsStoreTemporaryKeychainCacheTests test-store baseline. The same four assertions reproduce on clean origin/main@cc8da27c; this PR changes no Claude/Keychain files.
  • GitHub CI at 766a8d83 passed 73/74 groups in macOS shard 0, including every fork/cache suite changed here. The sole failed group was the unrelated SpendDashboardTokenProvenanceTests: an earlier rollover test persisted the 7-day selection in UserDefaults.standard, so its fixed July 16 fixture fell outside the July 23 window. The same three assertions reproduce on clean origin/main@cc8da27c. Isolate dashboard rollover test defaults #2386 is the isolated base-test repair and is exact-head green/CLEAN; this PR should rebase after it lands rather than rerun the unchanged merge ref.

Separate raw cumulative fork-boundary proof from owned usage subtraction and parent invalidation.

Selectively migrate legacy parent-dependent cache rows, including known-model artifacts and later last-only snapshots.
Copilot AI review requested due to automatic review settings July 22, 2026 16:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses incorrect token attribution for nested compact Codex subagents when a child rollout copies its parent’s raw cumulative counter at the fork boundary (previously leading to large “Unknown model” buckets). It introduces a raw-cumulative proof path (separate from owned/deduplicated totals) and adds a narrowly scoped cache migration to selectively quarantine/reparse only legacy parent-dependent fork artifacts.

Changes:

  • Add a raw-cumulative snapshot path to CodexInheritedTotalsResolver and use it only for copied-prefix detection (exact raw equality).
  • Introduce a fork-attribution migration marker and logic to quarantine/hide legacy parent-dependent cached rows until repaired, while preserving sentinel-owned unknown usage.
  • Add/extend tests covering known-model copied-prefix suppression, raw-proof requirements (cumulative totals only), and cache migration behaviors.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Tests/CodexBarTests/CostUsageScannerBreakdownTests.swift Adds regression coverage for raw-parent-prefix suppression (including known-model legacy artifacts) and ensures last-only events can’t serve as raw proof.
Tests/CodexBarTests/CodexForkAttributionMigrationTests.swift Adds targeted migration tests ensuring legacy parent-dependent candidates are quarantined/reparsed while legitimate sentinel-owned “unknown” remains visible.
Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner+CacheHelpers.swift Implements legacy fork-candidate detection, forces reparsing for those candidates, and hides them from cache-backed presentation until migration completes.
Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift Splits owned vs raw parent snapshots, switches copied-prefix proof to raw totals, and adds migration planning/removal of out-of-window legacy suspects.
Sources/CodexBarCore/Vendored/CostUsage/CostUsageCache.swift Allows loading a prior compatible producer key and adds cache/file migration markers for fork-attribution repair.
Sources/CodexBarCore/Generated/CodexParserHash.generated.swift Rotates the Codex parser producer hash to reflect the attribution logic change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Sources/CodexBarCore/Vendored/CostUsage/CostUsageCache.swift Outdated
Comment thread Sources/CodexBarCore/Vendored/CostUsage/CostUsageScanner.swift Outdated
Require complete file parses before fresh-cache reuse, centralize provenance-based presentation projection, and add growing-rollout plus mixed-breakdown regressions.
@hhh2210

hhh2210 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up hardening is now on 766a8d83:

  • addressed both Copilot findings;
  • centralized provenance quarantine at the shared cached-report boundary, with mixed daily/project/session coverage;
  • fixed partial-parse cache reuse by requiring parsedBytes == size;
  • added a growing-rollout regression where an appended first-turn marker must reclassify a cached compact child prefix.

Validation: make check passes; 122/122 focused fork/cache tests pass. The local full-suite runner stops at the unchanged Claude temporary-Keychain test-store baseline, whose same four assertions reproduce on clean origin/main@cc8da27c.

@hhh2210

hhh2210 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

CI triage update for exact head 766a8d83:

  • macOS shard 0 passed 73/74 groups; every compact/fork/cache suite touched by this PR passed.
  • The only failed group was the unrelated SpendDashboardTokenProvenanceTests (three assertions). SpendDashboardClockRolloverTests had persisted settingsSpendDashboardDays=7 through UserDefaults.standard; on July 23, its fixed July 16 fixture was therefore filtered out.
  • The same three failures reproduce on clean origin/main@cc8da27c, so this is not an attribution regression.
  • Isolate dashboard rollover test defaults #2386 contains the isolated test-defaults fix and is currently exact-head green, CLEAN, and MERGEABLE.

No scanner change is needed here. Rerunning this head would reuse the same old merge ref and fail again; after #2386 lands, I will rebase this PR so CI runs against the repaired base.

@clawsweeper

clawsweeper Bot commented Jul 23, 2026

Copy link
Copy Markdown

ClawSweeper status: review started.

I am starting a fresh review of this pull request: Fix nested compact Codex subagent attribution This is item 1/1 in the current shard. Shard 0/1.

This placeholder means the worker is alive and reading the current context. I will edit this same comment with the actual review when the claws are done clicking.

Crustacean status: shell secured, claws on keyboard, evidence pebbles being sorted.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants