← Blog Security & Trust 15 September 2026 9 min read

Signing in the vendor's way: one keychain slot per account

In January 2026 Anthropic started blocking tools that pretend to be Claude Code. Luminair takes the plain route: Anthropic's own command-line tool does every Claude sign-in, in a private folder per account. Here is how that works, and the one environment variable that still managed to overwrite an account.

Fig 01  One folder, one hash, one Keychain slotHow the Claude CLI names a credential
ACCOUNT FOLDER · CLAUDE_CONFIG_DIR SHA-256, FIRST 8 HEX MACOS KEYCHAIN ITEM Personal account …/claude-cli-homes/acct-a 18ec3c58 Claude Code-credentials-18ec3c58 Team workspace, same email …/claude-cli-homes/acct-b 21f10ef6 Claude Code-credentials-21f10ef6 A second personal account …/claude-cli-homes/acct-c 7549dc92 Claude Code-credentials-7549dc92 THE OLD WAY · LEGACY SHARED SLOT Whatever folder the app process points at no suffix Claude Code-credentials one login at a time; token copied out by the app
The naming rule is real and taken from desktop/engines/claude-cli.js; we also checked that slot names on a real Mac match the hashes of its account folders. The folder paths here are made-up examples, and the hashes are the real SHA-256 prefixes of those example strings.

The short version

  1. When you add a Claude account, Luminair runs Anthropic's own Claude Code tool to sign you in. It does not build its own login or copy your token out.
  2. Each account gets a private folder. The tool names its Keychain entry after a hash of that folder, so every account has its own slot and one sign-in cannot replace another.
  3. On 14 September 2026 that broke anyway. An app relaunched from a working session inherited that session's folder, and an old login path signed a Team workspace into the Primary account's slot.
  4. The fix strips inherited session variables at startup, gives every login a clean environment, and reopens the app with an empty one. Nine tests lock it in.
02What changed in January

The end of pretending.

A Claude subscription is priced for use through Claude Code. For a while, some third-party tools used that subscription by making their own requests look like they came from Claude Code. VentureBeat described the technique in January 2026 as “spoofing the client identity, sending headers that convince the Anthropic server the request is coming from its own official command line interface (CLI) tool.”

The same report, on 9 January, said Anthropic had confirmed “strict new technical safeguards” against this, a move that disrupted users of the open-source agent OpenCode. The Hacker News thread, “Anthropic blocks third-party use of Claude Code subscriptions”, passed 600 points. In February, The Register reported that Anthropic had rewritten its legal page to make the rule explicit. Anthropic engineer Thariq Shihipar gave the reason in engineering terms: such tools “generate unusual traffic patterns without any of the usual telemetry that the Claude Code harness provides, making it really hard for us to help debug when they have questions about rate limit usage or account bans”.

We are not going to read terms of service for you, and this post makes no legal claim. The engineering lesson is simpler. Anything that fakes being the vendor's client is fragile: it breaks the day the vendor checks. The sturdy design is to let the vendor's own tool do the part that is the vendor's business, which is who you are and what your token is.

03How Luminair signs in

The real binary, in its own folder.

Luminair ships the Claude Code binary inside the app. When you click Add a Claude account, it does four things, all visible in desktop/engines/claude-cli.js:

01

Make a fresh folder

A new, empty folder under the app's data directory, one per account. It becomes that account's CLAUDE_CONFIG_DIR, the variable that tells Claude Code where its settings and sign-in live.

02

Run the vendor's login

claude auth login --claudeai, inside a terminal the app controls, pointed at that folder. The CLI opens your browser itself. You approve on Anthropic's page.

03

Hand the code back

The CLI then waits at “Paste code here if prompted”. Luminair shows a paste box, or picks the code up from your clipboard when you press Copy on Anthropic's page, and types it into the CLI.

04

Ask the CLI who you are

It polls claude auth status --json in the same folder. When that reports signed in, it reads your email and organization from it and runs the checks in section 7.

Claude Code stores the credential itself, in the macOS login Keychain. The item is named Claude Code-credentials- plus the first eight hex characters of the SHA-256 of the folder path. Different folder, different name, different slot. That is Fig 01.

From then on, every turn on that account runs the same binary with the same folder. The usage meter asks the CLI for its numbers through a control request on its own channel. The engine file puts it bluntly: “We never read, copy, or refresh the credential; the CLI uses its own keychain slot for its own fetch.”

04Why not one shared slot

One slot holds one login.

Luminair did not start this way. The older sign-in, still in main.js as accounts:startLogin, ran the same CLI command without a private folder. The CLI wrote to the default slot, plain Claude Code-credentials. The app took a snapshot of that slot, waited for a new token to appear, and stored its own copy per account. Its own comment admits the limit: “The shared keychain holds one login at a time.”

That design has two weak points. The app holds copies of tokens it did not need to hold. And the whole thing depends on which folder the CLI thinks is home when it runs. Nobody expected that to change. It did.

0514 September

An app that inherited a session.

Luminair is often updated from inside one of its own sessions. An update script stages the new build, and a small chain of helpers installs it and reopens the app: update-app.sh, then keep.sh, then install-staged-update.sh, then macOS open -a.

That session was running Claude Code for the Primary account, so its shell had CLAUDE_CONFIG_DIR set to the Primary account's folder, along with CLAUDECODE, CODEX_HOME and others. open passed that environment on. The relaunched app came up believing it lived inside Primary's folder.

Fig 02  How one variable crossed into the appSchematic · 14 Sep 2026
Session shell CLAUDE_CONFIG_DIR= …/primary Update chain update-app.sh › keep.sh › install-staged-update.sh › open -a Luminair, reopened still carries CLAUDE_CONFIG_DIR Legacy Add account login claude auth login, env: process.env Team sign-in writes to Primary's slot, overwritten next turn: organization has disabled access App watches the shared slot never sees the sign-in finish FIXED BY scrub at startup clean env for every login reopen under env -i no legacy add Filled dot: shipped. Dashed box: the path the app was watching.
Reconstructed from the commit messages of 5fc52f1c and de0c2ee5 and the comment in desktop/lib/core/inherited-env.js. The folder path is shortened.

Then an account was added through the old path. It spawned claude auth login with the app's full environment, so the CLI ran inside Primary's folder. The browser signed into a Team workspace, and the CLI saved that credential into Primary's slot, as it should for that folder. Meanwhile the app was watching the shared slot, so as far as it could tell the sign-in never connected.

The next turn on Primary failed with “Your organization has disabled Claude subscription access”. The account had not been banned. It had been swapped for a different workspace. The same day, a second existing account was overwritten the same way.

Why this was hard to seeNothing crashed. The sign-in looked like it failed, the account looked like it was blocked, and the real cause was a variable set in a different process, two helpers earlier, that nobody had typed.
06Three fixes

Belt, braces, and a clean reopen.

The fix, in commit 5fc52f1c, closes the leak three times over, so no single miss can reopen it.

First, scrub at startup. A new module, inherited-env.js, lists every variable that only makes sense inside one session or one helper job. It runs at the very top of main.js, before any process is spawned, and deletes those keys from the app's own environment. What it removed is written to the security log as app.inherited-session-env.

desktop/lib/core/inherited-env.jswhat never reaches the app
const EXACT = new Set([
  'CLAUDE_CONFIG_DIR',        // per-account sign-in folder of a running session
  'CLAUDECODE',               // "you are inside a Claude Code session" marker
  'CLAUDE_PID',
  'CLAUDE_AGENT_SDK_VERSION',
  'CLAUDE_CODE_OAUTH_TOKEN',  // a session's inference token
  'CODEX_HOME',               // per-account sign-in folder of a Codex session
  'CODEX_CI',
  'CODEX_THREAD_ID',
  'CODEX_SESSION_ID',
]);
const PREFIXES = ['CLAUDE_CODE_', 'OW_KEEP_', 'OW_MCP_'];

Second, a clean environment for every login. Both sign-in paths now spawn the CLI with a filtered copy, so they do not depend on the scrub having run. The per-account path also drops ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL and the Bedrock, Vertex and Foundry switches before setting its own folder, so a stray API key in your shell cannot quietly take over a subscription account.

Third, reopen with nothing. The installer now reopens the app under env -i, passing only HOME, USER, LOGNAME and a minimal PATH.

The same afternoon, a separate commit, de0c2ee5, removed the old path from the front door. Add a Claude account and both re-login entry points now go through the per-account login. The legacy flow is kept only for old token accounts that still need it.

07Who signed in?

An email is not an identity.

The overwrite had a second lesson. A personal account and a Team workspace can share one email address. Luminair used to de-duplicate accounts by email alone, so adding a Team workspace looked like adding a twin of the personal one, and it was dropped. Since 14 September an account's identity is its email plus its organization, both read from the CLI, and a Team account carries a Team badge and its organization's name.

With that, the per-account login checks the result before it keeps anything:

Fig 03  What a finished sign-in must passFrom login() in claude-cli.js
No email or organization“Could not verify the signed-in account and organization. Please try again.” The new folder is deleted.
Re-login came back as someone else“You signed into a different account or organization. Use Add account to connect it; the existing login was kept.”
Already connectedRefused by name, so a browser that auto-picks the wrong account cannot silently reuse an existing record.
Re-login, same identityThe account moves to the new folder. The old one is left alone, since a turn in flight may still be using it.
New identityA new account is added with its own folder and its own slot.
Error texts are verbatim from the source. Every failure path also deletes the attempt's folder and its Keychain item.

That last clean-up came a day later. On 15 September we found that removing a folder left its Keychain item behind: a dozen orphaned credentials had piled up from failed and removed sign-ins. Removing or failing a Claude sign-in now also runs security delete-generic-password on the matching slot. The same commit fixed a sign-in that timed out every time: the bundled CLI was waiting for the browser's code, and nothing was handing it back.

08Find it in the app

Add an account, safely.

  1. 1Open Settings › Connectors, choose Claude, then Add a Claude account. Your browser opens on Anthropic's sign-in page.
  2. 2Approve, then press Copy on the code page. Luminair picks the code up from the clipboard and says so; you can also paste it into the box yourself.
  3. 3If you use a personal account and a Team workspace on one email, add each one. They show as separate accounts, the Team one with its badge and organization name.

Removing an account deletes its folder and its Keychain item together. The app only ever removes folders inside its own data directory.

09What we checked

Checked, and not claimed.

9/9
Tests pass for the env scrub, clean login spawns, clean reopen and the add-account path
9 + 3
Exact variable names and prefixes stripped at startup
8
Hex characters of SHA-256 that name each account's Keychain slot
2
Existing accounts overwritten on 14 September before the fix

What this post does not claim

  • Anything about how Anthropic's terms apply to Luminair or to you. Read them yourself.
  • That the Keychain naming rule is a documented interface. It is Claude Code's behaviour as we observed it; a future CLI could change it.
  • That every legacy token account has been migrated. The old flow still exists for them.

Sources

One account, one slot

Add each Claude account through Anthropic's own sign-in, and keep them apart.

Download Luminair →