Skip to content

feat(claude): weekly Fable usage tracking across menu, CLI, and widget - #1837

Closed
chrisw1005 wants to merge 1 commit into
steipete:mainfrom
chrisw1005:feature/claude-weekly-fable-usage
Closed

feat(claude): weekly Fable usage tracking across menu, CLI, and widget#1837
chrisw1005 wants to merge 1 commit into
steipete:mainfrom
chrisw1005:feature/claude-weekly-fable-usage

Conversation

@chrisw1005

@chrisw1005 chrisw1005 commented Jul 2, 2026

Copy link
Copy Markdown

What & why

Adds a first-class weekly "Fable" rate-limit window to the Claude provider, parallel to the existing all-models and model-specific (Sonnet/Opus) weekly buckets, and surfaces it everywhere Claude usage is shown: the menu card, the menu descriptor, the CLI renderer, and the macOS widget, plus a dedicated plan-utilization history series.

Motivation: the newest Claude usage payload exposes model-scoped weekly limits in a new limits array, and the current code has no way to show the Fable weekly bucket.

Key correctness detail — the real payload uses limits[], not seven_day_fable

On the newest response from the official Anthropic OAuth usage endpoint (GET https://api.anthropic.com/api/oauth/usage), the legacy top-level seven_day_opus / seven_day_sonnet / seven_day_fable keys are null, and the model-scoped weekly limits live in a limits array, identified by scope.model.display_name:

{ "kind": "weekly_scoped", "group": "weekly", "percent": 59,
  "resets_at": "", "scope": { "model": { "id": null, "display_name": "Fable" } } }

So this PR parses the limits array (OAuth + Web) and maps:

  • weekly_scoped with display_name == "Fable" → the new Fable window.
  • other weekly_scoped entries → the existing model-specific (Sonnet/Opus) slot — which also fixes that slot on the new payload shape, where it currently shows nothing.

The legacy seven_day_fable key is kept as a fallback. The CLI TTY path parses the confirmed Current week (Fable) panel.

Evidence — trimmed real response from the official /api/oauth/usage endpoint

Unreleased per-model codename keys and the *_dollars fields are omitted; the shape of the relevant keys is unchanged.

{
  "five_hour":       { "utilization": 100, "resets_at": "2026-07-02T07:10:00Z" },
  "seven_day":       { "utilization": 34,  "resets_at": "2026-07-04T11:00:00Z" },
  "seven_day_opus":   null,
  "seven_day_sonnet": null,
  "seven_day_fable":  null,
  "limits": [
    { "kind": "session",       "group": "session", "percent": 100,
      "resets_at": "2026-07-02T07:10:00Z", "scope": null, "is_active": true },
    { "kind": "weekly_all",    "group": "weekly",  "percent": 34,
      "resets_at": "2026-07-04T11:00:00Z", "scope": null, "is_active": false },
    { "kind": "weekly_scoped", "group": "weekly",  "percent": 59,
      "resets_at": "2026-07-04T10:59:59Z",
      "scope": { "model": { "id": null, "display_name": "Fable" } }, "is_active": false }
  ]
}

Here the account has no weekly_scoped Sonnet/Opus entry, so only Session / Weekly / Fable are shown — the model-specific (opus) slot correctly stays empty.

Design

Plumbs a quaternary window through the shared stack so it renders on all four surfaces including the widget:

  • UsageSnapshot (manual Codable, replacing(), backfillingResetTimes, hasRateLimitWindows)
  • WidgetSnapshot.ProviderEntry + widget UI rows
  • ProviderMetadata.fableLabel / supportsFable (defaulted, so the other ~40 providers compile and behave unchanged)
  • the three rateWindowLabels render surfaces (extracted to a shared RateWindowLabels struct to stay within the large_tuple lint limit)
  • a .fable plan-utilization history series

The lighter extraRateWindows approach was considered but does not reach WidgetSnapshot, so it can't give widget parity.

When Fable data is absent the window is simply nil at every layer (no rows emitted, widget filters the nil-percent row, history skips the sample, Codable encodes null) — identical to how an account without an Opus/Sonnet weekly behaves today. No force-unwraps on the new field.

Honesty / review notes

  • OAuth path is confirmed against the real response from the official Anthropic /api/oauth/usage endpoint (Fable at 59% via limits[].scope.model.display_name; see the evidence block above).
  • The Web limits fallback mirrors OAuth by analogy (same backend shape) but was not verified against a real web (claude.ai/api/.../usage) payload.
  • The CLI probe-JSON keys week_fable / week_fable_only are CodexBar-internal (no external producer); added by analogy to the existing week_sonnet handling.

Tests

swift build, swift test, swiftformat --lint, and swiftlint --strict all pass. New tests cover the real OAuth limits payload, the Sonnet+Fable split, the web limits fallback, the Current week (Fable) TTY panel, and the fixture contract.

🤖 Generated with Claude Code

Add a first-class weekly "Fable" rate-limit window, parallel to the
existing all-models and model-specific (Sonnet/Opus) weekly buckets, and
surface it in the menu card, menu descriptor, CLI renderer, and the macOS
widget, plus a dedicated plan-utilization history series.

Data sources:
- OAuth/Web: read the model-scoped weekly limit from the newest `limits`
  array via scope.model.display_name == "Fable" (the legacy top-level
  seven_day_* keys are null on the new payload shape); keep seven_day_fable
  as a fallback.
- Non-Fable scoped limits now also populate the model-specific weekly slot,
  so Sonnet/Opus weekly is restored on the new payload shape.
- CLI TTY: parse the "Current week (Fable)" panel.
- CLI probe JSON: parse week_fable / week_fable_only.

Plumbs a `quaternary` window through UsageSnapshot (manual Codable,
replacing(), backfillingResetTimes, hasRateLimitWindows), WidgetSnapshot,
ProviderMetadata (fableLabel/supportsFable, defaulted so other providers
are unaffected), and the three rateWindowLabels render surfaces (extracted
to a shared RateWindowLabels struct to stay within the large_tuple limit).

Tests cover the real OAuth `limits` payload, the Sonnet+Fable split, the
web limits fallback, the TTY panel, and the fixture contract.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JjzeKiduXP5G2mhGPnu5cT
@clawsweeper

clawsweeper Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codex review: needs real behavior proof before merge. Reviewed July 2, 2026, 6:29 AM ET / 10:29 UTC.

Summary
The PR adds Claude Fable weekly usage parsing and rendering across OAuth/Web/CLI probes, shared snapshots, menu/CLI/widget surfaces, and plan-utilization history.

Reproducibility: yes. for the review finding: source inspection shows WebUsageData stores only fablePercentUsed and the Fable RateWindow then uses webData.weeklyResetsAt. No live provider probe was run because AGENTS.md says real provider checks must be explicitly requested.

Review metrics: 3 noteworthy metrics.

  • Diff scope: 21 files, +415/-23. The change spans shared models, Claude fetchers, menu rendering, CLI rendering, widget rows, history, and tests.
  • Claude data paths: 3 paths changed. OAuth, Web API, and CLI parsing can expose different payload shapes, so each path needs consistent reset semantics.
  • Shared snapshot structs: 2 structs changed. UsageSnapshot and WidgetSnapshot cross app/widget and persisted payload boundaries, so additive compatibility and optional decode behavior matter.

Merge readiness
Overall: 🧂 unranked krab
Proof: 🧂 unranked krab
Patch quality: 🦐 gold shrimp
Result: blocked until real behavior proof is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Add redacted after-fix proof from a real CodexBar surface, such as menu/widget screenshot, CLI terminal output, or logs.
  • [P1] Carry scoped Fable reset times through the web parser and add a test where the scoped reset differs from the all-model weekly reset.

Proof guidance:

  • [P1] Needs real behavior proof before merge: Needs real behavior proof before merge: the PR has upstream OAuth payload evidence but no after-fix CodexBar menu, CLI, widget, terminal output, or logs; proof should be redacted and added to the PR body for re-review. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Mantis proof suggestion
A visible menu/widget or terminal proof would materially help verify the new user-facing Fable row after the provider parser change. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis visual task: verify that a real Claude Fable weekly limit appears with the correct percent and reset in CodexBar menu, CLI, or widget output.

Risk before merge

  • [P2] The web fallback can show a blank or wrong Fable reset when scoped Fable resets_at differs from the all-model weekly reset or when seven_day is absent.
  • [P1] The PR body has upstream OAuth payload evidence but no after-fix CodexBar output from the menu, CLI, widget, terminal, or logs.
  • [P2] The PR explicitly says the web limits fallback was not verified against a real claude.ai web usage payload.

Maintainer options:

  1. Preserve Web Scoped Resets (recommended)
    Carry a Fable-specific reset date through WebUsageData for seven_day_fable and limits[] entries, then test a scoped reset that differs from seven_day.
  2. Require Real Surface Proof
    Ask the contributor to add redacted after-fix proof from the menu, widget, CLI terminal output, or logs before considering the PR merge-ready.
  3. Narrow The Unverified Web Claim
    If maintainers do not want to carry the unverified web path yet, narrow the PR to the confirmed OAuth and CLI paths and track web support separately.

Next step before merge

  • [P2] Contributor proof is still required, and maintainers should decide whether to repair or narrow the unverified web fallback before merge.

Security
Cleared: The diff changes Swift parsing, shared models, rendering, widget, history, and tests only; no dependency, workflow, secret, release, or supply-chain execution surface was changed.

Review findings

  • [P2] Carry the scoped Fable reset through the web path — Sources/CodexBarCore/Providers/Claude/ClaudeUsageFetcher.swift:1221-1222
Review details

Best possible solution:

Carry Fable-specific reset metadata through the web parser and mapper, add a distinct-reset regression test, and require redacted after-fix proof from at least one real CodexBar surface before merge.

Do we have a high-confidence way to reproduce the issue?

Yes for the review finding: source inspection shows WebUsageData stores only fablePercentUsed and the Fable RateWindow then uses webData.weeklyResetsAt. No live provider probe was run because AGENTS.md says real provider checks must be explicitly requested.

Is this the best way to solve the issue?

No as-is: the first-class Fable lane is plausible for widget parity, but the web parser should preserve scoped reset times before the implementation is merge-ready. The PR also needs redacted real CodexBar output because tests alone do not prove the claimed runtime surface.

Full review comments:

  • [P2] Carry the scoped Fable reset through the web path — Sources/CodexBarCore/Providers/Claude/ClaudeUsageFetcher.swift:1221-1222
    When limits[] supplies Fable, the scoped object has its own resets_at, but this builds the Fable window from weeklyResetsAt. A web payload with a missing or different all-model weekly reset will show a blank or wrong Fable reset even though the source payload has the correct value.
    Confidence: 0.9

Overall correctness: patch is incorrect
Overall confidence: 0.86

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 450ca4d91565.

Label changes

Label justifications:

  • P2: This is a normal-priority user-visible Claude provider usage improvement with a bounded parser/rendering blast radius.
  • merge-risk: 🚨 other: Green tests do not settle the unverified web payload path or the scoped reset-time mismatch introduced by the PR.
  • rating: 🧂 unranked krab: Overall readiness is 🧂 unranked krab; proof is 🧂 unranked krab and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: Needs real behavior proof before merge: the PR has upstream OAuth payload evidence but no after-fix CodexBar menu, CLI, widget, terminal output, or logs; proof should be redacted and added to the PR body for re-review. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

What I checked:

Likely related people:

  • kmatsunami: Git blame and PR metadata point to the Claude web/OAuth usage fetcher baseline and CLI rendering path introduced in the merged provider/CLI work. (role: introduced behavior; confidence: high; commits: 5bfd1d1474bb; files: Sources/CodexBarCore/Providers/Claude/ClaudeUsageFetcher.swift, Sources/CodexBarCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift, Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift)
  • Shengqiang-Zhang: Recent Claude web placeholder handling touched the same web usage fetcher and snapshot projection area. (role: recent adjacent contributor; confidence: medium; commits: d2690d4176b3; files: Sources/CodexBarCore/Providers/Claude/ClaudeUsageFetcher.swift, Sources/CodexBarCore/Providers/Claude/ClaudeWeb/ClaudeWebAPIFetcher.swift)
  • steipete: Recent Claude OAuth isolation and usage mapping changes touched adjacent Claude provider refresh paths. (role: recent area contributor; confidence: medium; commits: c57de22c9ffe, 92bbb7a67073; files: Sources/CodexBarCore/Providers/Claude/ClaudeUsageFetcher.swift, Sources/CodexBarCore/Providers/Claude/ClaudeOAuth/ClaudeOAuthUsageFetcher.swift)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@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: 251b385fe5

ℹ️ 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 +1221 to +1222
resetsAt: webData.weeklyResetsAt,
resetDescription: webData.weeklyResetsAt.map { Self.formatResetDate($0) })

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 Preserve the scoped Fable reset time

When the Claude web usage payload falls back to limits[] (or when seven_day_fable has its own reset), the Fable/scoped object carries its own resets_at, but WebUsageData only keeps the percent and this new window reuses the all-model weeklyResetsAt. If seven_day is null/missing or the scoped bucket resets at a different time, the menu/CLI/widget will show a blank or wrong reset for Fable even though the source payload has the correct value; carry the scoped Fable reset through the parser and use it here.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P2 Normal priority bug or improvement with limited blast radius. labels Jul 2, 2026
@clawsweeper clawsweeper Bot added rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. and removed rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. labels Jul 2, 2026
@chrisw1005

Copy link
Copy Markdown
Author

Updated the description with a trimmed real response from the official /api/oauth/usage endpoint as evidence.

On the newest payload the top-level seven_day_opus / seven_day_sonnet / seven_day_fable keys are null, and the model-scoped weekly limits (including Fable) now arrive through the limits array via scope.model.display_name. This PR reads that (keeping seven_day_fable as a fallback) and, as a side effect, also restores the Sonnet/Opus weekly slot on the new payload shape.

swift build, swift test, swiftformat --lint, and swiftlint --strict all pass. Happy to switch to the lighter extraRateWindows route instead of the first-class quaternary window if you'd prefer — the reason I went with quaternary was widget parity (extra windows don't reach WidgetSnapshot).

@steipete

steipete commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Thanks for the broader implementation and proof, @chrisw1005. We landed the narrower shared scoped-weekly mapping in #1851 (21e3beb3), covering both Claude OAuth and web usage responses without introducing separate widget/CLI parsing paths.

Closing this conflicted variant as superseded. If widget presentation still needs a dedicated adjustment after the shared snapshot propagation, that can be a focused follow-up.

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

Labels

merge-risk: 🚨 other 🚨 Merging this PR has meaningful risk outside the owned taxonomy. P2 Normal priority bug or improvement with limited blast radius. rating: 🧂 unranked krab Not merge-ready due to missing proof or serious correctness/safety concerns. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants