Skip to content

fix: preserve Codex usage after session archiving - #181

Merged
chattymin merged 2 commits into
chattymin:mainfrom
ite476:fix/codex-archived-session-scan-upstream
Aug 19, 2026
Merged

chattymin merged 2 commits into
chattymin:mainfrom
ite476:fix/codex-archived-session-scan-upstream

Conversation

@ite476

@ite476 ite476 commented Aug 17, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Scan both active and archived Codex rollout roots.
  • Preserve usage when Codex moves a rollout from sessions to archived_sessions.
  • Deduplicate the same rollout when it is temporarily visible in both roots.
  • Make the default Codex root composition injectable so tests exercise the same source used by the reader and cache.
  • Normalize Codex scan roots before enumerating them, including symlinked and nested roots.
  • Document the Codex active/archive root rule in docs/reference/provider-extension.md.
  • Add regression tests for default root composition, archive relocation, cross-root duplication, and symlinked roots.

Root cause

Codex defines both sessions and archived_sessions as local rollout locations. Its archive implementation moves rollout files with std::fs::rename() and then updates the archived thread metadata:

PokeTokenBar previously scanned only ~/.codex/sessions, so an archived rollout appeared to disappear from the daily total. The two directories are alternative locations for the same local rollout history, not independent usage streams.

Codex records session identity and cumulative token usage in the rollout format. The existing resolver already uses stable session/state-based canonical IDs, so this change reuses that deduplication boundary instead of summing both directory trees blindly. Codex documents total_token_usage as accumulated session usage and last_token_usage as the latest active context usage in its TUI model:

Scope

This fixes usage loss caused by normal sessions → archived_sessions relocation and prevents duplicate counting during a temporary cross-root overlap.

This PR does not attempt to solve torn reads during an active write, corrupted or missing rollout files, custom Codex homes, account switching, or server-side quota attribution.

Validation

  • swift test --filter LocalUsageReaderTests — 56 tests passed.
  • ./scripts/test-gate.sh — 680 tests passed, 7 skipped, 0 failures.
  • Logic-core line coverage: 89.67% (required: 75%).

Type of change

  • Bug fix
  • New feature
  • Refactor / cleanup
  • Documentation
  • Other:

Checklist

  • swift build and swift test pass locally.
  • PR title and description are written in English.
  • No UI changes.
  • No copyrighted assets, secrets, or private tooling references are included.
  • Tests were added or updated for this change.

@chattymin

Copy link
Copy Markdown
Owner

Thanks @ite476 — the root cause holds up. I pulled the Codex sources rather than take the description's word for it: ARCHIVED_SESSIONS_SUBDIR is defined in rollout/src/lib.rs, the archive path goes through std::fs::rename, and upstream has a test named archive_thread_moves_rollout_to_archived_collection. The move semantics are what you describe.

Requesting one change before merge; the other two are notes.

1. The tests don't share the single root source — blocking

I reverted the user-visible half of the fix and the full suite stayed green:

static var codexScanRoots: [URL] {
    [codexSessionsDir]        // archived root dropped
}
swift test → Executed 678 tests, with 7 tests skipped and 0 failures

Both new tests inject codexRoots: [root, archivedRoot], so nothing exercises codexScanRoots. docs/reference/provider-extension.md asks for the opposite — the scanner, the cache, and the tests should share that one list.

There is a seam for this already: claudeProjectRoots has computeClaudeProjectRoots(configDirValue:home:), and LocalUsageReaderTests pins the composition through it with a fake home. codexScanRoots reads homeDirectoryForCurrentUser directly, so it can't be pinned the same way. Mirroring that shape would close it.

2. normalizedRoots already exists — non-blocking

The same doc section asks for root folding via normalizedRoots, and the Claude path uses it (LocalUsageReader.swift:88). codexRolloutFiles(in roots:) introduces a separate byPath dictionary instead. The existing helper resolves symlinks and collapses nested roots; raw path-string comparison does neither.

With two hardcoded sibling directories this makes no practical difference today, which is why it's a note rather than a request.

3. The source links don't resolve — note

rollout/src/lib.rs#L600-L603 points past the end of that file (144 lines); the constants are at L67-68. Branch links drift, so a commit-pinned permalink would keep the root-cause evidence readable later.

Verified as correct

  • The relocation test is real: narrowing the scan to [root] fails it with [] vs [110], so it reads the archived file rather than passing on a stale cache blob.
  • FileManager.enumerator recurses, so a flat or date-nested archive layout both work.
  • codexRoot / codexRoots mirrors the existing claudeRoot / claudeRoots pair, and a single codexScanRoots list matches the extension convention.

@ite476

ite476 commented Aug 19, 2026 •

Copy link
Copy Markdown
Contributor Author

@chattymin

Thanks for the thorough review. I addressed the blocking test gap and incorporated the two follow-up notes.

  • Added computeCodexScanRoots(home:) so the active and archived Codex roots are defined in one testable place.
  • Kept codexScanRoots as the shared source used by the reader and cache.
  • Added regression coverage for the injected default roots and symlinked roots.
  • Updated multi-root Codex scanning to use normalizedRoots.
  • Updated docs/reference/provider-extension.md to document the Codex active/archive root rule.
  • Replaced the PR's upstream Codex references with commit-pinned permalinks.

Validation:

  • swift test --filter LocalUsageReaderTests — 56 tests passed.
  • ./scripts/test-gate.sh — 680 tests passed, 7 skipped, 0 failures.
  • Logic-core line coverage: 89.67%.

The changes are pushed in commit b9f369c.

@chattymin
chattymin merged commit eff18bb into chattymin:main Aug 19, 2026
2 checks passed
@chattymin

Copy link
Copy Markdown
Owner

Thank you for your contribution!

Borisserz added a commit to Borisserz/PokeTokenBar that referenced this pull request Aug 20, 2026
…n#177)

resolvedRoots hardcoded the CLI path, which is defaultRoots[1] after
chattymin#182, so a no-root scan dropped 2.0/Core and IDE stores. Codex custom
roots had the same class of hole against archived_sessions (chattymin#181).
LuceteYang added a commit to LuceteYang/PikaTokenBar that referenced this pull request Aug 21, 2026
Brings in the representative-Pokémon pin (chattymin#158), Antigravity 2.0 multi-root
discovery (chattymin#182), Codex rollout memory bounding (chattymin#184), and fixes for Codex
usage after session archiving (chattymin#181), the Kiro rescan (chattymin#178), the AppLog
willTerminate flush (chattymin#174), the update-loop PID wait (chattymin#175), the popover
outside-click monitor (chattymin#168), and menu bar/popover sprite aspect ratio (chattymin#194).
All 11 are scope- and identity-agnostic, so nothing is rejected.

Conflict resolutions:
- PokeTokenBarApp / FloatingPetPanel: the menu bar and pet now read the
  representative subject AND the animation-quality frame floor. The floor stays
  in the frame-cache identity key and in the observation set, so pinning a
  species and changing FPS both take effect immediately.
- UpdateChecker: took upstream's PID-based exit wait and testable launcher
  signature, kept the fork's cask token and login-agent label via AppIdentity.
  The fork guard that asserted `pgrep -x <executable>` now asserts the PID wait,
  since upstream deliberately replaced that mechanism.
- UsageStore: kept the fork's async notification-authorization request, which
  avoids the Swift 6 executor trap the closure form hits.
- CLAUDE.md: kept the fork-maintenance row alongside upstream's expanded
  defect-log row.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
chattymin pushed a commit that referenced this pull request Aug 22, 2026
* feat: add per-provider additional scan folders (#177)

Logs outside the built-in paths were a silent undercount for every
provider except Claude. Store one list per provider so parsers never
share folders, and only add custom roots — never evict a curated default.

* fix: union custom scan roots onto every Antigravity default (#177)

resolvedRoots hardcoded the CLI path, which is defaultRoots[1] after
#182, so a no-root scan dropped 2.0/Core and IDE stores. Codex custom
roots had the same class of hole against archived_sessions (#181).
lukemonaghan pushed a commit to lukemonaghan/PokeTokenBar that referenced this pull request Aug 24, 2026
* fix: preserve Codex usage after session archiving

* fix: cover archived Codex roots in tests
lukemonaghan pushed a commit to lukemonaghan/PokeTokenBar that referenced this pull request Aug 24, 2026
…ymin#187)

* feat: add per-provider additional scan folders (chattymin#177)

Logs outside the built-in paths were a silent undercount for every
provider except Claude. Store one list per provider so parsers never
share folders, and only add custom roots — never evict a curated default.

* fix: union custom scan roots onto every Antigravity default (chattymin#177)

resolvedRoots hardcoded the CLI path, which is defaultRoots[1] after
chattymin#182, so a no-root scan dropped 2.0/Core and IDE stores. Codex custom
roots had the same class of hole against archived_sessions (chattymin#181).
donky-ey pushed a commit to donky-ey/PokeDexBar that referenced this pull request Oct 14, 2026
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