{"record":{"id":"454040c423131529","repo":"googleworkspace/cli","slug":"invalid-client-secret-json-format-e","errorCode":null,"errorMessage":"Invalid client_secret.json format: {e}","messagePattern":"Invalid client_secret\\.json format: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/google-workspace-cli/src/oauth_config.rs","lineNumber":96,"sourceCode":"    let path = client_config_path();\n    if let Some(parent) = path.parent() {\n        std::fs::create_dir_all(parent)?;\n    }\n\n    let json = serde_json::to_string_pretty(&config)?;\n    crate::fs_util::atomic_write(&path, json.as_bytes())\n        .map_err(|e| anyhow::anyhow!(\"Failed to write client config: {e}\"))?;\n\n    Ok(path)\n}\n\n/// Loads OAuth client configuration from the standard Google Cloud Console format.\npub fn load_client_config() -> anyhow::Result<InstalledConfig> {\n    let path = client_config_path();\n    let data = std::fs::read_to_string(&path)\n        .map_err(|e| anyhow::anyhow!(\"Cannot read {}: {e}\", path.display()))?;\n    let file: ClientSecretFile = serde_json::from_str(&data)\n        .map_err(|e| anyhow::anyhow!(\"Invalid client_secret.json format: {e}\"))?;\n    Ok(file.installed)\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n\n    #[test]\n    fn test_save_load_round_trip() {\n        let dir = tempfile::tempdir().unwrap();\n        let path = dir.path().join(\"client_secret.json\");\n\n        let config = ClientSecretFile {\n            installed: InstalledConfig {\n                client_id: \"test-id.apps.googleusercontent.com\".to_string(),\n                client_secret: \"GOCSPX-test\".to_string(),\n                project_id: \"my-project\".to_string(),\n                auth_uri: \"https://accounts.google.com/o/oauth2/auth\".to_string(),","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/googleworkspace/cli/blob/a3768d0e82ad83cca2da97724e46bea4ff0e6dbd/crates/google-workspace-cli/src/oauth_config.rs#L78-L114","documentation":"The client_secret.json file was read but `serde_json::from_str::<ClientSecretFile>` failed. The struct requires a top-level `installed` key (see the format documented at the top of oauth_config.rs); the serde error text says exactly which field broke. The classic cause is downloading the wrong OAuth client type from Google Cloud Console — a Web application client downloads `{\"web\": {...}}`, which deserializes to nothing here.","triggerScenarios":"User saved a 'Web application' or 'Chrome app' client JSON instead of 'Desktop app'; the file was truncated or empty; the file is actually the OAuth *token/credentials* JSON (client env credentials, service-account key) rather than the client secret; hand-edited JSON with a syntax error or a renamed key.","commonSituations":"Following generic Google OAuth docs that default to Web clients; copy-pasting only part of the JSON; confusing the service-account key file with the client secret; saving with a BOM or wrapped in quotes.","solutions":["In Google Cloud Console → APIs & Services → Credentials, create an OAuth client of type **Desktop app** and download that JSON — it contains the `installed` key.","If you must keep the Web client, its JSON has a `web` key — either re-wrap it as `installed` (copying client_id/client_secret/auth_uri/token_uri) or use env vars instead.","Validate the file locally: `jq 'keys' client_secret.json` must print [\"installed\"].","If you meant to use a credentials file rather than a client secret, set `GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE` instead of placing it at client_secret.json."],"exampleFix":"# before — Web client JSON fails to deserialize\njq 'keys' ~/.config/gws/client_secret.json   # [\"web\"]\ngws auth login  # -> Invalid client_secret.json format: missing field `installed`\n\n# after — download a Desktop app client, or rewrite the envelope\njq '{installed: .web | {client_id, client_secret, project_id,\n     auth_uri: \"https://accounts.google.com/o/oauth2/auth\",\n     token_uri: \"https://oauth2.googleapis.com/token\"}}' \\\n  web_secret.json > ~/.config/gws/client_secret.json\ngws auth login","handlingStrategy":"validation","validationCode":"// Validate the secret file shape before handing it to gws\nuse serde_json::Value;\nfn is_desktop_client_secret(path: &std::path::Path) -> anyhow::Result<bool> {\n    let v: Value = serde_json::from_str(&std::fs::read_to_string(path)?)?;\n    Ok(v.get(\"installed\").is_some())\n}","typeGuard":null,"tryCatchPattern":"match serde_json::from_str::<ClientSecretFile>(&data) {\n    Err(e) if data.trim_start().starts_with('{') && data.contains(\"\\\"web\\\"\") => {\n        eprintln!(\"this is a Web-application client — download a Desktop app client instead\");\n        Err(anyhow::anyhow!(\"Invalid client_secret.json format: {e}\"))\n    }\n    other => other.map(|f: ClientSecretFile| f.installed),\n}","preventionTips":["Always create OAuth clients of type 'Desktop app' for CLI use — the JSON must have an `installed` key.","Automate checks: `jq -e 'has(\"installed\")' client_secret.json` in provisioning pipelines.","Never hand-edit the secret JSON; download it fresh from Cloud Console."],"tags":["oauth","client-secret","json","serde","auth"],"backgroundTag":"oauth-client-config-invalid","analyzedSha":"a3768d0e82ad83cca2da97724e46bea4ff0e6dbd","analyzedAt":"2026-08-16T19:51:46.516Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}