Skip to content

fix(config): read each saved secret once per command - #117

Merged
thebrandonlucas merged 1 commit into
masterfrom
brandon/dev-2952-cache-keychain-secret
Oct 29, 2026
Merged

thebrandonlucas merged 1 commit into
masterfrom
brandon/dev-2952-cache-keychain-secret

Conversation

@thebrandonlucas

Copy link
Copy Markdown
Contributor

Problem

On macOS, one command could ask for the keychain password several times. Each credential lookup read the saved secret from the store again: a picker, the wallet currency lookup, the wallet shortlist, and then the API call. A user who selects "Allow" instead of "Always Allow" gets one prompt for each read.

Change

Settings now keeps each secret in memory after the first read or write. The process reads the store at most once for each secret, so a command shows at most one prompt.

Event Cache
First read Reads the store, then keeps the secret
Later read Uses the kept secret
Write succeeds Keeps the written secret
Write fails, or delete Forgets the secret; the next read uses the store
Credential lock taken Forgets all secrets

The lock rule keeps refreshes correct. A refresh reads the login again under the lock. That read goes to the store, so it sees a token that another process rotated while this one waited. A refresh therefore reads the store one more time, as before.

Limit: if another process logs out, a long-running command such as payments list --watch sees that only at its next refresh or 401 retry, not at its next read.

This change does not fix the prompts after each upgrade. That needs Developer ID signing of the macOS release binaries, which #116 tracks.

Tests

  • a_secret_is_read_once_until_the_lock_is_taken: a secret changed by another Settings is not seen until the lock is taken.
  • a_deleted_or_failed_write_reads_the_store_again: after a write that fails after its rename, and after a delete, the next read goes to the store.
  • The integration tests now read saved credentials through a new Settings, because a long-lived one keeps what it read before the CLI subprocess ran.
  • The cache is store-agnostic, so the tests use the file store. The tests cannot reach an OS keychain.

Checks

  • cargo fmt --check, cargo clippy --locked --all-targets -- -D warnings, cargo machete --with-metadata, cargo test --locked, python3 scripts/check-coverage.py: all pass.
  • CRAP report: 0 violations. The highest score is 18.10 (src/api.rs wait_for_payment, which this change does not touch).
  • Self-review with voltage-pr-review: one cleanup was applied (a Secrets type alias).
  • Not run: a manual check against the macOS Keychain.

Refs #116, DEV-2952

The macOS Keychain can ask for approval on every read, and one command
read a saved login once for each credential lookup. Settings now keeps a
secret in memory after the first read or write. Taking the credential
lock forgets it, so a refresh still sees a token another process saved.

Refs #116, DEV-2952

@tee8z tee8z left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The design holds up. A refresh still reads the store again under the lock. A failed write or a delete clears the cached secret. holds_secret bypasses the cache on purpose, and Settings has no Debug, so the cached secrets cannot be printed.

Non-blocking: lock() clears every cached secret, and create_profile, add_account, and profiles delete take the lock as well as a refresh does. So a command that reads a secret and then saves configuration can still prompt twice on macOS. To keep the refresh guarantee and avoid that, you could clear only the secret that the refresh reads again. Clearing everything is the safer default, and the description already names the limit, so this is fine to merge as it is.

@thebrandonlucas
thebrandonlucas marked this pull request as ready for review October 29, 2026 12:26
@thebrandonlucas
thebrandonlucas merged commit d38a464 into master Oct 29, 2026
7 checks passed
@thebrandonlucas
thebrandonlucas deleted the brandon/dev-2952-cache-keychain-secret branch October 29, 2026 14:31
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.

2 participants