diff --git a/.changeset/fix-token-storage-error-logging.md b/.changeset/fix-token-storage-error-logging.md new file mode 100644 index 0000000..dd1f5e6 --- /dev/null +++ b/.changeset/fix-token-storage-error-logging.md @@ -0,0 +1,13 @@ +--- +"@anthropic/gws": patch +--- + +Log token cache decryption/parse errors instead of silently swallowing + +Previously, `load_from_disk` used four nested `if let Ok` blocks that +silently returned an empty map on any failure. When the encryption key +changed or the cache was corrupted, tokens silently stopped loading and +users were forced to re-authenticate with no explanation. + +Now logs specific warnings to stderr for decryption failures, invalid +UTF-8, and JSON parse errors, with a hint to re-authenticate. diff --git a/src/token_storage.rs b/src/token_storage.rs index 4b24ec4..c54f5f7 100644 --- a/src/token_storage.rs +++ b/src/token_storage.rs @@ -35,16 +35,38 @@ impl EncryptedTokenStorage { } async fn load_from_disk(&self) -> HashMap { - if let Ok(data) = tokio::fs::read(&self.file_path).await { - if let Ok(decrypted) = crate::credential_store::decrypt(&data) { - if let Ok(json) = String::from_utf8(decrypted) { - if let Ok(map) = serde_json::from_str(&json) { - return map; - } - } + let data = match tokio::fs::read(&self.file_path).await { + Ok(d) => d, + Err(_) => return HashMap::new(), // File doesn't exist yet — normal on first run + }; + + let decrypted = match crate::credential_store::decrypt(&data) { + Ok(d) => d, + Err(e) => { + eprintln!( + "warning: failed to decrypt token cache ({}): {e:#}", + self.file_path.display() + ); + eprintln!("hint: you may need to re-authenticate with `gws auth login`"); + return HashMap::new(); + } + }; + + let json = match String::from_utf8(decrypted) { + Ok(j) => j, + Err(e) => { + eprintln!("warning: token cache contains invalid UTF-8: {e}"); + return HashMap::new(); + } + }; + + match serde_json::from_str(&json) { + Ok(map) => map, + Err(e) => { + eprintln!("warning: failed to parse token cache JSON: {e}"); + HashMap::new() } } - HashMap::new() } async fn save_to_disk(&self, map: &HashMap) -> anyhow::Result<()> {