Skip to content

Fix Claude 0/0 usage with multi-source limits + auto-refresh#29

Merged
BlockedPath merged 3 commits into
mainfrom
fix/claude-multi-source-limits
May 21, 2026
Merged

Fix Claude 0/0 usage with multi-source limits + auto-refresh#29
BlockedPath merged 3 commits into
mainfrom
fix/claude-multi-source-limits

Conversation

@BlockedPath

Copy link
Copy Markdown
Owner

Summary

  • Daemon's Claude path was returning s:0, w:0 because 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 no rate_limits field (only emitted under some Pro auth states), and the token-count fallback hit 100% used against a hardcoded 200k daily budget.
  • Daemon now tries ~/.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.
  • New scripts/claude-statusline-limits.py persists rate_limits whenever Claude Code statusline emits them.
  • install.sh now 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/ clean
  • Restart daemon, hit http://localhost:9596/usage → returns real s/w percentages ({"s":57,"sr":197,"w":96,"wr":2127,"st":"5h 43% used · 7d 4% used"})
  • iOS app shows non-zero values after force-quit/reopen

🤖 Generated with Claude Code

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>

@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: 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".

Comment thread daemon/codex-usage-daemon.py Outdated
BlockedPath and others added 2 commits May 21, 2026 07:44
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>
@BlockedPath
BlockedPath merged commit ba94a46 into main May 21, 2026
4 checks passed
@BlockedPath
BlockedPath requested a review from Copilot May 21, 2026 12:02
@BlockedPath BlockedPath self-assigned this May 21, 2026

Copilot AI 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.

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.py to persist Claude Code rate_limits into 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:
Comment thread install.sh
Comment on lines +82 to +92
<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
Comment on lines 982 to +986
def local_claude_activity_snapshot() -> UsageSnapshot:
hook_snapshot = claude_hook_limits_snapshot()
if hook_snapshot is not None:
return hook_snapshot

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