Project Context
Goose is an open-source AI agent that supports multiple LLM providers including GitHub Copilot via OAuth authentication. The application uses system keyring storage for API tokens, with automatic fallback to file-based storage when keyring is unavailable.
The Problem
A WSL user reported that GitHub Copilot OAuth configuration showed an error message: Failed to save token: Secret stored using file-based fallback, even though:
- The token was successfully saved to
~/.config/goose/secrets.yaml - Goose ran correctly after the "error"
- The fix from PR #7177 (v1.24.0) should have prevented this error
Expected Behavior:
- Keyring unavailable → Fallback to file storage → Return success
- No error shown to user
Actual Behavior:
- Keyring unavailable → Fallback to file storage → Error still shown
Root Cause Analysis
The Fix That Should Work
PR #7177 added graceful fallback handling in base.rs:
match self.handle_keyring_operation(
|entry| entry.set_password(&json_value),
service,
Some(&values),
) {
Ok(_) => {}
Err(ConfigError::FallbackToFileStorage) => {} // Treat as success
Err(e) => return Err(e),
}This should catch FallbackToFileStorage and return Ok(()), preventing the error from propagating.
Architectural Constraints Identified
-
Config Singleton Pattern: The
Configobject is created once at startup withSecretStorage::KeyringorSecretStorage::Filebased onGOOSE_DISABLE_KEYRINGenv var. When fallback sets this env var at runtime, the Config's internal state doesn't change. -
Dual Error Handling Paths:
configure.rsusestry_store_secret()which correctly handlesFallbackToFileStoragegithubcopilot.rscallsset_secret()directly with.map_err(), converting all errors toProviderError::ExecutionError
-
WSL Environment Parity: WSL handles inter-process communication (IPC) for keyrings differently than native Linux. If dbus or gnome-keyring services are present but unreachable (Windows side running but WSL can't connect), the error returned might not match expected patterns. Could be
IOErrororConnectionRefusedthat bypassesis_keyring_availability_error()detection.
The Error Flow
handle_keyring_fallback_error() writes to file successfully
↓
Returns Err(ConfigError::FallbackToFileStorage)
↓
set_secret() match should catch this and return Ok(())
↓
But error still propagates to githubcopilot.rs:566
↓
Converted to ProviderError::ExecutionError
↓
Displayed to userInvestigation Status
Unable to reproduce locally. The code inspection shows the fix should work, but user reports (v1.25.0) show the error still appears.
Possible Causes:
- WSL-specific error path not covered by pattern matching
- Different error type being returned that looks like
FallbackToFileStorage - Compilation/deployment mismatch
- Error being logged before the match can catch it
Next Steps
- Debug Logging: User to run
RUST_LOG=debug goose configureto capture actual error flow - Expand Error Patterns: Add WSL-specific error patterns to
is_keyring_availability_error() - Per-Provider Handling: Consider if
githubcopilot.rsneeds its ownFallbackToFileStoragehandling instead of relying onmap_err()
Why This Matters
The graceful fallback should work universally across all providers without per-provider changes. Finding why it doesn't would:
- Improve UX for all WSL users
- Prevent duplicate error handling code
- Ensure consistent behavior across different environments
Lessons Learned
-
Environment Matters: WSL is not quite Linux. IPC, file systems, and process isolation behave differently enough to break assumptions.
-
Leaky Abstractions: The
ConfigError::FallbackToFileStorageabstraction leaks when different parts of the codebase handle errors differently. -
Testing Gap: The fallback path works in theory but may have untested edge cases in cross-platform scenarios.
-
Error Display ≠ Failure: The operation actually succeeded (token saved), but the error message suggests otherwise. This is a UX issue masking a success.