Fix Claude 0/0 usage with multi-source limits + auto-refresh#29
Conversation
The daemon's Claude path returned 0/0 because: - claude-plan-usage.json was past the 10-min TTL (no scheduler) - claude-statusline.json had no rate_limits (only set for some Pro auth states) - Token-counting fallback compared 1.2M+ tokens to a hardcoded 200k budget Changes: - Daemon now reads ~/.claude/plan-limits.json (statusline hook output) first, then the existing chrome-scrape cache, then statusline rate_limits, then the token-count fallback. Bumped chrome TTL to 60 min so partially stale data still surfaces real usage. - New scripts/claude-statusline-limits.py persists rate_limits from Claude Code statusLine into ~/.claude/plan-limits.json when present. - install.sh now schedules scripts/claude-plan-usage-cache.py via launchd every 10 min on macOS, keeping the chrome-scrape cache fresh when a claude.ai/usage tab is open. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 15e7060666
ℹ️ 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".
Treat a hook payload missing either window as unknown and defer to the next source. Otherwise a partial payload would override a complete cached source with 100% remaining for the missing window. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
End-to-end staleness was dominated by the 10-minute scrape window; drop it so iOS sees fresh percentages within ~60s + 30s daemon poll + 30s app poll = ~2 minutes worst case. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR improves Claude usage reporting in CodexMeter by adding a new “hook-written limits” data source, extending cache TTLs, and adding an auto-refresh mechanism so the daemon no longer falls back to misleading 0/0 usage when upstream sources are stale or missing fields.
Changes:
- Add
scripts/claude-statusline-limits.pyto persist Claude Coderate_limitsinto a normalized~/.claude/plan-limits.json. - Update the daemon to prefer hook limits first, and to use configurable TTLs for both hook and chrome-scrape caches.
- Extend the installer to add a macOS LaunchAgent that periodically refreshes the Claude plan usage cache.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
scripts/claude-statusline-limits.py |
New helper script that normalizes and persists statusline rate limit data to a stable JSON file for the daemon to consume. |
scripts/claude-plan-usage-cache.py |
Minor cleanup (remove unused import) in the chrome-scrape cache script. |
install.sh |
Adds a macOS LaunchAgent to periodically run the chrome-scrape cache updater. |
daemon/codex-usage-daemon.py |
Adds hook-limits snapshot support, prioritizes it in local Claude usage resolution, and makes TTLs configurable. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
|
|
||
| def normalized_limits(statusline: dict) -> dict | None: |
| <key>StartInterval</key><integer>60</integer> | ||
| <key>RunAtLoad</key><true/> | ||
| <key>StandardOutPath</key><string>$HOME/Library/Logs/codexmeter-claude-scrape.log</string> | ||
| <key>StandardErrorPath</key><string>$HOME/Library/Logs/codexmeter-claude-scrape.err.log</string> | ||
| </dict> | ||
| </plist> | ||
| PLIST | ||
| launchctl unload "$SCRAPE_PLIST" >/dev/null 2>&1 || true | ||
| launchctl load "$SCRAPE_PLIST" | ||
| echo "Installed LaunchAgent: $SCRAPE_PLIST (every 60s)" | ||
| fi |
| def local_claude_activity_snapshot() -> UsageSnapshot: | ||
| hook_snapshot = claude_hook_limits_snapshot() | ||
| if hook_snapshot is not None: | ||
| return hook_snapshot | ||
|
|
Summary
s:0, w:0because all three data sources had become inert: the chrome-scrape cache was past its 10-min TTL with no scheduler refreshing it, the Claude Code statusline JSON had norate_limitsfield (only emitted under some Pro auth states), and the token-count fallback hit 100% used against a hardcoded 200k daily budget.~/.claude/plan-limits.json(hook output) →claude-plan-usage.json(chrome) →claude-statusline.json(rate_limits) → token-count fallback. Chrome TTL bumped to 60 min so slightly stale Pro data still surfaces over the 0% fallback.scripts/claude-statusline-limits.pypersistsrate_limitswhenever Claude Code statusline emits them.install.shnow installs a second LaunchAgent that runs the chrome-scrape every 10 min, so the cache stays fresh as long as a claude.ai/usage tab is open in Chrome.Test plan
pytest -q(11 passed)ruff check daemon/ scripts/cleanhttp://localhost:9596/usage→ returns reals/wpercentages ({"s":57,"sr":197,"w":96,"wr":2127,"st":"5h 43% used · 7d 4% used"})🤖 Generated with Claude Code