Skip to content

[FIX] fix: coerce None→0 in token telemetry serialization boundary#3994

Merged
Trecek merged 1 commit into
developfrom
codex-backend-cache-write-tokens-none-crashes-token-summary/3993
Jun 11, 2026
Merged

[FIX] fix: coerce None→0 in token telemetry serialization boundary#3994
Trecek merged 1 commit into
developfrom
codex-backend-cache-write-tokens-none-crashes-token-summary/3993

Conversation

@Trecek

@Trecek Trecek commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Summary

CanonicalTokenUsage uses int | None for cache fields to preserve provider semantics (Codex has no cache-write concept). However, the persistence boundary in flush_session_log() and both readers (load_from_log_dir, _load_sessions) use dict.get("key", 0) which silently passes None through when the key is present — dict.get() only returns the default for absent keys, not for None-valued ones. This violates the TokenUsageFileEntry TypedDict contract (which declares cache_write_tokens: int, not int | None) and causes int += NoneTypeError at reload.

The architectural weakness is a missing coercion layer at the serialization boundary: CanonicalTokenUsage.to_dict() emits domain-model values (None = "not applicable") directly into a dict that consumers treat as storage-model values (all ints). The fix is to enforce the TokenUsageFileEntry contract structurally — make it impossible to write non-int values for int-typed fields.

Requirements

patch_pr_token_summary crashes with TypeError: unsupported operand type(s) for +=: 'int' and 'NoneType' when aggregating telemetry from pipeline runs that include Codex-backend sessions. The crash is caused by a dict.get() semantic trap: None values written to token_usage.json by the Codex backend bypass the 0 fallback in .get("cache_write_tokens", 0) because dict.get() only uses the default when the key is absent, not when it is explicitly None.

Closes #3993

Implementation Plan

Plan file: /home/talon/projects/autoskillit-runs/remediation-20260610-190114-286663/.autoskillit/temp/rectify/rectify_none_bypass_token_serialization_2026-06-10_192500.md

🤖 Generated with Claude Code via AutoSkillit

Token Usage Summary

Step Model count uncached output cache_read peak_ctx turns cache_write time
rectify* opus[1m] 1 4.9k 23.2k 1.3M 97.0k 43 82.9k 21m 11s
review_approach* opus[1m] 1 4.5k 6.1k 260.0k 49.0k 14 35.4k 5m 23s
dry_walkthrough* opus 1 41 9.6k 867.8k 89.3k 35 67.7k 7m 33s
implement* MiniMax-M3 1 3.7M 9.0k 0 0 79 0 7m 44s
audit_impl* opus[1m] 1 1.2k 11.3k 257.3k 48.2k 18 39.9k 4m 33s
prepare_pr* MiniMax-M3 1 284.5k 2.9k 0 0 12 0 1m 7s
compose_pr* MiniMax-M3 1 351.0k 1.3k 0 0 12 0 51s
review_pr* opus[1m] 1 43 26.5k 980.9k 89.0k 36 68.0k 6m 17s
Total 4.4M 89.8k 3.6M 97.0k 294.0k 54m 42s

* Step used a non-Anthropic provider; caching behavior may differ.

Token Efficiency

Step LoC Changed cache_read/LoC cache_write/LoC output/LoC
rectify 0
review_approach 0
dry_walkthrough 0
implement 243 0.0 0.0 37.0
audit_impl 0
prepare_pr 0
compose_pr 0
review_pr 0
Total 243 14903.1 1210.0 369.7

Model Usage Breakdown

Model steps uncached output cache_read cache_write time
opus[1m] 4 10.7k 67.0k 2.8M 226.3k 37m 26s
opus 1 41 9.6k 867.8k 67.7k 7m 33s
MiniMax-M3 3 4.4M 13.2k 0 0 9m 42s

CanonicalTokenUsage.to_dict() now emits 0 for None-valued cache fields
instead of None, enforcing the TokenUsageFileEntry TypedDict contract at
the serialization boundary. Defensive None-safe accumulations added to
load_from_log_dir, record(), flush_session_log, sessions.jsonl writer,
and _load_sessions/_format_model_table to guard against already-corrupt
or future None-valued fields in token_usage.json.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Trecek Trecek added this pull request to the merge queue Jun 11, 2026
Merged via the queue into develop with commit 8428e4a Jun 11, 2026
3 checks passed
@Trecek Trecek deleted the codex-backend-cache-write-tokens-none-crashes-token-summary/3993 branch June 11, 2026 03:09
Trecek added a commit that referenced this pull request Jun 28, 2026
Produce merged machine-readable seed disposition table at
.autoskillit/temp/issue_disposition_draft.md that consolidates WP1 (20 issues
— Strategic & SessionLocator cluster) and WP2 (49 unique issues — String-to-
Capability, Conformance, Builder-Divergence, Guard-Bypass, D9) with new WP3
classifications for six remaining issue clusters.

The merged table uses a normalized five-column schema:
  issue_number | title | classification | absorbing_requirement_or_wp | close_action

Header section documents snapshot date (2026-06-28), seed coverage (75 issues
across 3 WPs), source file references, and the P2-A9 extension instruction.

WP3 work:
- Reclassified 7 of 8 D9 issues from INDEPENDENT to absorbed (#3297, #3638,
  #3877, #3676, #3836, #3781, #3756); #3876 stays independent (Codex sandbox
  constraint).
- Added 8 new rows: #3335 (Codex Config Schema Drift Immunity), #3971 (fleet
  authority), #3993 (cache_write_tokens closed via PR #3994), #823/#2453/#2671/
  #2479/#2481 (CanonicalTokenUsage / backend registry).
- For overlap issues #3385 and #3699 (in both WP1 and WP2 main rows), used
  WP2's absorbed classification with WP1's R-A R-group reference.

Verification:
- 75 unique issue rows in main table (sorted ascending by issue_number).
- All 13 AC-enumerated issues present.
- All 8 D9 items reconciled to absorbing requirement or flagged independent.
- #3993 has classification=closed, close_action=already-closed.
- WP span verified: WP1 (#23), WP2 (#3103), WP3 (#3335) all present.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant