googleworkspace/cli · error · anyhow::Error

Failed to write client config: {e}

Error message

Failed to write client config: {e}

What it means

`save_client_config()` could not persist the OAuth client_secret JSON to `<config dir>/client_secret.json`: the atomic write (sibling temp file + rename via `fs_util::atomic_write`) returned an OS error, wrapped with this message. The parent directory is created first, so the failure is almost always a filesystem-level problem with the config directory itself, not a missing path.

Source

Thrown at crates/google-workspace-cli/src/oauth_config.rs:85

        installed: InstalledConfig {
            client_id: client_id.to_string(),
            client_secret: client_secret.to_string(),
            project_id: project_id.to_string(),
            auth_uri: "https://accounts.google.com/o/oauth2/auth".to_string(),
            token_uri: "https://oauth2.googleapis.com/token".to_string(),
            auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs".to_string(),
            redirect_uris: vec!["http://localhost".to_string()],
        },
    };

    let path = client_config_path();
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent)?;
    }

    let json = serde_json::to_string_pretty(&config)?;
    crate::fs_util::atomic_write(&path, json.as_bytes())
        .map_err(|e| anyhow::anyhow!("Failed to write client config: {e}"))?;

    Ok(path)
}

/// Loads OAuth client configuration from the standard Google Cloud Console format.
pub fn load_client_config() -> anyhow::Result<InstalledConfig> {
    let path = client_config_path();
    let data = std::fs::read_to_string(&path)
        .map_err(|e| anyhow::anyhow!("Cannot read {}: {e}", path.display()))?;
    let file: ClientSecretFile = serde_json::from_str(&data)
        .map_err(|e| anyhow::anyhow!("Invalid client_secret.json format: {e}"))?;
    Ok(file.installed)
}

#[cfg(test)]
mod tests {
    use super::*;

View on GitHub (pinned to a3768d0e82)

Solutions

  1. Check writability: `touch ~/.config/gws/.probe` (or your CONFIG_DIR) — fix ownership with `chown -R $USER ~/.config/gws` if root-owned.
  2. If intentional, point `GOOGLE_WORKSPACE_CLI_CONFIG_DIR` to a writable directory and re-run the auth command.
  3. Free disk space / raise quota if the write failed with ENOSPC.
  4. Remove immutable attributes (`chattr -i`) or read-only mounts on the config dir.

Example fix

# before
GOOGLE_WORKSPACE_CLI_CONFIG_DIR=/etc/gws gws auth login
# -> Failed to write client config: ... Permission denied

# after — point at a writable dir (or fix ownership of the default)
export GOOGLE_WORKSPACE_CLI_CONFIG_DIR="$HOME/.config/gws"
chown -R "$(id -u):$(id -g)" "$HOME/.config/gws"
gws auth login
Defensive patterns

Strategy: validation

Validate before calling

// Prove the config dir is writable before starting auth
fn config_dir_writable(dir: &std::path::Path) -> bool {
    std::fs::create_dir_all(dir).is_ok()
        && std::fs::write(dir.join(".probe"), b"").is_ok()
        && std::fs::remove_file(dir.join(".probe")).is_ok()
}

Try / catch

if let Err(e) = gws_oauth_config::save_client_config(id, secret, project) {
    if e.to_string().contains("Failed to write client config") {
        eprintln!("config dir not writable — set GOOGLE_WORKSPACE_CLI_CONFIG_DIR to a writable path");
    }
    return Err(e);
}

Prevention

When it happens

Trigger: `gws auth login` / `gws auth setup` when `GOOGLE_WORKSPACE_CLI_CONFIG_DIR` points somewhere unwritable (a root-owned dir, a read-only mount); the default `~/.config/gws` being read-only (chmod 500); disk full; an immutable-file attribute (chattr +i) on the file.

Common situations: Running as a different user than the one that owns ~/.config; Docker containers with read-only volumes mounted at the config dir; home directory quota exhausted; sudo/root-owned ~/.config/gws from a previous run.

Related errors


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