Repository navigation
fix(host): answer Settings diagnostics without waiting on a keychain prompt - #367
Merged
Merged
Conversation
…prompt Diagnostics now reports ACE_SECRET from the host's last finished read, marked "Ace Helper is waiting for keychain access" while a read is under way, and Settings polls until that read lands. Keychain reads of the same name share one in-flight read, so Settings, directory syncs, and moves raise at most one prompt. Refresh skips waiting for a directory sync that waits on the keychain. Fixes #360 Why: apps/host/src/keys.ts#function latest | Settings must never await a keychain read; after a prompted read only a fresh (Refresh) request reads again, or each poll raises another prompt Why: apps/host/src/keys.ts#function keychain | concurrent reads of one name share a single in-flight Bun.secrets.get so a person sees at most one prompt
This was referenced Oct 15, 2026
iamnbutler
added a commit
that referenced
this pull request
Oct 15, 2026
…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
iamnbutler
added a commit
that referenced
this pull request
Oct 15, 2026
…ompt (#370) * fix(host): answer Settings providers without waiting on a keychain prompt 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 * lane: sync providers-keychain memory
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 #360 (found fixing two-person study scenario 18, #356 / #218).
Settings → This Mac read
ACE_SECRETfrom the Keychain on every diagnostics request with no time limit. When macOS gated that read behind a prompt, the whole diagnostics answer waited for it. Settings then showed stale or empty state, such as "Waiting for the first sync…" even after the host had recorded a sync error.apps/host/src/keys.tslatest,apps/host/src/diagnostics.ts). A read that doesn't finish within 50 ms answers with the last finished read, markedwaiting: "Ace Helper is waiting for keychain access". If there was no earlier read, it answerssource: "unavailable",stored: null. The read keeps running and the next poll picks up the result. If a read had to wait on a prompt, later polls reuse its result, and only Refresh status reads again, so polling doesn't keep raising prompts. Saving or removing the secret clears the cached result.keychain): concurrent reads of the same name share one in-flightBun.secrets.get. Settings, the directory heartbeat sync, and a move's credential read (fix(host): read move credentials before fencing the channel, and report keychain waits #353) therefore raise at most one prompt between them. The move path keeps fix(host): read move credentials before fencing the channel, and report keychain waits #353's boundedcredentials()helper and now takes its waiting text from the sharedKEYCHAIN_WAITING.diagnostics sync: trueno longer waits up to 25 s on a directory sync while the secret read is waiting. The sync result shows up on a later poll.CredentialStatusgains an optionalwaiting, so no protocol bump: an older app ignores it.docs/architecture.mddescribes the behavior.Not changed: the Providers section's
settingsop still reads provider keys, and pi-ai's model availability, without a time limit. In the test below it read about 40 provider env names from the Keychain while a read was delayed. That is the same class of problem for Settings → Providers and should be a follow-up.Verification (isolated
ace serveon port 4379 withACE_HOME,ACE_CONFIG_HOME, andACE_KEYCHAIN_SERVICE=ace-test-360under /tmp, Tailscale shimmed out, andACE_DIRECTORY=http://127.0.0.1:9). A--preloadreplacedBun.secretswith an in-memory fake whosegetwaits 8–9 s. No real Keychain item, launchd job, or Canary Helper was touched.bun typesandbun run cipass. Theapps/appvite build passes.diagnosticsanswered in 51–73 ms. The first answer wasunavailable+ waiting when nothing had been read yet, andkeychain+ waiting after an earlier read.sync: truealso answered in 73 ms. Once the read finished, the next poll returnedkeychainwithout waiting, and the directory row had the sync error. The fake logged a singleACE_SECRETread across the startup sync and all polls.ACE_SECRETread happened.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.