Skip to content

Ask for the keychain once, not on every focus - #97

Merged
AndersRobstad merged 1 commit into
mainfrom
keychain-prompt-loop-broken-app
Sep 7, 2026
Merged

Ask for the keychain once, not on every focus#97
AndersRobstad merged 1 commit into
mainfrom
keychain-prompt-loop-broken-app

Conversation

@AndersRobstad

Copy link
Copy Markdown
Collaborator

Fixes the prompt loop that made the app unusable after updating to 0.16.0.

What happens

Fiber is signed ad-hoc ("signingIdentity": "-"), so a new build is a new code identity and every dev.fiber.app keychain item's access list still names the old one. For one launch after each update, reading a credential is a macOS authorization dialog. That is the intended cost, and the code says so in several places — it should be one dialog per credential.

It was not, for two reasons.

A failed loader run is retried on every focus. refresh() only writes a cache on success, so loadedAt never moves, the collection stays stale for ever, and refreshStale() — wired to <svelte:window onfocus> — runs it again. The authorization dialog is itself a focus event: it takes focus, and dismissing it hands focus straight back to the window that starts the next run, which reads the keychain, which raises the next dialog. Allow, refocus, prompt, forever, for as long as the API keeps refusing the credential.

A failure now counts as an attempt, so the TTL means the same thing for both outcomes: don't ask this API again for another ttlSeconds. Refresh and Sign in again call refresh() directly and are untouched — those are the user asking.

Saving a section reached for the keychain. sync_section_sharing landed with the credentials file in 0.15.0 and runs on every save_section. Reconciling that file means unsealing it, and unsealing it reads the sealing key from the keychain — so renaming a request or typing in a URL could raise a password prompt. A prompt has to belong to something the user did that needs it. It is now reconciled only when sharing itself moves: the switch, or which credential the collection uses, which is what it was always for.

Tests

  • a loader that failed is not re-run on every focus — three focus events after a 403, one run.
  • an_ordinary_edit_is_not_a_change_to_sharing — a rename is not a sharing change; toggling the switch or swapping the reference is.

Local: 135 Rust tests, cargo check --all-targets with the gui feature, cargo fmt --check, clippy -D warnings, pnpm check, and all 339 e2e tests.

Not fixed here

The prompt itself. The real answer is a stable Developer ID signature, which release.yml already has scaffolded and commented out pending the APPLE_* secrets — with it, the access lists survive an update and this whole class of prompt goes away. Happy to do that next.

Fiber is signed ad-hoc, so a new build is a new code identity and every
keychain item's access list still names the old one. For one launch after
each update, reading a credential is a macOS authorization dialog. That
is the intended cost; it should be one dialog per credential.

It was not. A failed loader run writes no cache, so `loadedAt` never
moved, the collection stayed stale, and the focus trigger re-ran it — and
the authorization dialog is itself a focus event, handing the window back
the moment it is dismissed. Allow, refocus, re-run, prompt, for as long as
the API keeps refusing the credential. A failure now counts as an attempt,
so the TTL means the same thing for both outcomes: don't ask this API
again for another `ttlSeconds`. Refresh and Sign in again call `refresh`
directly and are untouched — those are the user asking.

The second prompt arrived with the credentials file in 0.15.0. Saving a
section reconciled that file, reconciling it means unsealing it, and
unsealing it reads the sealing key from the keychain. Fiber saves a
section on any edit, debounced, so renaming a request or typing in a URL
came through here — and a prompt has to belong to something the user did
that needs it. It is now reconciled only when sharing itself moves: the
switch, or which credential the collection uses, which is what it was
always for.
@AndersRobstad
AndersRobstad merged commit a2b9a8c into main Sep 7, 2026
4 checks passed
@AndersRobstad
AndersRobstad deleted the keychain-prompt-loop-broken-app branch September 7, 2026 06:39
@github-actions github-actions Bot mentioned this pull request Sep 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant