{"record":{"id":"2345e789aa44a1fd","repo":"googleworkspace/cli","slug":"key-file-exists-but-is-corrupt","errorCode":null,"errorMessage":"key file exists but is corrupt","messagePattern":"key file exists but is corrupt","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/google-workspace-cli/src/credential_store.rs","lineNumber":337,"sourceCode":"                    );\n                }\n            }\n        }\n    }\n\n    // --- 2. File fallback ------------------------------------------------\n    if let Some(key) = read_key_file(key_file) {\n        return Ok(key);\n    }\n\n    // --- 3. Generate new key, save to file (race-safe) -------------------\n    let key = generate_random_key();\n    let b64_key = STANDARD.encode(key);\n    match save_key_file_exclusive(key_file, &b64_key) {\n        Ok(()) => Ok(key),\n        Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {\n            // Another process created the file first — use their key.\n            read_key_file(key_file).ok_or_else(|| anyhow::anyhow!(\"key file exists but is corrupt\"))\n        }\n        Err(e) => Err(e.into()),\n    }\n}\n\n/// Returns the encryption key, generating and persisting one if it doesn't exist.\n///\n/// The key is cached in-process via `OnceLock` so it is only read from disk once.\n/// Backend selection is controlled by `GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND`.\nfn get_or_create_key() -> anyhow::Result<[u8; 32]> {\n    static KEY: OnceLock<[u8; 32]> = OnceLock::new();\n\n    if let Some(key) = KEY.get() {\n        return Ok(*key);\n    }\n\n    #[cfg(not(test))]\n    let backend = KeyringBackend::from_env();","sourceCodeStart":319,"sourceCodeEnd":355,"githubUrl":"https://github.com/googleworkspace/cli/blob/a3768d0e82ad83cca2da97724e46bea4ff0e6dbd/crates/google-workspace-cli/src/credential_store.rs#L319-L355","documentation":"File-backend key creation is race-safe: it writes the base64 key with an exclusive create, and if another process won the race (AlreadyExists), it re-reads the winner's file. read_key_file returns None when the content cannot be decoded to exactly 32 bytes (unreadable, invalid base64, or wrong decoded length), so this error means the concurrently-created key file exists but does not parse — a genuinely corrupt key file, not just a lost race.","triggerScenarios":"Two gws processes starting simultaneously on a first run where the winner's write landed truncated (0-byte) or the file was created empty by an external tool; disk-full causing a partial key file; someone hand-edited the key file mid-init.","commonSituations":"Parallel first-run invocations (CI matrix sharing a HOME, two terminals); sync clients (Dropbox) touching the key file during creation; NFS/overlayfs quirks truncating the exclusive create.","solutions":["Delete the key file under the config dir (e.g. ~/.config/gws/encryption key file) and rerun — a fresh one will be generated (existing encrypted credentials must then be re-created via logout/login)","Check disk space and filesystem writability on the config dir","Serialize first-run initialization when multiple processes share the same HOME"],"exampleFix":"$ rm ~/.config/gws/gws.key   # then rerun gws auth login","handlingStrategy":"validation","validationCode":"// Before relying on the file-backend key, validate it parses to 32 bytes\nfn key_file_ok(path: &std::path::Path) -> bool {\n    use base64::{engine::general_purpose::STANDARD, Engine as _};\n    std::fs::read_to_string(path).ok()\n        .and_then(|s| STANDARD.decode(s.trim()).ok())\n        .is_some_and(|k| k.len() == 32)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Do not run two first-time initializations concurrently against a shared HOME (CI matrix)","Exclude the gws config dir from sync clients","If a key-file error appears, deleting the key file is always safe — worst case you re-login"],"tags":["encryption-key","race-condition","file-backend","corruption"],"backgroundTag":"corrupt-encryption-key-file","analyzedSha":"a3768d0e82ad83cca2da97724e46bea4ff0e6dbd","analyzedAt":"2026-08-16T19:51:46.516Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}