Skip to content

[settings-naming] cli: normalize env vars — TRACEBLOC_ENV alias + TRACEBLOC_ prefix - #657

Merged
aptracebloc merged 3 commits into
developfrom
settings-naming/env-normalize
Sep 11, 2026
Merged

[settings-naming] cli: normalize env vars — TRACEBLOC_ENV alias + TRACEBLOC_ prefix#657
aptracebloc merged 3 commits into
developfrom
settings-naming/env-normalize

Conversation

@aptracebloc

@aptracebloc aptracebloc commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

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_ENV becomes the canonical name; the legacy name is read as a fallback with remove_by: 2026-12-31.

Old → new env-var map

legacy (still read, remove_by: 2026-12-31) canonical (preferred) where
$CLIENT_ENV (process env) $TRACEBLOC_ENV api.ResolveEnv — the CLI's own stage read (--env flag still wins over both)
CLIENT_ENV (jobs-manager Deployment spec) TRACEBLOC_ENV doctor egress probe — the cluster's stage, read off the chart-written container env (consumer-side alias)

Value semantics are unchanged: dev/stg/prod resolution is identical, and the stg-vs-staging value spelling is a separate later step per the issue.

Scope notes

  • Stage var (task 1) + 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, …).
  • Config keys (task 3): none present to rename — there is no REGISTRY_URL/SKIP_TELEMETRY/TRACEBLOC_SKIP_* in this repo, and the one boolean gate (TRACEBLOC_ALLOW_UNVERIFIED) is already B5-compliant. Nothing deferred.
  • Docs (task 4): no CLAUDE.md/README/BUGBOT.md/runbook names the old var. The user-facing surface that named it — the login / auth status --check --env help and the unknown-env error — now names $TRACEBLOC_ENV (legacy noted); goldens regenerated.
  • Left as third-party (unchanged): MYSQL_* entrypoint contract, AWS_*/AZURE_*/KUBECONFIG, and the terminal/framework standards NO_COLOR/TERM/COLORTERM/COLORFGBG/CI/DO_NOT_TRACK/HTTP(S)_PROXY.

Guard + hermeticity

  • The env-resolution meta-guard (TestNoNewEnvironmentResolutionSiteAppears) now registers TRACEBLOC_ENV as a needle, so a new read of either name lands in the sanctioned allowlist. Both touched files were already sanctioned sites.
  • Because the canonical name now out-ranks 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 package TestMain clears both stage vars at process start — verified: the cli/api/doctor suites pass with TRACEBLOC_ENV=dev CLIENT_ENV=dev in the environment.
  • doctor's literal keys are pinned in lock-step with the api consts by a compile-checked test assertion.

VERSION bump

Yes — 0.10.24 → 0.10.25. The env reads live under internal/*, which the version-bump-gate treats 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_ENV is canonical; $CLIENT_ENV remains a fallback until 2026-12-31. Resolution order is unchanged in spirit — --env, then env vars, then prod — but the env-var step now reads TRACEBLOC_ENV first, then CLIENT_ENV.

api.ResolveEnv centralizes that in stageFromEnv() (the only process-env reader for stage in the module). login / auth status --check help and unknown-env errors document the new default chain; goldens updated.

doctor backend egress uses the same alias-first rule for the cluster stage on the jobs-manager Deployment (TRACEBLOC_ENV, else chart CLIENT_ENV), via stageFromClusterSpec.

Tests add alias-precedence coverage, a cli TestMain that unsets both stage vars for hermetic runs, and extend the env-resolution guard needles to TRACEBLOC_ENV. VERSION0.10.26.

Reviewed by Cursor Bugbot for commit 6fbbb5a. Bugbot is set up for automated code reviews on this repo. Configure here.

…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>
@aptracebloc aptracebloc self-assigned this Sep 11, 2026
saadqbal added a commit that referenced this pull request Sep 11, 2026
…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>
aptracebloc and others added 2 commits September 11, 2026 10:35
…develop merge (cli#656)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@saadqbal saadqbal left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@aptracebloc
aptracebloc merged commit 2d616f0 into develop Sep 11, 2026
30 checks passed
@aptracebloc
aptracebloc deleted the settings-naming/env-normalize branch September 11, 2026 09:35
saadqbal added a commit that referenced this pull request Sep 11, 2026
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>
saadqbal added a commit that referenced this pull request Sep 11, 2026
… 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[settings-naming] cli: normalize env vars — TRACEBLOC_ENV alias + TRACEBLOC_ prefix

3 participants