Skip to content
varsafe
Esc
navigateopen⌘Jpreview
On this page

Troubleshooting

Exact CLI error messages, what actually causes them, and the fastest fix — from stale contexts to rate limits.

CLI v7.2.7

Real failure modes, matched to the exact message the CLI prints. Each section starts with the error, explains the cause, and gives the fix.

Invalid or expired token

Invalid or expired token

Printed when the API rejects the token. Two distinct causes:

1. The token is actually invalid — it was revoked, rotated (rotation invalidates the old token immediately), or mistyped. Create or copy a fresh token from the dashboard’s API Tokens page.

2. The token is fine, but your saved context points at a team the token cannot access. The CLI sends the active team from your saved context with every request. API tokens are scoped to one team — if a leftover context references a different team, a perfectly valid token is rejected. This bites when you switch between a personal login and a CI token on the same machine.

Fix by clearing the stale context, then retry:

# If a .varsafe file exists in the directory (or any parent), remove it —
# it takes priority over the global context
rm ./.varsafe

# Then clear the global saved context
varsafe use --clear

# Confirm the token is accepted (reads VARSAFE_TOKEN from the environment)
varsafe whoami

Browser login hangs or never completes

varsafe login opens your browser to authenticate. On headless machines, over SSH, or when the browser session gets stuck, use one of the non-browser paths:

# API token, prompted with masked input (not visible in shell history)
varsafe login --token-prompt

# API token piped in from a secret manager
secret-manager read varsafe-token | varsafe login -t -

There is no email/password path — passwords are only ever entered in the browser. On a machine with no keychain at all (most CI runners), skip login entirely and export VARSAFE_TOKEN instead; see CI/CD usage.

The browser flow expires after a few minutes if it is not completed — if it times out or the confirmation code expires, rerun the login with one of the options above.

OS secure storage unavailable

varsafe login stores its credential in the operating system keychain. When that fails, the built-in diagnostic tells you which half is broken:

varsafe doctor

It checks two things independently — that the native keyring addon loads, and that a real write/read round-trip through the OS keychain succeeds — and prints which one failed. Add --json for machine-readable output.

The usual cause on a server or CI image is a temp directory mounted noexec. The CLI is a single-file binary that extracts its native keyring addon to $TMPDIR and loads it from there; if that directory forbids execution, the addon cannot load and the message appears even though the keychain itself is fine. The CLI detects this and re-executes itself once with a usable temp directory, so it normally self-heals. If it does not, point TMPDIR at an exec-able directory you own:

TMPDIR="$HOME/.cache/varsafe-tmp" varsafe doctor --addon-only

On a CI runner there is usually no keychain at all, and that is expected — do not run varsafe login there. Export VARSAFE_TOKEN instead and skip login entirely; see CI/CD usage.

Not signed in

Not signed in
Run `varsafe login` to authenticate, or set VARSAFE_TOKEN env var for CI/CD

No stored session and no token in the environment. Run varsafe login, or export VARSAFE_API_TOKEN (alias: VARSAFE_TOKEN) — every command picks the env var up without a login step.

varsafe list shows nothing, or the wrong secrets

You are looking at a different project or environment than you think. The CLI resolves context in this order: command flags (-p/-i/-e) → local .varsafe file → global saved context. Check and fix:

# Show the current saved context
varsafe use

# Re-select interactively
varsafe use -p my-api -e development

# Or bypass context entirely for one command
varsafe list -p my-api -e development

If a teammate deleted the project or environment your context references, commands fail with:

Project not found. It may have been deleted. Run `varsafe use` to update.

My app received the literal text ${DB_PASS}

The secret is a literal whose value happens to contain ${DB_PASS}, not a composed secret. varsafe never expands ${...} in an ordinary value — see Values are stored, not interpreted.

Check which it is:

varsafe list --reveal

The Kind column reads composed or literal. To convert a literal into a composition, rewrite it:

varsafe set DATABASE_URL --template 'postgres://${DB_PASS}@db.internal/app'

If the secret already reads composed and your process still got ${...}, something in your pipeline is asking for the source representation — check for a stray --source on the read.

cannot resolve secrets: ... does not exist in this environment

A composed secret references a key that is missing from this environment. Resolution is all-or-nothing, so the whole read fails rather than returning a partial value — the message names the composed secret and the reference it could not satisfy.

Usually this means the composition was promoted to an environment that does not have its inputs yet. Create the missing key, or check you are pointed at the right environment:

varsafe status
varsafe list

Other secrets are composed from this one

You are deleting a secret something else points at, and varsafe will not guess what should happen to the dependents. Say which:

varsafe unset DB_PASS --cascade   # delete the dependents too
varsafe unset DB_PASS --flatten   # keep them working by copying this value into each

If you are deleting the secret because it leaked, use --cascade. --flatten would copy the compromised value into every dependent, where it becomes an ordinary literal nobody will think to rotate. See Composed Secrets.

Project is required in scripts and CI

Project is required. Use -p <project> or -i <project-id> to specify.

In a non-interactive session (CI, cron, piped output) the CLI cannot prompt, so the project and environment must come from flags or a saved context. Pass -i with the project ID for rename-proof automation — see CI/CD Pipelines.

Installer refuses your platform

Unsupported architecture: <arch>

The installer maps x86_64/amd64 to x64 and arm64/aarch64 to arm64, on Linux and macOS, and picks a musl build when it finds a musl loader. Anything else — 32-bit ARM on an older Raspberry Pi OS, i686, s390x — has no published artifact and stops here.

Linux arm64 is supported, on both glibc and musl. If you are on Graviton, a modern Raspberry Pi, or an ARM CI runner and hit this, re-fetch install.sh — an installer predating the arm64 builds refuses the platform outright. The installer prints the artifact it settled on, for example Detected platform: linux-arm64-musl, which is the fastest way to confirm it identified your machine correctly.

Alpine or musl: “Error loading shared library libstdc++.so.6”

Error loading shared library libstdc++.so.6: No such file or directory

The musl builds link libstdc++.so.6 and libgcc_s.so.1 dynamically, and stock Alpine ships neither. Without them the binary dies inside the dynamic loader, so what you get is dozens of mangled C++ symbol names and nothing identifying varsafe as the cause. The dependency comes from Bun’s own musl runtime, so it cannot be removed on our side.

You should not normally see this from a current installer. Running as root it installs both libraries for you; otherwise it stops and prints the command rather than leaving you with a binary that cannot start:

apk add --no-cache libstdc++ libgcc

If you are not root and would rather the installer handle it, re-run with VARSAFE_INSTALL_PREREQS=1 and it will use sudo. It does not do that on its own — a script piped from the internet should not change your system packages just because sudo happens to be available.

The installer also runs the downloaded binary once before putting it on your PATH, so an install that would fail on first run fails during installation instead, with the reason. If you are seeing this error at all, you likely installed with an older install.sh — re-fetch it.

Pointing the CLI at a self-hosted API

By default the CLI talks to https://api.varsafe.dev. To target a self-hosted or staging instance, either:

# Per-invocation or CI: env var takes priority over everything
VARSAFE_API_URL=https://varsafe.internal.example.com varsafe whoami

# Or persist it at login time
varsafe login --api-url https://varsafe.internal.example.com

If commands fail with Cannot connect to server, verify the URL is reachable from the machine (curl the API host) — the CLI falls back to cached project/environment data where it can, but secret fetches need a live connection.

Rate limits

The API rate-limits requests and answers 429 with a Retry-After header when a client exceeds its budget. If a script hits limits, space out calls or batch work — one varsafe run per deploy instead of a varsafe get per variable.

Where to see what happened

Every secret read, write, export, and login is recorded in the append-only audit trail: dashboard → Audit Log. Filter by actor, action, or date to reconstruct exactly what a token, teammate, or AI agent did — useful for confirming whether a “missing” secret was deleted, renamed, or never existed. See the Dashboard Guide.

Still stuck?

  • VARSAFE_DEBUG=1 makes the CLI write diagnostic details to its debug log
  • Check the exact error message against this page’s headings — the CLI prints stable, grep-able messages
  • Contact support with the command, the full error output, and your CLI version (varsafe --version)