Skip to content

perf(auth): hydrate secure tokens once per process - #3656

Draft
cpacker wants to merge 2 commits into
fix/secret-backends-nodefrom
fix/secure-token-cache
Draft

perf(auth): hydrate secure tokens once per process#3656
cpacker wants to merge 2 commits into
fix/secret-backends-nodefrom
fix/secure-token-cache

Conversation

@cpacker

@cpacker cpacker commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Stacked on #3655 — review that first. This PR is 2 of 3 splitting #3423.

Contains two independent commits; the second can be dropped without affecting the first.

1. perf(auth): hydrate secure tokens once per process

Every settings read that needed Cloud credentials could reach the OS credential store again. On macOS each read is a Keychain access, and with the Node backends from #3655 each one can be a subprocess — so repeated reads are both slow and a source of approval prompts.

Token reads now go through SecureTokenCache: one single-flight hydration per process, with one later retry so a transient failure can still recover before the cache stops trying. LETTA_API_KEY short-circuits secure storage entirely, since an explicit key means there is nothing to look up.

getSecureTokensWithStatus distinguishes "no credential stored" from "the read failed", which is what lets the cache decide between caching an empty result and retrying.

Note a precedence change: getSettingsWithSecureTokens now resolves process.env.LETTA_API_KEY ?? secureTokens.apiKey ?? settings.env.LETTA_API_KEY. Previously the Keychain value won and process.env was not consulted in that function. This matches what src/index.ts:860 already did, so it is a consistency fix — but anyone with a stale LETTA_API_KEY exported in their shell and an active Cloud login will now use the env var. Worth a changelog line.

The cache does not poll for credential changes made by another process. Letta's own writes update it directly, and reload or restart rehydrates.

2. fix(auth): keep file fallback when secure storage is session-scoped

This one is not from #3423 — it fixes a regression that #3655 would otherwise introduce.

Token migration deletes the settings-file copy once tokens are written to the OS store. That is safe on macOS and Windows, where the store is process-independent. It is not safe on Linux: the Secret Service is reached over the session bus, so a credential written from a desktop session is unreadable from any process without DBUS_SESSION_BUS_ADDRESS.

Before the Node backends existed this never fired — Linux had no usable backend, so tokens simply stayed in the file and worked everywhere. Making Linux keychain-capable activates the destructive half of a migration that was previously unreachable, which would strand ssh, systemd, and cron listeners with no credential source at all. That is the same lost-auth symptom this whole effort exists to fix, relocated to Linux.

Adds SecretBackend.isDurable and skips the delete when it is false.

Verified as a real regression test: with the guard disabled, refreshToken becomes undefined in the persisted settings file.

Validation

  • bun run check — 12/12 pass
  • src/settings-manager-secret-durability.test.ts — 3 pass (new)
  • src/utils/secure-token-cache.test.ts + settings-manager-auth-cache.test.ts — 23 pass
  • src/settings-manager.test.ts — 70 pass
  • Full unit suite — 5,935 pass, 0 fail

🤖 Generated with Claude Code

cpacker and others added 2 commits August 3, 2026 15:37
Every settings read that needed Cloud credentials could reach the OS
credential store again. On macOS each read is a Keychain access, and with the
Node backends each one can be a subprocess, so repeated reads are both slow
and a source of approval prompts.

Route token reads through SecureTokenCache: one single-flight hydration per
process, with one later retry so a transient failure can still recover before
the cache stops trying. LETTA_API_KEY now short-circuits secure storage
entirely, since an explicit key means there is nothing to look up.

getSecureTokensWithStatus distinguishes "no credential stored" from "the read
failed", which is what lets the cache decide between caching an empty result
and retrying.

The cache does not poll for credential changes made by another process.
Letta's own writes update it directly, and reload or restart rehydrates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Token migration deletes the settings-file copy once tokens are written to the
OS store. That is safe on macOS and Windows, where the store is
process-independent, but not on Linux: the Secret Service is reached over the
session bus, so a credential written from a desktop session is unreadable from
any process without DBUS_SESSION_BUS_ADDRESS.

Before the Node backends existed this never fired, because Linux had no usable
backend and tokens simply stayed in the file. Making Linux keychain-capable
activates the destructive half of a migration that was previously unreachable,
which would strand ssh, systemd, and cron listeners with no credential source
at all - the same lost-auth symptom this work exists to fix.

Add SecretBackend.isDurable and skip the delete when it is false, so the file
copy remains the fallback for processes that cannot reach the session. The
regression test asserts the file copy survives a session-scoped migration and
is still removed for a durable one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@cpacker
cpacker marked this pull request as draft August 5, 2026 06:15
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