Skip to content

Support selecting / switching the Claude organization (multi-org accounts locked to first org) #27

Description

@sllnnx

Feature request: support selecting / switching the Claude organization

Summary

ClaudeMeter currently has no way to choose which Claude organization it reports
usage for. On accounts that belong to more than one organization (e.g. a
personal org plus one or more team/enterprise orgs), the app silently locks onto
whichever org happens to be returned first by the Claude API and offers no UI
to change it. Users on the "wrong" org see usage numbers that don't match
claude.ai/settings/usage, with no obvious cause or remedy.

This request asks for an organization picker (and/or automatic use of the
account's currently active org).

Environment

  • App: ClaudeMeter (Homebrew cask eddmann/tap/claudemeter)
  • Bundle ID: com.eddmann.ClaudeMeter
  • macOS menu-bar build, session-key auth (claude.ai cookie)
  • Account: member of multiple organizations

Current behavior

  1. User configures a session key (auto-import or manual paste).
  2. The menu bar shows usage for one organization.
  3. That organization is not the one the user wants, and there is no setting
    anywhere in the app to change it.
  4. None of the obvious user-side actions have any effect:
    • Switching the active org inside claude.ai in the browser.
    • Re-importing / re-pasting the session key.
    • Clearing and re-adding the session key.

Expected behavior

When an account belongs to multiple organizations, the user can either:

  • pick the target organization explicitly from a list (preferred), and/or
  • have the app follow the account's currently active organization,

and the menu-bar usage updates to that organization.

Root cause

The organization is resolved once and cached, with no path back to the other
orgs the account can see.

Org resolutionClaudeMeter/Services/UsageService.swift
The org id is chosen in this priority order:

  1. settings.cachedOrganizationId (if set, always wins)
  2. sessionKey.organizationId (typically nil for a manually pasted key)
  3. organizations.first from GET /api/organizations

The usage call is then made against
…/organizations/{organizationId}/usage.

Where the cache is writtenClaudeMeter/App/AppModel.swift,
validateAndSaveSessionKey(_:)
On every session-key save, the app fetches the org list and unconditionally
takes the first element:

let organizations = try await usageService.fetchOrganizations(sessionKey: sessionKey)
guard let firstOrg = organizations.first,
      let orgUUID = firstOrg.organizationUUID else {
    throw AppError.organizationNotFound
}
...
settings.cachedOrganizationId = orgUUID

fetchOrganizations (UsageService.swift) already retrieves all
organizations via GET /api/organizations (OrganizationListResponse), but only
.first is ever used; the rest are discarded.

Reset pathAppModel.clearSessionKey() sets
settings.cachedOrganizationId = nil, but the next setup re-runs the
organizations.first logic and lands on the same org again.

Why each workaround fails

Action Why it has no effect
Switch active org in claude.ai The server-side order of GET /api/organizations does not change, and cachedOrganizationId takes precedence regardless.
Re-import / re-paste session key Re-runs validateAndSaveSessionKey, which again picks organizations.first.
Clear + re-add session key clearSessionKey() nils the cache, but re-setup re-pins to the first org.
Re-auth Claude Code CLI This app reads a claude.ai sessionKey cookie, not the CLI OAuth token, so the CLI's org is irrelevant.

Current manual workaround (for reference)

The only way to change the org today is to overwrite the cached UUID directly in
UserDefaults (key app_settings, field cached_organization_id, domain
com.eddmann.ClaudeMeter) and relaunch. This requires knowing the target org's
UUID (e.g. from https://claude.ai/api/organizations) and is clearly not a
user-facing solution. It also gets wiped by "Clear session key".

Proposed solution

  1. Organization picker in Settings.
    • After a session key validates, call the existing fetchOrganizations() and
      present all returned orgs (name + UUID) in a Picker, similar to the
      existing refresh-interval / icon-style pickers in SettingsView.
    • Persist the selected UUID to settings.cachedOrganizationId.
    • Default the selection to organizations.first to preserve current behavior
      for single-org accounts.
  2. Re-selectable without re-auth.
    • Allow changing the org from Settings at any time without clearing the
      session key, writing the new UUID to cachedOrganizationId and forcing a
      refresh.
  3. Optional: show org name in the menu / Settings so the user can confirm at
    a glance which org is being tracked.
  4. Optional: "follow active org" mode that re-resolves the active org on each
    refresh instead of caching, for users who switch orgs frequently.

Acceptance criteria

  • A multi-org account can select any of its organizations from the app UI.
  • The selected org persists across relaunch.
  • The org can be changed later without removing the session key.
  • Single-org accounts see no change in behavior or extra prompts.
  • The currently tracked org is identifiable from the UI.

Implementation notes

Most of the plumbing already exists:

  • fetchOrganizations() already returns the full OrganizationListResponse.
  • AppSettings.cachedOrganizationId is already persisted and already consumed
    with top priority by UsageService.

The main work is UI (a picker + a "tracked org" label) plus a settings-change
hook that updates cachedOrganizationId and triggers refreshUsage(forceRefresh:),
rather than only writing it inside validateAndSaveSessionKey.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions