Skip to content

feat(usage): read claude-cli limits from Claude Code's login - #94

Merged
k3dom merged 2 commits into
masterfrom
usage-claude-cli-login
Oct 21, 2026
Merged

k3dom merged 2 commits into
masterfrom
usage-claude-cli-login

Conversation

@k3dom

@k3dom k3dom commented Oct 21, 2026

Copy link
Copy Markdown
Owner

What and why

claude-cli sends every request through the claude CLI, so it bills the account Claude Code is logged into. The usage plugin still read Claude limits from pi's own Anthropic /login. After Claude Code switched accounts (in my case through tagteam), the widget showed limits for the wrong account.

With this change:

  • Widget for claude-cli models: reads claudeAiOauth.accessToken from $CLAUDE_CONFIG_DIR/.credentials.json (default ~/.claude/) and queries the same usage and profile endpoints as before. The claude-code limits are cached apart from the anthropic ones.
  • Token renewal: the plugin only reads the token. Claude Code rotates the refresh token on every renewal, so renewing it here could sign Claude Code out. An expired token shows up as an inline error until Claude Code renews it on its next request.
  • /usage: when the claude-cli provider is registered, the report adds a "Claude Code" section. It drops the pi "Claude" section unless pi has its own Anthropic OAuth login. Without claude-cli, the report is unchanged.
  • New errors: a missing login, an unreadable credentials file and an expired token each get their own error in UsageServiceError.
  • Docs and release: updated the usage and claude-cli READMEs, which no longer say to sign both in to the same account, and added a minor changeset.

Verification

  • nix develop -c ci passes: format, lint, type check, build and tests.
  • Bundled a scratch script against UsageService.claudeCode() and ran it:
    • Against my real Claude Code login: the section heading showed the plan and the email from ~/.claude.json (Claude Code (Max 5x) — <the Claude Code account>), with 4 limit rows.
    • With CLAUDE_CONFIG_DIR pointing at test directories: a missing file, invalid JSON, a file with no claudeAiOauth, and an expired expiresAt each gave the expected error.
  • I did not try the widget in an interactive pi session.

Review findings I skipped

  • Em dashes in the new error messages. They match the existing messages in the same union (… — run /login to (re-)authenticate), which the report shows inline. Changing only the new ones would mix two styles.
  • Deciding on the pi "Claude" section with readStoredCredential in index.ts. The reviewer suggested running the section and dropping it on CredentialsMissing instead. That would make sections optional results, which seemed more machinery than the one-line check.
  • homedir() from node:os. Effect has no home-directory API, and HOME isn't set on Windows.

Open questions

  • macOS: Claude Code keeps its login in the Keychain there (Claude Code-credentials, with a hash suffix per config dir). The plugin doesn't read it, and says so in the missing-login error. Reading it through security find-generic-password would raise a one-time access prompt, so I left it for a follow-up.

@k3dom
k3dom merged commit d1ab3ff into master Oct 21, 2026
2 checks passed
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