{"record":{"id":"5d22275d5c2a8cdd","repo":"googleworkspace/cli","slug":"failed-to-create-token-directory","errorCode":null,"errorMessage":"Failed to create token directory '{}': {}","messagePattern":"Failed to create token directory '(.+?)': (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/google-workspace-cli/src/token_storage.rs","lineNumber":86,"sourceCode":"        match serde_json::from_str(&json) {\n            Ok(map) => map,\n            Err(e) => {\n                eprintln!(\n                    \"warning: failed to parse token cache JSON: {}\",\n                    sanitize_for_terminal(&e.to_string())\n                );\n                HashMap::new()\n            }\n        }\n    }\n\n    async fn save_to_disk(&self, map: &HashMap<String, TokenInfo>) -> anyhow::Result<()> {\n        let json = serde_json::to_string(map)?;\n        let encrypted = crate::credential_store::encrypt(json.as_bytes())?;\n\n        if let Some(parent) = self.file_path.parent() {\n            tokio::fs::create_dir_all(parent).await.map_err(|e| {\n                anyhow::anyhow!(\n                    \"Failed to create token directory '{}': {}\",\n                    sanitize_for_terminal(&parent.display().to_string()),\n                    e\n                )\n            })?;\n            #[cfg(unix)]\n            {\n                use std::os::unix::fs::PermissionsExt;\n                tokio::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700))\n                    .await\n                    .map_err(|e| {\n                        anyhow::anyhow!(\n                            \"Failed to set permissions on token directory '{}': {}\",\n                            sanitize_for_terminal(&parent.display().to_string()),\n                            e\n                        )\n                    })?;\n            }","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/googleworkspace/cli/blob/a3768d0e82ad83cca2da97724e46bea4ff0e6dbd/crates/google-workspace-cli/src/token_storage.rs#L68-L104","documentation":"`FileTokenStorage::save_to_disk()` failed at `tokio::fs::create_dir_all(parent)` for the token-cache directory (the parent of the encrypted token file under the gws config dir). The message includes the sanitized directory path and OS error. The token map is encrypted and written via atomic rename immediately after, so this is a pure filesystem/permissions problem on the directory path.","triggerScenarios":"`gws auth login` when the config dir (default `~/.config/gws`, overridable via `GOOGLE_WORKSPACE_CLI_CONFIG_DIR`) cannot be created: parent is read-only, HOME unset in a service context, path collides with an existing *file*, or sandbox/seatbelt denies writes outside allowed paths.","commonSituations":"Running gws under systemd/launchd without HOME set; macOS Gatekeeper/sandboxed execution; CONFIG_DIR pointing into a read-only container volume; a leftover file named like the directory.","solutions":["Ensure HOME is set (or CONFIG_DIR exported) in the environment the command runs in — `runuser -u me -- env HOME=/home/me gws auth login`.","mkdir -p the path shown in the error by hand and fix ownership/permissions (0700, your uid).","Remove any *file* occupying the directory path (ENOTDIR shows as 'Not a directory').","In containers, mount a writable volume at the config dir or point CONFIG_DIR at /tmp-based storage for ephemeral auth."],"exampleFix":"# before — service context, HOME unset\n[Unit] Service: ExecStart=/usr/bin/gws gmail +standup-report\n# -> Failed to create token directory '/.config/gws/tokens': Permission denied\n\n# after — explicit writable config dir\n[Service]\nEnvironment=HOME=/var/lib/gws\n# or: Environment=GOOGLE_WORKSPACE_CLI_CONFIG_DIR=/var/lib/gws/config\nExecStart=/usr/bin/gws gmail +standup-report","handlingStrategy":"validation","validationCode":"// Prove the token dir can be created/used before login\nasync fn token_dir_ok(dir: &std::path::Path) -> bool {\n    tokio::fs::create_dir_all(dir).await.is_ok()\n        && tokio::fs::write(dir.join(\".probe\"), b\"\").await.is_ok()\n        && tokio::fs::remove_file(dir.join(\".probe\")).await.is_ok()\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = storage.save(&map).await {\n    if e.to_string().contains(\"Failed to create token directory\") {\n        eprintln!(\"cannot create {} — check HOME/GOOGLE_WORKSPACE_CLI_CONFIG_DIR and permissions\", e);\n    }\n    return Err(e);\n}","preventionTips":["Always run gws with HOME set, or export GOOGLE_WORKSPACE_CLI_CONFIG_DIR explicitly in services/containers.","Pre-create and chown the config dir in provisioning scripts.","Ensure no regular file shadows any directory component of the token path."],"tags":["filesystem","auth","token-storage","permissions","config"],"backgroundTag":"file-write-permission-denied","analyzedSha":"a3768d0e82ad83cca2da97724e46bea4ff0e6dbd","analyzedAt":"2026-08-16T19:51:46.516Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}