Skip to content

GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE ignored when credentials.enc exists (inverts documented precedence) #857

Open

Description

@rndmcnlly

Summary

On 0.22.5, when an encrypted credentials.enc exists in the config dir, setting GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE has no effect: API calls authenticate as the encrypted-credential account, not the account in the file. This inverts the documented precedence.

AGENTS.md and the README both state that CREDENTIALS_FILE takes precedence over encrypted credentials, with encrypted credentials being the fallback only when the env var is unset:

GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE | Path to OAuth credentials JSON (no default; if unset, falls back to encrypted credentials in ~/.config/gws/)

(README precedence table: GOOGLE_WORKSPACE_CLI_TOKEN = priority 1, GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE = priority 2, encrypted credentials = priority 3.)

Observed precedence is the reverse for priorities 2 vs 3: the encrypted store wins whenever credentials.enc is present, even with CREDENTIALS_FILE explicitly set.

Reproduction

Controlled A/B in an isolated config dir, varying only the presence of credentials.enc. Two distinct accounts (call them A and B); credentials.enc is account A, the credentials file is account B.

# Case 1: config dir has a credentials.enc (account A), written by `gws auth login`
GOOGLE_WORKSPACE_CLI_CONFIG_DIR=/tmp/dir-with-enc \
GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/path/to/accountB-creds.json \
  gws drive about get --params '{"fields":"user(emailAddress)"}'
# => returns account A's email   (CREDENTIALS_FILE ignored)

# Case 2: identical, but config dir has NO credentials.enc
GOOGLE_WORKSPACE_CLI_CONFIG_DIR=/tmp/dir-without-enc \
GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE=/path/to/accountB-creds.json \
  gws drive about get --params '{"fields":"user(emailAddress)"}'
# => returns account B's email   (CREDENTIALS_FILE honored, correct)
credentials.enc present CREDENTIALS_FILE set Identity returned
yes yes (account B) account A (wrong)
no yes (account B) account B (correct)

The only difference between the two runs is whether credentials.enc exists. The credentials file is well-formed (type: authorized_user with a valid refresh_token); Case 2 proves it loads and authenticates correctly in isolation. This was reproduced from a freshly gws auth login-written credentials.enc in a pristine GOOGLE_WORKSPACE_CLI_CONFIG_DIR, so it is not stale/migration state.

Expected vs actual

  • Expected (per docs): with CREDENTIALS_FILE set, it wins; credentials.enc is only the fallback when the var is unset.
  • Actual: credentials.enc shadows CREDENTIALS_FILE.

Impact

This is the documented multi-account story: a user with a logged-in default account cannot select an alternate account via CREDENTIALS_FILE, because the default's credentials.enc always wins. The only reliable workaround is an isolated GOOGLE_WORKSPACE_CLI_CONFIG_DIR per account (which has no credentials.enc to shadow the file). Worth noting the failure is silent: it returns another account's data with a 200, rather than erroring, which is a sharp edge for agent use.

Environment

  • gws 0.22.5 (Homebrew googleworkspace-cli 0.22.5, macOS arm64)
  • keyring backend (default)

Possible locus

Credential-source selection likely consults the encrypted store before checking the GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE env var (auth.rs / credential_store.rs). The TOKEN (priority 1) path was not tested here.


Filed by an agent (Claude Opus 4.8 via opencode) and verified by a human (the clean-room A/B repro above was run interactively). ✨

Activity

  1. cuppett commented on Jun 30, 2026

    @cuppett

    Can confirm this also affects GOOGLE_WORKSPACE_CLI_TOKEN (documented as priority 1).

    Setup: gws 0.22.5 on Fedora 44 x86_64, keyring backend. gws auth login was done against GCP project A (cloudability-it). A separate OAuth client on GCP project B (dev) has Gmail/Calendar/Drive/Sheets APIs enabled.

    Repro for TOKEN (priority 1):

    # Get a fresh access token from project B's OAuth client
    ACCESS_TOKEN=$(python3 -c "
    from google.oauth2.credentials import Credentials
    from google.auth.transport.requests import Request
    import json
    with open(\"creds.json\") as f: data = json.load(f)
    c = Credentials(token=data[\"token\"], refresh_token=data[\"refresh_token\"],
        token_uri=data[\"token_uri\"], client_id=data[\"client_id\"],
        client_secret=data[\"client_secret\"], scopes=data[\"scopes\"])
    if c.expired or not c.valid: c.refresh(Request())
    print(c.token)")
    
    # Verify the token works via raw curl (project B's Gmail API)
    curl -s -H "Authorization: Bearer $ACCESS_TOKEN" \
      "https://gmail.googleapis.com/gmail/v1/users/me/profile"
    # => {"emailAddress":"","messagesTotal":X,...}  ✓
    
    # Same token passed to gws via the env var
    GOOGLE_WORKSPACE_CLI_TOKEN="$ACCESS_TOKEN" \
      gws gmail users getProfile --params '{"userId":"me"}'
    # => 403: "Gmail API has not been used in project cloudability-it-gemini"
    #    (project A's error — TOKEN ignored, encrypted creds used instead)

    Also tried combining TOKEN + CONFIG_DIR pointed at an empty dir + KEYRING_BACKEND=file — still got the project A error. Only way to get the env var honored was removing credentials.enc entirely (or in our case, disabling gws from the calling script).

    So the full observed precedence is:

    Priority Documented Actual
    1 TOKEN credentials.enc
    2 CREDENTIALS_FILE credentials.enc
    3 credentials.enc TOKEN or CREDENTIALS_FILE (only when .enc absent)

    All three priorities are inverted when credentials.enc exists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions