Password Managers
The ws CLI reads its API token from the WS_TOKEN environment variable, and WS_TOKEN takes priority over any token stored in ws.toml or ~/.config/ws/config.toml. That means you can keep your config files tokenless and let a password manager, secret manager, or OS keychain supply the token for each command.
Note:
WS_TOKENoverrides file configuration, so the examples below work with aws.tomlthat contains onlyurl,defaults.workspace_key, andstatus_aliases. See CLI Configuration for the full priority order.
The Pattern
Every example has the same shape: fetch the secret, pass it as WS_TOKEN, run the command.
WS_TOKEN=<fetch the secret> ws task minePrefer this one-shot form over exporting the variable. A one-shot token exists only for that single command. If you export WS_TOKEN=..., every program you start in that shell inherits it until the shell exits.
# Token available for one command only (recommended)
WS_TOKEN=$(op read "op://Dev/windshift/credential") ws task mine
# Token available for the rest of the shell session
export WS_TOKEN=$(op read "op://Dev/windshift/credential")
ws task mineWhy Not Put the Token in ws.toml
ws.toml is a project file. Projects get shared, committed, and cloned, and a token in that file overrides the global token for everyone who uses the checkout. Keep the token out of it:
ws.toml
WINDSHIFT.md1Password
With op run (recommended)
Create a .env file (gitignore it) containing an op:// reference:
# .env
WS_TOKEN=op://Dev/windshift/credentialThen run any ws command through op run:
op run --env-file=.env -- ws task mineWith op read
WS_TOKEN=$(op read "op://Dev/windshift/credential") ws task mineBitwarden
Unlock the vault once per shell, then read the item:
export BW_SESSION=$(bw unlock --raw)
WS_TOKEN=$(bw get password windshift) ws task minepass
WS_TOKEN=$(pass show windshift/token) ws task mineHashiCorp Vault
WS_TOKEN=$(vault kv get -field=token secret/windshift) ws task mineDoppler
Store the token as WS_TOKEN in your Doppler config, then run the CLI through Doppler:
doppler run -- ws task mineInfisical
Store the token as WS_TOKEN in your Infisical project and environment, then:
infisical run -- ws task mineIf the directory is not linked to a project, pass the identifiers:
infisical run --projectId <project-id> --env prod -- ws task mineOr fetch a single secret:
WS_TOKEN=$(infisical secrets get WS_TOKEN --plain --env=prod) ws task minemacOS Keychain
Store the token once:
security add-generic-password -a "$USER" -s windshift-token -w "crw_..." -UThen read it per command:
WS_TOKEN=$(security find-generic-password -a "$USER" -s windshift-token -w) ws task mineCloud Secret Managers
AWS Secrets Manager
WS_TOKEN=$(aws secretsmanager get-secret-value --secret-id windshift/token \
--query SecretString --output text) ws task mineGoogle Cloud Secret Manager
WS_TOKEN=$(gcloud secrets versions access latest --secret=windshift-token) ws task mineAzure Key Vault
WS_TOKEN=$(az keyvault secret show --vault-name my-vault --name windshift-token \
--query value -o tsv) ws task mineShell Sessions with direnv
If you always want the token available in a specific checkout, use direnv with a gitignored .envrc:
# .envrc
export WS_TOKEN="$(op read 'op://Dev/windshift/credential')"direnv allowAfter that, plain ws task mine works in that directory. Keep secret values out of a committed .envrc.
CI
Inject WS_TOKEN from the CI secret store. Do not write it into ws.toml. When WS_URL and WS_TOKEN are set, the CLI does not need a config file at all. If you do want a file, ws config init --non-interactive reads the same environment variables.
GitHub Actions
env:
WS_URL: https://windshift.example.com
WS_WORKSPACE: ENG
WS_TOKEN: ${{ secrets.WS_TOKEN }}GitLab CI
variables:
WS_URL: https://windshift.example.com
WS_WORKSPACE: ENG
WS_TOKEN: $WS_TOKENTroubleshooting
- "API token not configured" —
WS_TOKENis not set in the process that runsws. Exporting it in a parent shell does not reach a command launched from elsewhere. op,bw,vault, orinfisicalnot found — the manager's CLI is not onPATHfor that shell or CI job.- Authentication fails with a token that looks correct — the secret may contain a trailing newline or multiple lines. Command substitution strips trailing newlines, but a multi-line secret does not. Fetch a single field, or pipe through
head -n1.
Related
- CLI Configuration — priority order, config file format, and environment variables
- CLI Installation — install and initial setup