[settings-naming] cli: normalize env vars — TRACEBLOC_ENV alias + TRACEBLOC_ prefix - #657
Conversation
…0076 Normalize this repo's owned stage-selecting env var toward the RFC-0076 canon (backend#3391), alias-first — read new-or-old, never break an existing deployment. TRACEBLOC_ENV is now the canonical name; the legacy CLIENT_ENV is read as a fallback (remove_by: 2026-12-31). - api.ResolveEnv: --env flag, then $TRACEBLOC_ENV, then legacy $CLIENT_ENV, then prod. Alias precedence lives in one place (stageFromEnv). - doctor: the cluster's stage, read off the jobs-manager Deployment spec, is now read alias-first (stageFromClusterSpec) — a consumer-side alias so the edge chart can adopt the canonical key on its own S3-edge timeline. - auth login / status --check help + the unknown-env error name the canonical var (legacy noted); goldens regenerated. - env-resolution guard registers TRACEBLOC_ENV as a needle so a new read of either name lands in the allowlist; TestMain clears both stage vars so the higher-precedence canonical name can't make CLIENT_ENV-only isolation flaky. - VERSION 0.10.24 -> 0.10.25 (version-bump-gate: env reads are packaged paths). The other RFC-0076 config keys (registry REGISTRY_URL, telemetry, boolean gates) have no owned, unprefixed occurrence here: TRACEBLOC_ALLOW_UNVERIFIED already carries the prefix and no REGISTRY_URL/SKIP_TELEMETRY var exists. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…cli#651) version-bump-gate failed on the previous head: VERSION still read 0.10.25, v0.10.25 is already released, and this PR changes a published file (internal/cli/auth.go, matching `internal/*`). The release train cuts the tag from this file and never bumps it, so leaving it stale doesn't fail here — it fails the next prod hop, days later, on somebody else (backend#1561). 0.10.26 is free: v0.10.25 is the highest released final version and no v0.10.26 tag exists. Patch, not minor — this ships one bug fix, and the new `login --force` flag is additive with no change to any existing invocation. (Open PR #657 also touches VERSION, but bumps 0.10.24 -> 0.10.25, which is already released — its gate is red for the same reason and it needs a rebase. It does not claim 0.10.26.) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…develop merge (cli#656) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
saadqbal
left a comment
There was a problem hiding this comment.
Nice, careful PR. Alias-first is the right shape, and the tests are unusually good — I mutated both resolvers locally and every break got caught by name: dropping the CLIENT_ENV fallback fails TestResolveEnv and TestSessionEnvFallsBackToClientEnvOnlyWhenUnset, reversing precedence fails TestResolveEnvStageAlias and TestStageFromClusterSpec, and renaming api.StageEnvVar trips the doctor drift assertion. I also ran the suites with TRACEBLOC_ENV and CLIENT_ENV set to different values rather than the same one — the asymmetric case is the actual hazard the new TestMain guards, and it holds. Back-compat here is genuinely enforced, not just asserted.
One thing worth a follow-up push: the prose sweep stopped at the user-facing surface. auth.go:475 still says the target env is resolved "exactly like login (--env, then $CLIENT_ENV, then prod)" — you changed that chain and updated the flag help eleven lines below it in the same file. Same for the docstrings on the untouched readers that route through ResolveEnv: client.go:149/156/159, telemetry.go:105-107/119/147-149, and cli/doctor.go:117. I checked directly — sessionEnv with TRACEBLOC_ENV=dev and CLIENT_ENV unset returns dev, so those comments now describe a chain that isn't the one in the code. Nothing behavioural, but it's the exact class this repo keeps getting bitten by.
No markdown is affected — I grepped the whole tree and no README, CLAUDE.md, BUGBOT or runbook names either var, so your task-4 note is accurate. Nothing leaks on the public side either.
Minor: the body says the VERSION bump is 0.10.24 → 0.10.25; the diff is 0.10.25 → 0.10.26. Body only, probably a rebase.
Picks up #657 (TRACEBLOC_ENV alias + TRACEBLOC_ prefix), which landed on develop after this branch opened. Two conflicts, both from #657 rewording the same `login` surface this branch extends, and both resolved by taking BOTH sides: - internal/cli/auth.go — `--env`'s help string gains #657's "$TRACEBLOC_ENV, then legacy $CLIENT_ENV" wording; this branch's `--force` registration is kept beside it. - 07-login.golden — regenerated rather than hand-merged, so it reflects the real `--help` output of the merged command. VERSION did not conflict: #657 bumped 0.10.24 -> 0.10.25, this branch 0.10.25 -> 0.10.26, so the merged file keeps 0.10.26 — still above every released tag (v0.10.25 is the highest), which is what version-bump-gate requires. No behaviour change beyond the two sides combined. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… flow (#651) (#658) * fix(login): reuse a valid session instead of always starting a device flow (cli#651) `tracebloc login` went straight to a device code even when the machine already held a valid session for the target env. On a headless host that is a dead end rather than an inconvenience: the credentials are on disk, but the command insists on a browser approval it has no way to complete — turning "re-run login to be safe" into a hard stop for any script or runbook. login now checks the session it already has before asking for one: - A session for the target env that the backend accepts ends the command at exit 0 with "Already signed in as <account>". The session is confirmed with a live WhoAmI rather than trusted off disk — a revoked token is still a token on disk. - `--force` is the opt-out (switching accounts, replacing a session believed stale) and skips the short-circuit entirely, including the probe. Same sense as `delete --force`: proceed despite the state that would otherwise stop you. - Every fall-through says WHY first, and only claims what it can tell apart: a LOCAL expires_at that has passed is named to the second (and is not presented to the backend), a 401/403 is reported as rejected, and anything else — DNS, a 5xx — is "couldn't check", not a verdict on the session. A 426 surfaces the upgrade instruction and starts no flow, since a fresh flow would hit the same version floor. "A session for this env" is resolved two ways, so credentials already on disk aren't stranded: the current session when sessionEnv resolves it to the target (the same predicate `auth status --check` uses, so login and the installer's probe cannot disagree), else that env's own profile — a machine on prod can hold a live dev token, and `login --env dev` adopts it and switches current_env. The profile is written back under the key it was found under, so a v1-migrated `"Dev"` config doesn't gain a second, lower-cased profile beside the real one. Tests cover each arm by asserting whether /device/code was requested at all — the browser demand is the behaviour that matters. Goldens regenerated for the new --force flag and copy. Closes #651 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * chore(release): bump VERSION to 0.10.26 for the login short-circuit (cli#651) version-bump-gate failed on the previous head: VERSION still read 0.10.25, v0.10.25 is already released, and this PR changes a published file (internal/cli/auth.go, matching `internal/*`). The release train cuts the tag from this file and never bumps it, so leaving it stale doesn't fail here — it fails the next prod hop, days later, on somebody else (backend#1561). 0.10.26 is free: v0.10.25 is the highest released final version and no v0.10.26 tag exists. Patch, not minor — this ships one bug fix, and the new `login --force` flag is additive with no change to any existing invocation. (Open PR #657 also touches VERSION, but bumps 0.10.24 -> 0.10.25, which is already released — its gate is red for the same reason and it needs a rebase. It does not claim 0.10.26.) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(login): find raw-keyed profiles, and exit 130 on a cancelled probe (cli#651) Two Bugbot findings on PR #658, both real. 1. storedSessionFor's second arm indexed cfg.Profiles with the ALREADY NORMALISED target env, so a live token written under a raw key such as `"Dev"` (config.migrateV1 stores a v1 `env` verbatim) went unseen the moment that profile stopped being the current one. `login --env dev` then ran a device flow and saved a SECOND profile under `"dev"`, stranding a perfectly good session beside it — on exactly the headless host this issue is about. The existing raw-key test could not see it: it keeps `"Dev"` CURRENT, which arm 1 catches before the map lookup is reached. The gap only opens after a `login --env` elsewhere has moved current_env. Lookup now folds the map's own KEYS (new profileKeyed). Exact match wins; the fold is a tie-break scanned in sorted order, so a config holding both `"Dev"` and `"dev"` cannot answer differently run to run on Go's randomised map iteration. The trim+lower-case is extracted from sessionEnv as normalizeEnv and shared, rather than hand-rolled a second time — a second copy is how the keys stop matching in the first place. 2. A cancelled context surfaces on the WhoAmI call as a plain error, so Ctrl-C during the new probe landed in the "couldn't check" arm: it printed "signing in again" and then failed RequestDeviceCode with exit 1, where every other interrupt in login exits 130 silently. Guarded on ctx.Err() before the classification — the same guard, for the same reason, as pollForToken's. Tests: the not-current raw-key case (asserts no flow, no duplicate profile, current_env set to the FOUND key), a determinism test running profileKeyed 50x over a config with three case variants, and a cancelled probe asserting exit 130, silence, and zero device codes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor(login): one shared WhoAmI classifier for both session probes (cli#651) Review on PR #658: reuseStoredSession's 426 / 401-403 / everything-else classification of a WhoAmI failure duplicated the identical block in runAuthCheck a few dozen lines down. The duplication is one this PR introduced, so it is fixed here rather than left for a follow-up — "fix the class, not the instance". classifyWhoAmIError returns a named whoAmIVerdict (whoAmIUnverified / whoAmIRejected / whoAmIUpgradeRequired) plus the *api.UpgradeRequiredError, so the caller surfaces the server's own version floor rather than a paraphrase. The three arms are deliberately not collapsible: only whoAmIRejected is a statement about the credential. A 5xx folded into it would tell someone to re-authenticate during an outage, and a 426 folded into it would send them to a browser step that cannot lift a version floor. The COPY stays at the call sites. The two commands answer different questions — "should I start a device flow?" vs "what is this exit code?" — and say so in different words; only the classification is shared. No user-facing string changed, and the goldens confirm it (regenerated, no diff). Test: a table over 401/403/426/500/404/429, a transport error, a cancelled context, and wrapped 401/426 — wrapped because both call sites receive the error through the api client's own fmt.Errorf wrapping, so matching the concrete type alone would silently demote every real verdict to "unverified". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Normalizes this repo's owned stage-selecting environment variable toward the RFC-0076 canon, alias-first — read new-or-old, never break an existing deployment.
TRACEBLOC_ENVbecomes the canonical name; the legacy name is read as a fallback withremove_by: 2026-12-31.Old → new env-var map
remove_by: 2026-12-31)$CLIENT_ENV(process env)$TRACEBLOC_ENVapi.ResolveEnv— the CLI's own stage read (--envflag still wins over both)CLIENT_ENV(jobs-manager Deployment spec)TRACEBLOC_ENVdoctoregress probe — the cluster's stage, read off the chart-written container env (consumer-side alias)Value semantics are unchanged:
dev/stg/prodresolution is identical, and thestg-vs-stagingvalue spelling is a separate later step per the issue.Scope notes
TRACEBLOC_prefix (B5, task 2) collapse to the single owned unprefixed var here —CLIENT_ENV. Every other owned var already carries the prefix (TRACEBLOC_CONFIG_DIR,TRACEBLOC_LOG_LEVEL,TRACEBLOC_INSTALLER,TRACEBLOC_ALLOW_UNVERIFIED,TRACEBLOC_NO_TELEMETRY, …).REGISTRY_URL/SKIP_TELEMETRY/TRACEBLOC_SKIP_*in this repo, and the one boolean gate (TRACEBLOC_ALLOW_UNVERIFIED) is already B5-compliant. Nothing deferred.CLAUDE.md/README/BUGBOT.md/runbook names the old var. The user-facing surface that named it — thelogin/auth status --check--envhelp and the unknown-env error — now names$TRACEBLOC_ENV(legacy noted); goldens regenerated.MYSQL_*entrypoint contract,AWS_*/AZURE_*/KUBECONFIG, and the terminal/framework standardsNO_COLOR/TERM/COLORTERM/COLORFGBG/CI/DO_NOT_TRACK/HTTP(S)_PROXY.Guard + hermeticity
TestNoNewEnvironmentResolutionSiteAppears) now registersTRACEBLOC_ENVas a needle, so a new read of either name lands in the sanctioned allowlist. Both touched files were already sanctioned sites.CLIENT_ENV, a test that pinned the ambient stage via only one name would be non-hermetic under an ambient value of the other (plausible on settings-naming work). A packageTestMainclears both stage vars at process start — verified: thecli/api/doctorsuites pass withTRACEBLOC_ENV=dev CLIENT_ENV=devin the environment.doctor's literal keys are pinned in lock-step with theapiconsts by a compile-checked test assertion.VERSION bump
Yes —
0.10.24 → 0.10.25. The env reads live underinternal/*, which theversion-bump-gatetreats as a published/packaged path.Closes #656
Part of tracebloc/backend#3391
🤖 Generated with Claude Code
Note
Medium Risk
Changes which backend URL the CLI and doctor probes target when both stage env vars are set differently; backward-compatible if only CLIENT_ENV is set, but mis-set TRACEBLOC_ENV could redirect sessions or egress checks unexpectedly.
Overview
Introduces RFC-0076 stage naming:
$TRACEBLOC_ENVis canonical;$CLIENT_ENVremains a fallback until 2026-12-31. Resolution order is unchanged in spirit —--env, then env vars, then prod — but the env-var step now readsTRACEBLOC_ENVfirst, thenCLIENT_ENV.api.ResolveEnvcentralizes that instageFromEnv()(the only process-env reader for stage in the module).login/auth status --checkhelp and unknown-env errors document the new default chain; goldens updated.doctorbackend egress uses the same alias-first rule for the cluster stage on the jobs-manager Deployment (TRACEBLOC_ENV, else chartCLIENT_ENV), viastageFromClusterSpec.Tests add alias-precedence coverage, a
cliTestMainthat unsets both stage vars for hermetic runs, and extend the env-resolution guard needles toTRACEBLOC_ENV. VERSION → 0.10.26.Reviewed by Cursor Bugbot for commit 6fbbb5a. Bugbot is set up for automated code reviews on this repo. Configure here.