perf(auth): hydrate secure tokens once per process - #3656
Draft
cpacker wants to merge 2 commits into
Draft
Conversation
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>
This was referenced Aug 3, 2026
just-cameron
approved these changes
Aug 4, 2026
cpacker
marked this pull request as draft
August 5, 2026 06:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 processEvery 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_KEYshort-circuits secure storage entirely, since an explicit key means there is nothing to look up.getSecureTokensWithStatusdistinguishes "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:
getSettingsWithSecureTokensnow resolvesprocess.env.LETTA_API_KEY ?? secureTokens.apiKey ?? settings.env.LETTA_API_KEY. Previously the Keychain value won andprocess.envwas not consulted in that function. This matches whatsrc/index.ts:860already did, so it is a consistency fix — but anyone with a staleLETTA_API_KEYexported 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-scopedThis 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.isDurableand skips the delete when it is false.Verified as a real regression test: with the guard disabled,
refreshTokenbecomesundefinedin the persisted settings file.Validation
bun run check— 12/12 passsrc/settings-manager-secret-durability.test.ts— 3 pass (new)src/utils/secure-token-cache.test.ts+settings-manager-auth-cache.test.ts— 23 passsrc/settings-manager.test.ts— 70 pass🤖 Generated with Claude Code