Relationships
#1381 Misleading 'Your collective token lacks the X scope' when scope resolution failed — startup whoami errors are silently swallowed
Opened by magistr · 7/24/2026
Summary
When the CLI cannot determine a collective token's scopes — because the startup whoami to swamp-club.com failed (DNS error, connect failure, timeout, 5xx) — team-feature pre-flights report:
Error: Your collective token lacks the datastore:* scope.The token actually has the scope. The real (network) error is silently discarded, so the user is sent to swamp-club.com/collectives to re-mint a token that was fine all along. On headless/ephemeral environments this is a day-loser: the same token passes on one machine and "lacks the scope" on another, which reads as a token or server-side scope bug, not the network hiccup it is.
Where (source)
Verified at stable 20260724.175725.0-sha.00d0edb5; still present on current main (10943ef).
src/cli/mod.ts:1337-1380— startup scope resolution whenSWAMP_API_KEYis set: try~/.config/swamp/scope_cache.json, else livewhoamiwith a 10sAbortSignal. The entire block is wrapped in a baretry { … } catch { /* Best-effort — don't block CLI startup */ }— any thrown error (DNS, TCP, timeout, HTTP 5xx viaUserError) is swallowed with no logging, leavingauthScopesundefined. Two more silent paths: a 401 makeswhoami()return{ authenticated: false }sosetAuthScopesis never called, and a 200 without ascopesfield setssetAuthScopes(undefined).src/cli/auth_context.ts:85-98—requireScope(scope)throwsmissing_scope("Your collective token lacks the X scope") whenevercollectiveToken === true && authScopes === undefined.- Call sites are pure client-side pre-flights on that in-memory state:
datastore_setup.ts:235,vault_create.ts:87(via the shared helper),serve.ts:962.
Net effect: four unrelated failure modes — network unreachable/timeout, HTTP 5xx, 401, and a 200 missing scopes — all render as the same "lacks the scope" message. Only "200 with scopes that don't include X" genuinely means what the message says.
Deterministic repro
export SWAMP_API_KEY=[REDACTED-SECRET-1] # valid collective token WITH datastore:* scope
export HOME="$(mktemp -d)" # fresh: no auth.json, no scope_cache.json
export SWAMP_CLUB_URL=https://[IP-1]:9 # unroutable -> startup whoami fails
swamp datastore setup extension @webframp/postgres-datastore
# -> Error: Your collective token lacks the datastore:* scope.Unset SWAMP_CLUB_URL (reachable swamp-club.com), same token, same command: passes the pre-flight. Any transient real-world network failure during the 10s window reproduces the same false message — no env override needed.
How we hit it
Headless Firecracker microVM guests authenticating with SWAMP_API_KEY only. Every boot starts with a fresh HOME → empty scope cache → mandatory live whoami through a NAT'd guest network that is occasionally flaky. Result: the token is "rejected" in the guest while the identical bytes pass on the (network-healthy) host — and after the host's first success, scope_cache.json makes host runs zero-network, deepening the false impression that the token itself differs per machine. We rotated/re-minted perfectly good tokens before reading the source.
Expected
- Distinguish "could not verify scopes" from "verified and missing": e.g.
Could not verify your collective token's scopes (whoami: <underlying error>). Retry, or check connectivity to swamp-club.com.with its own error code (scope_unverified), keepingmissing_scopefor the genuine case. - Report a 401 as an authentication failure, not a missing scope.
- Log the swallowed whoami error (warn/debug) instead of discarding it in the bare
catch. - Optionally: retry the whoami once at
requireScopetime when scopes are unresolved, since a single 10s startup attempt is the only shot per invocation.
Context
The scope gate is new: shipped in swamp PR #1934 and hot-fixed the same evening by PR #1936 — whose commit message documents three earlier causes of this exact false "lacks scope" symptom (username guard, --unstable-bundle state duplication, auth.json clobber). This is a fourth, environmental one; the pre-flight seems like a prime candidate for the E2E UAT tracked in Lab #1375.
Workaround we use meanwhile: pre-seed ~/.config/swamp/scope_cache.json with {"fingerprint": "<first 12 chars of the token>", "scopes": ["datastore:*", "vault:*"]} so the pre-flight passes without a live whoami.
Environment
- swamp stable
20260724.175725.0-sha.00d0edb5(also inspectedmain@10943ef) - Linux x86_64 (microVM guest, fresh HOME per boot) and macOS 14 host
- Auth:
SWAMP_API_KEYcollective token (swamp_org_…) minted viaswamp auth token create --collective … --scopes datastore:*,vault:*
Closed
No activity in this phase yet.