googleworkspace/cli · error

key file exists but is corrupt

Error message

key file exists but is corrupt

What it means

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.

Source

Thrown at crates/google-workspace-cli/src/credential_store.rs:337

                    );
                }
            }
        }
    }

    // --- 2. File fallback ------------------------------------------------
    if let Some(key) = read_key_file(key_file) {
        return Ok(key);
    }

    // --- 3. Generate new key, save to file (race-safe) -------------------
    let key = generate_random_key();
    let b64_key = STANDARD.encode(key);
    match save_key_file_exclusive(key_file, &b64_key) {
        Ok(()) => Ok(key),
        Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => {
            // Another process created the file first — use their key.
            read_key_file(key_file).ok_or_else(|| anyhow::anyhow!("key file exists but is corrupt"))
        }
        Err(e) => Err(e.into()),
    }
}

/// Returns the encryption key, generating and persisting one if it doesn't exist.
///
/// The key is cached in-process via `OnceLock` so it is only read from disk once.
/// Backend selection is controlled by `GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND`.
fn get_or_create_key() -> anyhow::Result<[u8; 32]> {
    static KEY: OnceLock<[u8; 32]> = OnceLock::new();

    if let Some(key) = KEY.get() {
        return Ok(*key);
    }

    #[cfg(not(test))]
    let backend = KeyringBackend::from_env();

View on GitHub (pinned to a3768d0e82)

Solutions

  1. 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)
  2. Check disk space and filesystem writability on the config dir
  3. Serialize first-run initialization when multiple processes share the same HOME

Example fix

$ rm ~/.config/gws/gws.key   # then rerun gws auth login
Defensive patterns

Strategy: validation

Validate before calling

// Before relying on the file-backend key, validate it parses to 32 bytes
fn key_file_ok(path: &std::path::Path) -> bool {
    use base64::{engine::general_purpose::STANDARD, Engine as _};
    std::fs::read_to_string(path).ok()
        .and_then(|s| STANDARD.decode(s.trim()).ok())
        .is_some_and(|k| k.len() == 32)
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of googleworkspace/cli@a3768d0e82 (2026-08-16). Data as JSON: /api/errors/2345e789aa44a1fd. Report an issue: GitHub.