Skip to content

Quota gauge always reads ~/.claude/.credentials.json, ignoring CLAUDE_CONFIG_DIR and dataDirectory #89

Closed

Description

@HoangJN

Version 2.2.2, Windows 11.

I run two Claude Code profiles on one machine, a personal account in VS Code and a work Team seat in a VS Code fork, kept apart with CLAUDE_CONFIG_DIR. The extension is installed in both windows.

The history side works exactly as advertised. Point claudeCodeUsage.dataDirectory at the right profile and the token counts, cost and project breakdown come out correct and separate. What threw me was the status bar: both windows showed the same 5-hour and weekly percentage, which had me convinced for a while that the two accounts had somehow been merged into one on Anthropic's end.

They hadn't. ClaudeApiClient builds its credentials path from os.homedir() and a literal .claude:

// out/claudeApiClient.js:31
this.credentialsPath = path.join(os.homedir(), '.claude', '.credentials.json');

So whatever dataDirectory or CLAUDE_CONFIG_DIR say, the quota call authenticates as whoever owns ~/.claude. Correct numbers in the panel, wrong account in the gauge directly above them, and nothing anywhere that looks like an error.

Repro

  1. Second profile at ~/.claude-second, signed in to a different account, editor launched with CLAUDE_CONFIG_DIR=~/.claude-second.
  2. Set claudeCodeUsage.dataDirectory to ~/.claude-second in that window.
  3. Tokens, cost and project breakdown: correct and distinct per window.
  4. Status bar quota: identical in both, matching the ~/.claude account.

The other half of it

saveCredentials() writes refreshed tokens back to the same hardcoded path, so my work window was rotating the personal account's credentials file. Nothing breaks, but it cost me a good while working out why ~/.claude/.credentials.json kept changing timestamp when I hadn't touched that profile all day.

Fix

ClaudeDataLoader.getClaudePaths() already resolves this properly, comma-separated form included. The credentials path just doesn't use it. Minimal version that fixes the reported case:

function resolveClaudeConfigDir() {
    const env = (process.env.CLAUDE_CONFIG_DIR || '').trim().split(',')[0].trim();
    if (env !== '') {
        try {
            if (fs.existsSync(env) && fs.statSync(env).isDirectory()) {
                return env;
            }
        }
        catch { }
    }
    return path.join(os.homedir(), '.claude');
}

Passing the window's already-resolved dataDirectory down into the client would be better again, since anyone who sets dataDirectory to a profile has effectively told you which account that window represents.

One caveat: the macOS fallback reads a single Claude Code-credentials Keychain item, so there's no per-profile path to fix there and it needs its own decision. The above covers Windows and Linux.

I don't think this duplicates #45. That was a switched account inside one profile going stale; this is two profiles running side by side, both holding valid credentials.

I've patched my local copy and it has reported the right account since. Happy to send a PR if you'd prefer it that way.

Activity

  1. github-actions commented on Aug 18, 2026

    @github-actions

    🤖 Automated first-pass reply

    **TL;DR**

    The report is consistent with the documented architecture: quota is a separate path owned by claudeApiClient, which reads the hardcoded ~/.claude/.credentials.json, while log-based numbers flow through dataLoader and honor the dataDirectory setting. So the status-bar gauge and the dashboard panel genuinely can disagree per profile. The proposed fix direction is sound, and a PR is welcome.

    Analysis

    • ARCHITECTURE.md confirms the exact asymmetry: dataLoader.ts reads ~/.claude/projects/**/*.jsonl through the config-respecting path, whereas claudeApiClient.ts "reads ~/.claude/.credentials.json" as a literal path. Because quota and local logs are independent pipelines, one profile's CLAUDE_CONFIG_DIR/dataDirectory will not affect the other's gauge — matching the repro.
    • dataDirectory is one of the three core settings read via SettingsStore (settings.ts), so the right architectural fix is to resolve the credentials path from the same source the extension already uses, rather than duplicating a third resolution. Prefer reusing the existing resolver (getClaudePaths() per the report) in claudeApiClient, with the window's resolved dataDirectory winning and CLAUDE_CONFIG_DIR/home fallback when unset — keeping the "new behavior defaults to existing behavior" invariant.
    • The `saveCredentials

    🤖 Generated by DeepSeek as an automated first pass — not a maintainer decision.

  2. Carl723000 commented on Aug 20, 2026

    @Carl723000
    Collaborator

    Thank you for the precise report, especially for tracing both the incorrect read and refresh-write behaviour to claudeApiClient.js:31.

    We will include the fix in v2.3.0. The quota client will resolve credentials from the same active Claude profile used by the window instead of always reading and writing ~/.claude/.credentials.json, with tests covering an explicitly configured directory, CLAUDE_CONFIG_DIR, and the default profile fallback.

    We will also avoid silently substituting the global macOS Keychain identity for an explicitly selected custom profile when that could make the gauge represent a different account. Your report will be credited in the changelog.


    🤖 Generated with OpenAI Codex

  3. Carl723000 commented on Aug 28, 2026

    @Carl723000
    Collaborator

    Implemented in #93 for v2.3.0. Quota credentials now follow the active profile: explicit dataDirectory, then the first valid CLAUDE_CONFIG_DIR, then the default profile. A selected custom profile no longer silently falls back to the global macOS Keychain identity. Reads, refresh writes, and persisted quota snapshots are covered by regression tests.\n\nThank you for the precise report. Please reopen this issue if the two profiles still show the same quota after updating to v2.3.0.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions