Repository navigation
Description
Summary
When gws cannot decrypt credentials.enc, it deletes the file rather than reporting the problem. The error surfaced to the user is unrelated to authentication, so there is no indication that the saved login was just destroyed. The only recovery is gws auth login.
I hit this with real credentials, then reproduced it deliberately in an isolated config directory.
Environment
- gws 0.22.5
- macOS 26.5.2 (Darwin 25.5.0), Apple Silicon (arm64)
- zsh, Node.js v26.5.0
- Keyring backend:
keyring(macOS Keychain), storage:encrypted
Reproduction
export D=/tmp/gws-repro
mkdir -p $D
# minimal client config (values can be fake)
cat > $D/client_secret.json <<'EOF'
{"installed":{"client_id":"000000000000-fake.apps.googleusercontent.com",
"project_id":"fake-project-123","auth_uri":"https://accounts.google.com/o/oauth2/auth",
"token_uri":"https://oauth2.googleapis.com/token","client_secret":"FAKE"}}
EOF
# stand in for a credentials file that cannot be decrypted
echo "UNDECRYPTABLE" > $D/credentials.enc
ls $D # -> client_secret.json credentials.enc
GOOGLE_WORKSPACE_CLI_CONFIG_DIR=$D gws drive files list --params '{"pageSize":1}'
ls $D # -> client_secret.json (credentials.enc is gone)credentials.enc is deleted. Nothing in the command output mentions credentials, decryption, or logout.
I ran this both with the Keychain reachable and in a stripped environment where it was not. The file is deleted in both cases, so the trigger is decryption failure generally, not Keychain availability specifically.
How this happened with real credentials
I had a working, fully authenticated setup. I then switched install methods — from npx @googleworkspace/cli to npm install -g @googleworkspace/cli — and ran a command.
macOS Keychain access control is per-binary-path, so the newly installed binary at a different path could not retrieve the encryption key that the previous one stored. Decryption failed, and credentials.enc plus token_cache.json were removed. gws auth status then reported auth_method: none, as though I had never signed in.
If that is the mechanism, any change to how gws is installed can silently destroy a user's saved login, which is a surprising outcome for what looks like a routine upgrade.
Expected behavior
Decryption failure should not delete user data. Preferably:
- Report the actual cause, e.g.
Failed to decrypt credentials (encryption key not available from keyring). Run 'gws auth login' to re-authenticate. - Leave
credentials.encin place, or move it aside (credentials.enc.bak) rather than removing it. - If deletion is intentional, say so explicitly in the output so the user knows they have been logged out.
The current behavior is hard to diagnose because the visible error points somewhere else entirely — in my case at the GCP project, which was not the problem.
Related
- macOS: Credential decryption always fails - OS Keyring not storing encryption key #360 (closed) covers decryption failing on macOS, but not the silent deletion of the credentials file.
- gws auth login can silently leave a stale/split token cache, causing 403 insufficient authentication scopes after re-auth #876 covers a stale/split token cache after re-auth, which is a different failure mode.
Activity
KlaasKing commentedon Aug 21, 2026
Same deletion, different trigger — and one that I suspect is common: several MCP clients sharing one config dir while disagreeing about the keyring backend.
Setup
Four
gwsprofiles under~/.config/gws/<profile>, each its own Google account. Five MCP clients use those same four profiles viagws-mcp-server: Claude Code (CLI), Claude Desktop, Codex CLI, Gemini CLI and Antigravity IDE. Each client config passesGOOGLE_WORKSPACE_CLI_CONFIG_DIRand--gws-path.What happened
One client (Claude Code) was switched to
GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=file, because it also runs from alaunchdjob where the macOS Keychain is not reliably reachable. The other four clients kept the defaultkeyringbackend — their configs simply never mentioned the variable.From that day on, the profiles broke roughly daily, seemingly at random. The pattern was always the same:
credentials.encencrypted with.encryption_key.gwsdeletes the file — exactly the behaviour in this issue.Which profile went down depended only on which client had touched it last, which is why it looked random for days. Re-authenticating "fixed" it for anywhere between minutes and a few hours. The visible errors pointed elsewhere —
invalid_grant: reauth related error (invalid_rapt)— so the decryption mismatch stayed invisible until I ran a command by hand and saw:"Created on a different machine" is a reasonable guess for the single-user case, but here both writers were the same machine, the same binary and the same config dir — only the backend differed.
Why I think this will hit others
Nothing in the MCP setup path surfaces the backend choice. A client config specifies
GOOGLE_WORKSPACE_CLI_CONFIG_DIRand that reads as sufficient — the config dir looks like the unit of identity. The backend is an environment property, so two clients pointed at the same directory can silently disagree, and the failure only shows up later as an unrelated auth error. Anyone runninggwsfrom more than one MCP client, or from both a GUI client and a background job, can end up here.What would have helped, in order of usefulness
.encryption_key), no client could disagree with the directory it was pointed at. This alone would have prevented the whole thing.credentials.enc.unreadableand say what happened. Already the substance of this issue, but worth restating: deletion turns a recoverable mismatch into a forced re-login for every client at once.gws auth status. When.encryption_keyexists in the config dir but the active backend iskeyring(or vice versa), that is detectable and worth a warning.Workaround, offered as data rather than as a solution
I have pinned
GOOGLE_WORKSPACE_CLI_KEYRING_BACKEND=fileacross all five client configs, and put a small wrapper in front ofgwsvia--gws-paththat (a) serialises calls per config dir withflock, and (b) keeps a copy ofcredentials.enc+.encryption_keyafter each successful call and restores it ifgwshas deleted it, giving up after three restores in fifteen minutes so a genuinely expired token still surfaces.To be clear: this has been running for a few hours, not weeks. I cannot yet say it holds. I am reporting it mainly because the diagnosis took several days and the deletion is what made it so hard to see — the evidence kept destroying itself.
fix(auth): rename undecryptable credentials.enc instead of deleting it
fix(auth): rename undecryptable credentials.enc instead of deleting it
fix(auth): rename undecryptable credentials.enc instead of deleting i…
fix(auth): trim OAuth Client ID/Secret on input and load (upstream go…
mattlampson commentedon Oct 19, 2026
Another trigger for the same deletion, with a valid credential and no install change — just a process that is denied keychain interaction.
Environment: gws 0.22.5 (Homebrew,
/opt/homebrew/bin/gws, unchanged binary), macOS 27.0.1 (Apple Silicon), keyring backendkeyring, storageencrypted.What happened: the same
credentials.encworks from Terminal/SSH sessions and from a launchd user agent that runsgws script scripts runevery 5 minutes. Running any read-only command (e.g.gws sheets spreadsheets values get …) from a process launched by a desktop app whose children macOS refuses interactive login-keychain access printed:…and deleted the file. The scheduled agent immediately lost auth (401) until a full
gws auth login. It happened three times in one day, once right after restoring the file from a Time Machine snapshot.Why this matters beyond #886's case: here the ciphertext was never corrupt and the key still existed — the keychain simply declined that caller (
errSecInteractionNotAllowed). A transient, per-process access refusal is being treated as proof the credential is garbage, so one restricted process destructively logs out every other consumer sharing the config dir (similar shape to the multi-client comment above).Suggested fix (complements #891): distinguish keyring access/interaction failures from authenticated-decryption failures. For the former, exit non-zero with an actionable message and leave
credentials.encbyte-identical; never delete on a keyring error. If deletion is ever kept for genuine corruption, rename tocredentials.enc.bakinstead.Workaround we use meanwhile: a wrapper that snapshots
credentials.encbefore each run and restores it only if the file existed at start and vanished during that run, plus routing restricted callers through a launchd agent that does have keychain access.fix(auth): keep credentials.enc when it cannot be decrypted