{"record":{"id":"c53b850adc151dd6","repo":"googleworkspace/cli","slug":"failed-to-set-permissions-on-token-directory","errorCode":null,"errorMessage":"Failed to set permissions on token directory '{}': {}","messagePattern":"Failed to set permissions on token directory '(.+?)': (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/google-workspace-cli/src/token_storage.rs","lineNumber":98,"sourceCode":"    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            }\n        }\n\n        // Write atomically via a sibling .tmp file + rename.\n        crate::fs_util::atomic_write_async(&self.file_path, encrypted.as_slice()).await?;\n\n        Ok(())\n    }\n\n    // Helper to join scopes consistently for cache keys\n    fn cache_key(scopes: &[&str]) -> String {\n        let mut s: Vec<&str> = scopes.to_vec();\n        s.sort_unstable();","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/googleworkspace/cli/blob/a3768d0e82ad83cca2da97724e46bea4ff0e6dbd/crates/google-workspace-cli/src/token_storage.rs#L80-L116","documentation":"After creating the token directory, `save_to_disk()` applies `chmod 0700` (unix only) and this call failed. The directory exists and was created, but the OS refused the permission change — typical on filesystems that don't support POSIX mode bits (vfat/exFAT/NTFS mounts, some NFS/CIFS configurations) or when the directory was concurrently replaced by something owned by another user.","triggerScenarios":"`GOOGLE_WORKSPACE_CLI_CONFIG_DIR` pointed at a FAT/NTFS-mounted USB or Windows-drive mount; an NFS home with root-squash oddities; another process (or admin tool) chowning/replacing the dir between create and chmod; SELinux/AppArmor denying chmod.","commonSituations":"WSL with config on /mnt/c; mounted external drives; hardened SELinux hosts; shared multi-user systems.","solutions":["Move the config dir to a POSIX filesystem: `GOOGLE_WORKSPACE_CLI_CONFIG_DIR=$HOME/.config/gws` with $HOME on ext4/xfs/apfs.","On SELinux systems, check for denials (`ausearch -m avc -ts recent`) and adjust the context for the config path.","Ensure no concurrent gws/auth processes are racing on the same dir; retry the login.","As a last resort on non-POSIX mounts, accept that mode bits cannot be enforced and use an encrypted-filesystem path instead (the token file is itself AES-GCM encrypted, but directory perms are defense-in-depth)."],"exampleFix":"# before — config dir on a Windows mount under WSL\nexport GOOGLE_WORKSPACE_CLI_CONFIG_DIR=/mnt/c/Users/me/gws\n gws auth login  # -> Failed to set permissions on token directory '...': Operation not supported\n\n# after — keep secrets on the Linux filesystem\nexport GOOGLE_WORKSPACE_CLI_CONFIG_DIR=\"$HOME/.config/gws\"\ngws auth login","handlingStrategy":"try-catch","validationCode":"// Detect non-POSIX mounts before relying on chmod\nfn supports_posix_perms(p: &std::path::Path) -> bool {\n    use std::os::unix::fs::MetadataExt;\n    match std::fs::metadata(p) {\n        Ok(m) => m.mode() & 0o777 != 0 || true, // best-effort: probe with a real chmod instead\n        Err(_) => false,\n    }\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = tokio::fs::set_permissions(parent, perms).await {\n    // 0700 is defense-in-depth; the token file itself is AES-GCM encrypted.\n    // Log loudly but do not fail the login on filesystems without POSIX bits.\n    tracing::warn!(error = %e, dir = %parent.display(), \"cannot enforce 0700 on token dir (non-POSIX fs?)\");\n}","preventionTips":["Keep GOOGLE_WORKSPACE_CLI_CONFIG_DIR on a native POSIX filesystem (ext4/xfs/apfs), never on FAT/NTFS mounts.","On SELinux hosts, label the config path correctly before login.","Avoid concurrent gws auth operations against the same config dir."],"tags":["filesystem","permissions","chmod","token-storage","unix"],"backgroundTag":"file-permission-change-failed","analyzedSha":"a3768d0e82ad83cca2da97724e46bea4ff0e6dbd","analyzedAt":"2026-08-16T19:51:46.516Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}