Skip to content

feat: add subscription description from Keychain - #237

Open
robbash wants to merge 1 commit into
griffinmartin:mainfrom
robbash:feat-show-credentials-description
Open

feat: add subscription description from Keychain#237
robbash wants to merge 1 commit into
griffinmartin:mainfrom
robbash:feat-show-credentials-description

Conversation

@robbash

@robbash robbash commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Append the comment field in Keychain if not empty, otherwise keep enumerating. This is useful to select the right subscription if multiple Claude subscriptions of the same kind (ie. "Claude Pro") are found in Keychain.

Related issue

Testing

  • Have multiple Claude subscriptions authenticated in keychain
  • Run the selection opencode auth login -p anthropic

Checklist

  • PR title follows Conventional Commits (feat:, fix:, docs:, chore:, etc.)
  • make all passes locally (runs lint, build, and test)
  • Tests added or updated where applicable
  • README or docs updated where applicable

@griffinmartin

Copy link
Copy Markdown
Owner

@greptileai

@greptile-apps

greptile-apps Bot commented Jul 25, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds Keychain comment (icmt) support to the multi-account selection flow, so that when a user has multiple Claude subscriptions of the same plan type the UI can display user-defined names (e.g. "Claude Pro - Work") instead of bare auto-incremented numbers.

  • Introduces parseKeychainDump (exported, tested) that replaces the old single-regex scan with a block-based parser capturing svce, 0x00000007, icmt, desc, and acct attributes.
  • Adds deriveKeychainDescription which extracts and trims the icmt comment field and returns undefined for blank/absent values.
  • Refactors readAllClaudeAccounts to append the Keychain comment to the plan label when present, falling back to the existing numeric-suffix disambiguation when it is absent.

Confidence Score: 3/5

The core parsing and description-extraction helpers look correct and are well-tested, but the label-building step in readAllClaudeAccounts has a logic gap that can produce confusing UI labels in the mixed-description scenario this PR is specifically designed to solve.

When a user has two "Claude Pro" accounts where one carries a Keychain comment and the other doesn't, the undescribed account is labelled "Claude Pro 2" — the numeric suffix is derived from a count that includes described accounts, so there is no visible "Claude Pro 1" in the picker. The PR's stated goal is exactly this disambiguation scenario, and the broken numbering undercuts it for the most common real-world split (one named, one anonymous).

Files Needing Attention: src/keychain.ts — specifically the label-construction block inside readAllClaudeAccounts starting at the baseLabels/numberedLabels split

Important Files Changed

Filename Overview
src/keychain.ts Adds parseKeychainDump/deriveKeychainDescription helpers and refactors readAllClaudeAccounts to use Keychain comments for labels; the label-numbering logic misfires when only some accounts of the same plan type carry descriptions
src/keychain.test.ts Adds comprehensive tests for parseKeychainDump and deriveKeychainDescription covering NULL attributes, multi-entry dedup, and edge cases; no issues found

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[readAllClaudeAccounts] --> B{darwin?}
    B -- No --> C[readCredentialsFile]
    C --> D[buildAccountLabels]
    D --> E[Return file account]
    B -- Yes --> F[listClaudeKeychainEntries]
    F --> G[execSync security dump-keychain]
    G --> H[parseKeychainDump]
    H --> I[Filter startsWith PRIMARY_SERVICE]
    I --> J[Dedup by service, primary first]
    J --> K[Return ordered KeychainEntry list]
    K --> L[readKeychainService per entry]
    L --> M[parseCredentials]
    M --> N[deriveKeychainDescription]
    N --> O{has description?}
    O -- Yes --> P["label = baseLabels[i] + description"]
    O -- No --> Q["label = numberedLabels[i] ⚠️ counts described accts too"]
    P --> R[Return ClaudeAccount]
    Q --> R
Loading

Fix All in Cursor Fix All in Claude Code Fix All in Codex

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
src/keychain.ts:291-313
**Misleading numeric suffix when described and undescribed accounts are mixed**

`numberedLabels` is built from **all** raw accounts, including those that already have a Keychain description. That means when two "Claude Pro" accounts exist and only one has a comment, `buildAccountLabels` counts both and produces `["Claude Pro 1", "Claude Pro 2"]`. The described account consumes slot 0 silently, so the undescribed one ends up labelled `"Claude Pro 2"` — implying a "Claude Pro 1" that never appears in the UI.

Concrete case: accounts `[{Pro, desc="Work"}, {Pro, no desc}]` → user sees `"Claude Pro - Work"` and `"Claude Pro 2"`. Only passing credentials from *undescribed* accounts to `buildAccountLabels` (then stitching the labels back at the right indices) would produce the correct `"Claude Pro"` (or `"Claude Pro 1"` / `"Claude Pro 2"` when multiple undescribed entries share a plan).

Reviews (1): Last reviewed commit: "feat: add subscription description from ..." | Re-trigger Greptile

Comment thread src/keychain.ts
Comment on lines +291 to 313
// Build base labels (e.g. "Claude Pro") without disambiguating numbers.
const baseLabels = rawAccounts.map((a) => {
const sub = a.credentials.subscriptionType
if (sub) return `Claude ${sub.charAt(0).toUpperCase() + sub.slice(1)}`
return "Claude"
})

// Only fall back to numeric suffixes for entries without a Keychain comment.
const numberedLabels = buildAccountLabels(
rawAccounts.map((a) => a.credentials),
)

return rawAccounts.map((a, i) => ({
label: labels[i],
// Append the user-set Keychain comment after the plan so the UI shows
// e.g. "Claude Pro - Claude Sub Duncan". Without a comment, fall back to
// the disambiguated label (e.g. "Claude Max 2").
label: a.description
? `${baseLabels[i]} - ${a.description}`
: numberedLabels[i],
source: a.source,
credentials: a.credentials,
description: a.description,
}))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Misleading numeric suffix when described and undescribed accounts are mixed

numberedLabels is built from all raw accounts, including those that already have a Keychain description. That means when two "Claude Pro" accounts exist and only one has a comment, buildAccountLabels counts both and produces ["Claude Pro 1", "Claude Pro 2"]. The described account consumes slot 0 silently, so the undescribed one ends up labelled "Claude Pro 2" — implying a "Claude Pro 1" that never appears in the UI.

Concrete case: accounts [{Pro, desc="Work"}, {Pro, no desc}] → user sees "Claude Pro - Work" and "Claude Pro 2". Only passing credentials from undescribed accounts to buildAccountLabels (then stitching the labels back at the right indices) would produce the correct "Claude Pro" (or "Claude Pro 1" / "Claude Pro 2" when multiple undescribed entries share a plan).

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/keychain.ts
Line: 291-313

Comment:
**Misleading numeric suffix when described and undescribed accounts are mixed**

`numberedLabels` is built from **all** raw accounts, including those that already have a Keychain description. That means when two "Claude Pro" accounts exist and only one has a comment, `buildAccountLabels` counts both and produces `["Claude Pro 1", "Claude Pro 2"]`. The described account consumes slot 0 silently, so the undescribed one ends up labelled `"Claude Pro 2"` — implying a "Claude Pro 1" that never appears in the UI.

Concrete case: accounts `[{Pro, desc="Work"}, {Pro, no desc}]` → user sees `"Claude Pro - Work"` and `"Claude Pro 2"`. Only passing credentials from *undescribed* accounts to `buildAccountLabels` (then stitching the labels back at the right indices) would produce the correct `"Claude Pro"` (or `"Claude Pro 1"` / `"Claude Pro 2"` when multiple undescribed entries share a plan).

How can I resolve this? If you propose a fix, please make it concise.

Fix in Cursor Fix in Claude Code Fix in Codex

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