Skip to content

fix(claude): keep entries with null model inside usage iterations - #1715

Closed
github-actions[bot] wants to merge 1 commit into
mainfrom
pullfrog/issue-1710-run-34723278783-attempt-1
Closed

github-actions[bot] wants to merge 1 commit into
mainfrom
pullfrog/issue-1710-run-34723278783-attempt-1

Conversation

@github-actions

@github-actions github-actions Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

References #1710: has_unsupported_null_field in rust/adapters/claude/src/lib.rs no longer drops entries whose only listed null is message.usage.iterations[].model. It computes string-aware byte ranges for every "iterations": [...] array and skips :null hits inside them, so Fable 5.1 entries deserialize normally. Top-level nulls are still rejected. Shared by both entry and daily loaders.\n\nTests: added 4 regression tests including the exact JSONL line from the issue (1 entry under claude-fable-5-1). Full adapter suite: 26 passed, 0 failed; fmt and clippy clean.\n\nCommitted locally on branch pullfrog/1710-iterations-null-model (commit 535bce34); working tree clean. Push disabled in this environment (Push is disabled. This repository is configured for read-only access.), so a maintainer needs to push the branch / open the PR.

Closes #1710


Summary by cubic

Fixes issue #1710 by keeping Claude usage entries whose only null field is message.usage.iterations[].model, which Fable 5.1 writes as explicit null. Previously these entries were dropped as unsupported; now has_unsupported_null_field skips :null hits inside "iterations": [...] arrays, while still rejecting top-level nulls.

  • Computes string-aware byte ranges for every iterations array and skips nulls within them.
  • Applies to both entry and daily loaders via the shared pre-check.
  • Adds 4 regression tests, including the exact JSONL fixture from the issue.

Written for commit 50c549a. Summary will update on new commits.

Review in cubic

@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 912f5ca5-0aca-46b3-816c-aaacaf80bdf7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ryoppippi

Copy link
Copy Markdown
Member

@coderabbitai @cubic-dev-ai Please review this one-line Nushell runtime fix.

@cubic-dev-ai

cubic-dev-ai Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

@coderabbitai @cubic-dev-ai Please review this one-line Nushell runtime fix.

@ryoppippi Couldn't start the review: please upgrade to a paid plan to review bot-authored PRs.

@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- PR metadata and changed files ---'
gh pr view 1715 --repo ccusage/ccusage --json title,body,baseRefName,headRefName,files --jq '{title, baseRefName, headRefName, files: [.files[] | {path, additions, deletions}]}'

echo '--- Diff ---'
gh pr diff 1715 --repo ccusage/ccusage --color=never

echo '--- Adapter source outline ---'
ast-grep outline rust/adapters/claude/src/lib.rs --items all --view expanded 2>/dev/null || true

echo '--- Relevant symbols and test references ---'
rg -n -A60 -B15 'has_unsupported_null_field|iterations|claude-fable-5-1' rust/adapters/claude/src/lib.rs

Length of output: 34636


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- Null-field policy and TokenUsageRaw definition/usages ---'
rg -n -A45 -B15 'struct TokenUsageRaw|enum Speed|is_unsupported_nullable_field|has_unsupported_null_field' rust

echo '--- Claude TypeScript loader null handling ---'
rg -n -A45 -B15 'unsupported.*null|null.*unsupported|iterations' --glob '*.ts' --glob '*.tsx' .

echo '--- Current PR commit and comparison ---'
gh pr view 1715 --repo ccusage/ccusage --json commits --jq '.commits[] | {oid: .oid, messageHeadline: .messageHeadline}'

echo '--- JSON tests covering invalid nulls within iterations ---'
rg -n -A8 -B5 'iterations.*(speed|input_tokens|output_tokens|type).*null|(speed|input_tokens|output_tokens|type).*null.*iterations' rust/adapters/claude/src/lib.rs

Length of output: 43797


@ryoppippi I found one issue in the current Rust change. The pull request does not contain a Nushell one-line fix.

  • rust/adapters/claude/src/lib.rs, Line 518: The excluded range suppresses validation for all null fields in each "iterations" array. This accepts {"type":"advisor_message","speed":null,...} even though speed remains in is_unsupported_nullable_field and deserializes as Option<Speed>. Restrict the exception to the model: null field in an iteration. Add a regression test for speed: null inside an iteration to preserve the existing null policy.

You are interacting with an AI system.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
ccusage-guide 50c549a Commit Preview URL

Branch Preview URL
Sep 12 2026, 10:49 PM

continue;
}
}
cursor = key_start + 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This fallthrough flips the string state for the rest of the line.

When "iterations" matches at cursor but is not followed by : and [, the loop resumes at key_start + 1 with in_string still false. The scan then walks the bytes of iterations, reaches the closing quote, and sets in_string = true, so from here on every quote is read in reverse. Two inputs reach this path: a string value that is exactly "iterations", and an iterations key whose value is not an array.

The effect on a real Fable line is fail-closed: a later "iterations":[ key is never seen because the scan arrives at its opening quote believing it is inside a string, no range is recorded, and the line is dropped as before this PR. The reverse case also exists: a quoted \"iterations\":[ inside a later string would be taken for a real array.

The opening quote already belongs to a string in this case, so the fix is to enter string mode before resuming:

cursor = key_start + 1;
in_string = true;
continue;

A test that fails on the current commit and passes with that change:

#[test]
fn keeps_string_state_after_iterations_string_value() {
    // A string value equal to "iterations" precedes the real array.
    assert!(!has_unsupported_null_field(
        br#"{"message":{"model":"m","content":"iterations","usage":{"input_tokens":0,"iterations":[{"type":"message","model":null,"input_tokens":0,"output_tokens":0}]}}}"#
    ));
}

Unrelated to the above: the CodeRabbit finding about exempting every null inside the array can stay as is. TokenUsageRaw in ccusage-core has no iterations field, so nothing inside the array reaches the typed parse of
the main entry, and speed there is already Option<Speed>. A null token count inside an iteration only makes advisor_usages_from_line return nothing, which is a softer failure than dropping the whole line.

@ryoppippi

Copy link
Copy Markdown
Member

Closing in favor of #1719, which has now merged the same #1710 fix with the latest validation and review fixes. Thank you for preparing this implementation.

@ryoppippi ryoppippi closed this Sep 15, 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.

Claude entries are dropped when usage.iterations[].model is null (Fable 5.1 since 2026-09-12)

2 participants