Surface the FJ_TOKEN fallback when the OS keychain is unavailable #96

Closed
opened 2026-06-10 21:58:33 +00:00 by stephen · 0 comments
Owner

What

In src/auth/mod.rs::load_token, treat keychain failures that mean "no usable secret is reachable here" (service unavailable, no Secret Service / D-Bus, locked keychain, access denied, and an Entry::new failure at path (a)) the same way keyring::Error::NoEntry is treated today: return Ok(None) instead of propagating the raw error. That lets the existing actionable message in src/client/resolve.rs:27-36 ("no token stored for host '...'. Run fj auth login --host ... or set FJ_TOKEN for this process.") be what the user sees on headless/CI machines. Match the relevant keyring::Error variants explicitly so a genuinely unexpected error still propagates, and log the underlying platform cause under --debug before returning Ok(None) so diagnosis is not lost.

Why

rasterstate/fj#93: when FJ_TOKEN is unset and the OS keychain is unreachable (headless container with no Secret Service, a Login keychain locked over SSH, or a copied hosts.toml on a machine that never ran fj auth login), every authenticated command dies with a raw keychain error and never mentions the FJ_TOKEN lever, even though that lever is documented in CLAUDE.md and used by this repo's own CI. The helpful hint only fires on keyring::Error::NoEntry; any other keychain error propagates through ?. CI and remote dev are exactly where teams evaluate a forge CLI and exactly where the keychain is absent, so the current behavior reads as "fj is broken in containers" and is a silent adoption cliff for the headless buyer.

Acceptance

  • In a headless environment with no keychain service and FJ_TOKEN unset, an authenticated command prints the actionable resolve.rs message naming FJ_TOKEN, not a raw keychain error.
  • Setting FJ_TOKEN makes the same command succeed without touching the keychain (unchanged behavior).
  • The underlying keychain cause is still visible under --debug.
  • A genuinely unexpected/unmatched keychain error still propagates rather than being silently swallowed.
  • A unit test covers the keychain-unavailable -> Ok(None) mapping (e.g. via keyring's mock store or an injected error).
  • cargo fmt --check, cargo clippy --all-targets --all-features -- -D warnings, and cargo test --all pass.

Dependencies

none

Out of scope

  • Changing how tokens are stored, or the keychain "Always Allow" / code-signing behavior.
  • Adding new auth backends.
  • A standalone troubleshooting doc (a one-line cross-link from the error text is acceptable but not required by this item).

Size

S

## What In `src/auth/mod.rs::load_token`, treat keychain failures that mean "no usable secret is reachable here" (service unavailable, no Secret Service / D-Bus, locked keychain, access denied, and an `Entry::new` failure at path (a)) the same way `keyring::Error::NoEntry` is treated today: return `Ok(None)` instead of propagating the raw error. That lets the existing actionable message in `src/client/resolve.rs:27-36` ("no token stored for host '...'. Run `fj auth login --host ...` or set FJ_TOKEN for this process.") be what the user sees on headless/CI machines. Match the relevant `keyring::Error` variants explicitly so a genuinely unexpected error still propagates, and log the underlying platform cause under `--debug` before returning `Ok(None)` so diagnosis is not lost. ## Why rasterstate/fj#93: when `FJ_TOKEN` is unset and the OS keychain is unreachable (headless container with no Secret Service, a Login keychain locked over SSH, or a copied `hosts.toml` on a machine that never ran `fj auth login`), every authenticated command dies with a raw keychain error and never mentions the `FJ_TOKEN` lever, even though that lever is documented in `CLAUDE.md` and used by this repo's own CI. The helpful hint only fires on `keyring::Error::NoEntry`; any other keychain error propagates through `?`. CI and remote dev are exactly where teams evaluate a forge CLI and exactly where the keychain is absent, so the current behavior reads as "fj is broken in containers" and is a silent adoption cliff for the headless buyer. ## Acceptance - [ ] In a headless environment with no keychain service and `FJ_TOKEN` unset, an authenticated command prints the actionable `resolve.rs` message naming `FJ_TOKEN`, not a raw keychain error. - [ ] Setting `FJ_TOKEN` makes the same command succeed without touching the keychain (unchanged behavior). - [ ] The underlying keychain cause is still visible under `--debug`. - [ ] A genuinely unexpected/unmatched keychain error still propagates rather than being silently swallowed. - [ ] A unit test covers the keychain-unavailable -> `Ok(None)` mapping (e.g. via keyring's mock store or an injected error). - [ ] `cargo fmt --check`, `cargo clippy --all-targets --all-features -- -D warnings`, and `cargo test --all` pass. ## Dependencies none ## Out of scope - Changing how tokens are stored, or the keychain "Always Allow" / code-signing behavior. - Adding new auth backends. - A standalone troubleshooting doc (a one-line cross-link from the error text is acceptable but not required by this item). ## Size S
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rasterstate/fj#96
No description provided.