Repository navigation
fix(host): answer Settings providers without waiting on a keychain prompt - #370
Merged
Merged
Conversation
…ompt Settings → Providers read each provider key and pi-ai's model availability (about 40 env names) through the keychain with no time limit. Settings now reads keychain names one at a time through #367's shared reads, answers with the last finished read marked waiting while a read waits on a prompt, and polls until it lands. Only Refresh reads a prompted name again. Workers keep `models()`, which waits for each read. Fixes #368 Why: apps/host/src/keys.ts#function look | Settings reads keychain names one at a time and never awaits a read held by a prompt; agents' models() still reads each key on every request
This was referenced Oct 15, 2026
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.
Fixes #368 (follow-up to #367, part of #5).
Before this change, Settings → Providers read each provider key's status with
credential(), and read pi-ai's model availability (about 40 env names) throughkey(). Neither read had a time limit, so a key gated by a macOS prompt held the wholesettingsanswer until someone answered the prompt.apps/host/src/keys.ts):latest()and the shared in-flightkeychain()reads now sit on one Settings read path,peek().waiting. A later request picks up the result.freshrequest (Refresh) reads that name again, so polling doesn't keep raising prompts.credential()is gone.latest()covers both diagnostics and providers.look()), so it raises at most one prompt at a time instead of up to one for each of the ~40 names. A missing name reads in about 0.5 ms, so reading them in sequence costs little. While a prompt holds the queue, rows whose own read already finished keep their state. Only the name being prompted for, or a name that has never been read, shows waiting.Bun.secretshas onlyget,setanddelete, with no way to check whether an item exists without reading it.security find-generic-passwordwithout-wwould work, but it is macOS-only and would mean starting a process for each name. Reading one name at a time is portable and solves the burst problem. This is recorded as a lane note onfunction look.settled()): this is pi-ai's catalog with a lookup that never waits on a prompt. While a name's read waits, it uses that name's last finished value, andSettings.waitingreports that the list may be incomplete. Values never leave Ace Helper.modelsop,defaultModel,setModeland the CLI still usemodels()/key(), which wait for each read. That means no change to which models a running channel can use, or to when it learns about a newly saved key.{ op: "settings", fresh?: boolean }andSettings.waiting?are optional fields, so there is no protocol bump.apps/app/src/settings.tsx): Provider rows show "Ace Helper is waiting for keychain access…". The Default model row shows it while availability waits. General and Providers re-poll every 3 s while anything is waiting, and Refresh sendsfresh.docs/architecture.mddescribes the behavior.Verification: an isolated
ace serveon port 4381 withACE_HOME/ACE_CONFIG_HOMEunder /tmp,ACE_KEYCHAIN_SERVICE=ace-test-368,--no-env-file,ACE_DIRECTORY=http://127.0.0.1:9, and Tailscale stubbed out. A--preloadreplacedBun.secretswith an in-memory fake that logs every read.ANTHROPIC_API_KEYandGEMINI_API_KEYwere stored and simulated prompts of 8 s and 6 s. No real Keychain item was touched.bun typesandbun run cipass, and theapps/appvite build passes.settingsanswered in 1–64 ms while prompts were pending. The full non-waiting read of ~40 names took 130–245 ms.unavailable+ waiting, models empty,waiting.anthropic:keychain,openai:missing, 16 Anthropic models, stillwaitingfor Gemini.fresh: trueread both prompted names again, again one at a time. Meanwhile, polls showedanthropic:keychain+ waiting and kept the last model list (82 models).key-store OPENAI_API_KEY: the nextsettingsshowedopenai:keychainand OpenAI models immediately.diagnosticswhile the queue was held answered in 18 ms with the team secret marked waiting.Built in Ace
No — built with Claude Code outside Ace by an overnight issue watcher, because this Mac's Ace Canary is a participant in the running two-person study (#218); dogfooding gap tracked in #5.