Prevent Keychain prompts during automatic refreshes; show an actionable error card - #24
Merged
Conversation
…ror card Background and launch refreshes read classic login-keychain items (Claude Code credentials, Chromium Safe Storage keys) guarded only by LAContext.interactionNotAllowed, which the classic ACL confirmation dialog ignores - so macOS could pop password prompts during automatic refreshes and block them behind the dialog. A process-wide KeychainUISuppression gate built on SecKeychainSetUserInteractionAllowed now keeps every non-interactive Security.framework operation prompt-free (a protected item fails fast into the friendly Permission Required state), while interactive reads from a manual Refresh hold the gate so a racing background read cannot disable UI beneath an open approval dialog. Providers whose refresh failed with no displayable data now replace their empty "No data" rows with an actionable error card (reason plus a Refresh button - the one action allowed to show a Keychain prompt), keeping quick links behind the caret, mirrored in the drag preview and share export. Claude-Session: https://claude.ai/code/session_01Ur7PxLJ1avetsk3JegAWVY
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a0953366bd
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ngage SecKeychainSetUserInteractionAllowed's return status was ignored: had the disable call ever failed, the suppression scope would have run its protected Security.framework call with interaction still enabled - able to show the exact background dialog the gate exists to prevent. The scope now reports whether suppression actually engaged; when it did not (logged loudly), non-interactive bodies skip the prompt-capable call and report their unavailable outcome instead, and a failed restore is retried before the next interactive operation so approval prompts cannot stay broken. Claude-Session: https://claude.ai/code/session_01Ur7PxLJ1avetsk3JegAWVY
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.
TL;DR
Automatic refreshes can no longer trigger macOS Keychain password dialogs — a protected item fails fast into the friendly "Permission Required" state — and a provider with nothing to show now gets an actionable error card instead of a wall of empty "No data" rows.
What was happening
LAContext.interactionNotAllowed. The classic ACL confirmation dialog ignores that flag, so macOS could unexpectedly show "Runway wants to use your confidential information…" during an automatic refresh — repeatedly on first run — and the refresh sat blocked behind the dialog (~15 minutes in one report).What this changes
KeychainUISuppressiongate built onSecKeychainSetUserInteractionAllowed— verified empirically as the only switch the classic keychain ACL path honors. Every non-interactive Security.framework read/probe/update runs suppressed and returns promptly (protected item →errSecAuthFailed→ Permission Required, no fallback to a possibly stale credentials file); interactive reads from a manual Refresh hold the gate for their whole duration so a racing background read can't disable UI beneath an open approval dialog. Claude Desktop and Sakana Safe Storage readers route through the same gate.Heads-up
SecKeychainSetUserInteractionAllowedis deprecated, but it is the only mechanism that actually suppresses classic keychain UI (kSecUseAuthenticationUIandLAContextboth fail); the rationale is documented at the gate.Tests
CLAUDE_CONFIG_DIR-scoped Claude refresh policy, empty-state detection (placeholder-only and hidden-metric cases), and error-copy formatting. Full suite: 1465 tests passing.https://claude.ai/code/session_01Ur7PxLJ1avetsk3JegAWVY