Skip to content

fix(keyring): cache availability probe with OnceLock to prevent repeated keychain dialogs#2651

Open
M3gA-Mind wants to merge 3 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/keyring-probe-onclock
Open

fix(keyring): cache availability probe with OnceLock to prevent repeated keychain dialogs#2651
M3gA-Mind wants to merge 3 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/keyring-probe-onclock

Conversation

@M3gA-Mind
Copy link
Copy Markdown
Contributor

@M3gA-Mind M3gA-Mind commented May 25, 2026

Summary

  • AuthProfilesStore::new() is called on every CoreStateProvider poll (~every 2s), which triggered the OS keyring availability probe on each call
  • On macOS the probe touched the Security framework keychain, causing the system permission dialog to appear in a loop — trapping users on the loading screen
  • Probe result is now cached in a process-lifetime static AVAILABILITY_CACHE: OnceLock<bool>; the macOS keychain dialog appears at most once per app launch
  • Extracted probe logic into a private probe_availability() fn; is_available() calls get_or_init — semantics unchanged, cost reduced from O(poll rate) to O(1)

Problem

  • macOS users opened the prod app and saw a repeating keychain access prompt ("openhuman wants to use the login keychain") with no way to proceed
  • Root cause: is_available() was called on every snapshot poll; each call attempted a keychain write/read/delete cycle which triggered the system dialog
  • No guard existed to short-circuit after the first successful (or failed) probe

Solution

  • Added static AVAILABILITY_CACHE: OnceLock<bool> at module scope in src/openhuman/keyring/ops.rs
  • is_available() now delegates to get_or_init(probe_availability) — the probe runs exactly once per process, result is shared for all subsequent callers
  • Added unit tests in keyring/tests.rs covering the cached probe and verifying the underlying backend operations (file backend used in CI to avoid keychain permission requirements)

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) — added keyring/tests.rs with probe caching and backend operation tests
  • Diff coverage ≥ 80% — new probe_availability() and is_available() paths covered by keyring/tests.rs; coverage CI gate will validate
  • N/A: Coverage matrix updated — internal caching change; no new user-facing feature rows in docs/TEST-COVERAGE-MATRIX.md
  • N/A: All affected feature IDs from the matrix listed — no feature matrix rows affected
  • No new external network dependencies introduced — OnceLock is from Rust std; file backend used in tests
  • N/A: Manual smoke checklist updated — keyring caching fix only; no release-cut surface changes
  • N/A: Linked issue closed via Closes #NNN — no linked issue (user-reported prod repro, not tracked in GitHub issues)

Impact

  • Desktop (macOS) only — fixes repeated keychain permission dialog on every CoreStateProvider poll
  • No behavior change for non-macOS platforms or when OPENHUMAN_KEYRING_BACKEND=file is set
  • is_available() return value is identical; only the call frequency to the OS keychain is reduced

Related

  • Closes:
  • Follow-up PR(s)/TODOs:

AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/keyring-probe-onclock
  • Commit SHA: dbd77b9b228a568ccd6a75b8fd7f1103ac2f6bfa

Validation Run

  • pnpm --filter openhuman-app format:check — ran via pre-push hook; auto-fixed import line wrap in oauthAuthReadiness.ts, committed
  • pnpm typecheck — passed
  • Focused tests: pnpm test:rust (keyring unit tests)
  • Rust fmt/check (if changed): cargo check --manifest-path Cargo.toml — exit 0
  • N/A: Tauri fmt/check (if changed) — no Tauri shell Rust changes

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: OS keychain probe runs once per process instead of on every snapshot poll
  • User-visible effect: macOS keychain permission dialog appears at most once per app launch (was repeating every ~2s)

Parity Contract

  • Legacy behavior preserved: is_available() returns the same boolean; callers unchanged
  • Guard/fallback/dispatch parity checks: file backend fallback path unchanged; OnceLock result is consistent across all callers in the same process

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: this one
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • Bug Fixes

    • Fixed keyring probe failures on repeated launches; readiness flow now handles desktop-first startup more reliably.
    • Resolved a UI readiness path that could stall by ensuring result state is set consistently.
  • Performance

    • Reduced redundant keyring probes with process-wide caching.
  • Tests

    • Added regression and end-to-end tests to validate keyring probe idempotence and readiness behavior.

Review Change Stack

…ted keychain dialogs

AuthProfilesStore::new() is called on every CoreStateProvider poll (every 2s),
which triggered the keyring availability probe repeatedly. On macOS this caused
the system keychain access dialog to appear in a loop, blocking users on the
loading screen.

Probe result is now cached in a process-lifetime OnceLock<bool> so macOS only
shows the permission dialog once per app launch.
@M3gA-Mind M3gA-Mind requested a review from a team May 25, 2026 21:56
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 25, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9ce77c03-65e1-4eeb-92c6-4f78663509cf

📥 Commits

Reviewing files that changed from the base of the PR and between 91f9019 and 3c39f31.

📒 Files selected for processing (1)
  • app/src/components/oauth/__tests__/oauthAuthReadiness.test.ts

📝 Walkthrough

Walkthrough

Simplify BootCheckGate's success handling, add a Tauri-specific core-mode fallback in OAuth readiness, move auth-profile loading into a blocking task, and make the keyring availability probe idempotent by caching results and deleting stale probe keys; add regression tests for probe idempotence.

Changes

OAuth Initialization Flow

Layer / File(s) Summary
BootCheckGate success path cleanup
app/src/components/BootCheckGate/BootCheckGate.tsx
Unconditionally set phase to 'result' and store checkResult on successful run, removing a redundant branch.
Tauri core mode readiness fallback
app/src/components/oauth/oauthAuthReadiness.ts
Expand configPersistence imports and add a Tauri-only early fallback that stores 'local' as core mode and proceeds when core mode is unset.
Async auth-profile snapshot loading
src/openhuman/app_state/ops.rs
Load auth session profile inside tokio::task::spawn_blocking, cloning config into the task and mapping panics into formatted errors.

Keyring Availability Probe Idempotence

Layer / File(s) Summary
OnceLock caching and stale-key cleanup
src/openhuman/keyring/ops.rs
Introduce a process-wide OnceLock<bool> cache for the availability probe and delete leftover probe keys before writing to avoid "key already exists" failures on subsequent runs.
Wire keyring tests module
src/openhuman/keyring/mod.rs
Conditionally compile mod tests; under #[cfg(test)].
Probe idempotence regression test suite
src/openhuman/keyring/tests.rs
Add StrictSetBackend to emulate OS "item already exists" errors, old_probe/new_probe helpers, and tests validating the new probe is idempotent and that is_available() returns true across repeated calls (including a gated OS backend E2E test).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

"🐰 I nudged checks to settle, and probes to stay true,
Tauri wakes quick with local mode anew.
Spawned a task so profiles sleep no more,
Stale keys removed — probes pass sure.
Hooray for tests that hop and prove!"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: caching the keyring availability probe with OnceLock to eliminate repeated keychain dialogs. This directly matches the primary change and the core problem being solved.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. bug labels May 25, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/keyring/tests.rs`:
- Around line 514-532: The test is meant to validate repeated calls but only
invokes super::ops::is_available() once; update the test function
is_available_returns_true_on_repeated_calls_os_backend to call
super::ops::is_available() twice (or more) after seeding the OS keyring via
backend::OsBackend and probe_key, asserting true on each call to exercise the
cached/second-call path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 27ec4a5e-9f0c-4d98-a329-16749c6f2d4a

📥 Commits

Reviewing files that changed from the base of the PR and between e05cab9 and dbd77b9.

📒 Files selected for processing (6)
  • app/src/components/BootCheckGate/BootCheckGate.tsx
  • app/src/components/oauth/oauthAuthReadiness.ts
  • src/openhuman/app_state/ops.rs
  • src/openhuman/keyring/mod.rs
  • src/openhuman/keyring/ops.rs
  • src/openhuman/keyring/tests.rs

Comment thread src/openhuman/keyring/tests.rs
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 25, 2026
CodeRabbit flagged that the repeated-calls test only invoked is_available()
once, leaving the OnceLock cache path untested. Add a second assertion.
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 25, 2026
…_mode_unset scenario

storeCoreMode was missing from the vi.mock factory, causing vitest to throw
on the Tauri branch. The core_mode_unset test also needed isTauri=false:
in Tauri mode the function defaults to 'local' and never returns that reason.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant