googleworkspace/cli · error · anyhow::Error
Failed to create token directory '{}': {}
Error message
Failed to create token directory '{}': {} What it means
`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.
Source
Thrown at crates/google-workspace-cli/src/token_storage.rs:86
match serde_json::from_str(&json) {
Ok(map) => map,
Err(e) => {
eprintln!(
"warning: failed to parse token cache JSON: {}",
sanitize_for_terminal(&e.to_string())
);
HashMap::new()
}
}
}
async fn save_to_disk(&self, map: &HashMap<String, TokenInfo>) -> anyhow::Result<()> {
let json = serde_json::to_string(map)?;
let encrypted = crate::credential_store::encrypt(json.as_bytes())?;
if let Some(parent) = self.file_path.parent() {
tokio::fs::create_dir_all(parent).await.map_err(|e| {
anyhow::anyhow!(
"Failed to create token directory '{}': {}",
sanitize_for_terminal(&parent.display().to_string()),
e
)
})?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
tokio::fs::set_permissions(parent, std::fs::Permissions::from_mode(0o700))
.await
.map_err(|e| {
anyhow::anyhow!(
"Failed to set permissions on token directory '{}': {}",
sanitize_for_terminal(&parent.display().to_string()),
e
)
})?;
}View on GitHub (pinned to a3768d0e82)
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.
Example fix
# before — service context, HOME unset [Unit] Service: ExecStart=/usr/bin/gws gmail +standup-report # -> Failed to create token directory '/.config/gws/tokens': Permission denied # after — explicit writable config dir [Service] Environment=HOME=/var/lib/gws # or: Environment=GOOGLE_WORKSPACE_CLI_CONFIG_DIR=/var/lib/gws/config ExecStart=/usr/bin/gws gmail +standup-report
Defensive patterns
Strategy: validation
Validate before calling
// Prove the token dir can be created/used before login
async fn token_dir_ok(dir: &std::path::Path) -> bool {
tokio::fs::create_dir_all(dir).await.is_ok()
&& tokio::fs::write(dir.join(".probe"), b"").await.is_ok()
&& tokio::fs::remove_file(dir.join(".probe")).await.is_ok()
} Try / catch
if let Err(e) = storage.save(&map).await {
if e.to_string().contains("Failed to create token directory") {
eprintln!("cannot create {} — check HOME/GOOGLE_WORKSPACE_CLI_CONFIG_DIR and permissions", e);
}
return Err(e);
} Prevention
- 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.
When it happens
Trigger: `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.
Common situations: 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.
Related errors
- Failed to write client config: {e}
- Failed to set permissions on token directory '{}': {}
- Token refresh failed with status {}: {}
- Token response contained no access token
- GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE points to {path}, but
AI-assisted analysis of googleworkspace/cli@a3768d0e82 (2026-08-16).
Data as JSON: /api/errors/5d22275d5c2a8cdd.
Report an issue: GitHub.