Summary
pat.json stores exactly one PAT with no record of which host issued it,
so logging into a second environment overwrites the first. Switching
between production and staging means re-running es auth login every
time, in both directions.
default_project_id in config.json has the same problem and is easier
to miss: it survives the switch and then points at a project that does
not exist on the other host, so an unrelated-looking command fails.
Reproducing
es auth login # production
export EVERSCRIBE_API_URL_OVERRIDE=https://api-staging.everscribe.io
export EVERSCRIBE_MANIFESTS_URL_OVERRIDE=https://staging.everscribe.io
es auth login # staging; overwrites the production PAT
unset EVERSCRIBE_API_URL_OVERRIDE
es projects list # production, now carrying a staging token
Why it happens
config.PAT (internal/config/config.go:15) is a flat struct with no
host field, and config.Save rewrites the whole file. config.Load has
no way to tell whether the token it returns belongs to the host the
client is about to call.
Separately, CLIConfig.DefaultProjectID
(internal/config/cli_config.go:16) is a single value shared across
every host.
Current state, for context
Pointing the CLI at a non-production host works today, via two
undocumented environment variables:
EVERSCRIBE_API_URL_OVERRIDE (internal/client/client.go:24)
EVERSCRIBE_MANIFESTS_URL_OVERRIDE
(internal/cmds/skills/fetcher.go:21)
Two hosts are involved because the API and the manifests CDN are
different services: api-staging.everscribe.io and
staging.everscribe.io.
The SDKs have the same capability with a different mechanism: all four
default to https://api.everscribe.io and accept a base URL
programmatically (WithBaseURL, baseUrl, base_url), with no
environment variable. That inconsistency is out of scope here but worth
knowing about.
Options considered
Discussed and deliberately not chosen yet, recorded so the next person
does not redo the analysis.
Stamp the issuing host on the PAT. One field plus a check; the
client refuses a token issued by a different host and says so. Smallest
fix, and it catches the environment-variable path too. Does not solve
default_project_id.
Key the whole config by host. api_url selects a block containing
the token, the default project, and anything added later. Solves both
halves with one concept. Requires merging the two files and a migration,
and cannot hold two identities on the same host.
Named profiles. es config use staging. Handles two accounts on one
host. Same migration cost, and introduces a profile concept that can
disagree with the host setting.
Separate config directories selected by a --profile flag, with the
existing files as the default profile. Appears to be the cheapest: the
file formats do not change, config.Load() keeps its signature, and the
change is confined to the two path-building functions
(config.Path, config.ConfigPath) plus a persistent flag. No
migration, since existing installs already are the default profile. The
cost is that switching becomes stateful and forgettable, which argues
for es config current printing the active profile and host.
Deciding factor
Whether two identities on the same host is ever needed. If not, keying
by host is conceptually tidier; if so, profiles or separate directories
are required.
Workaround
None needed for one-shot use: export the two variables, work in that
shell, and re-authenticate when switching back. The friction is only felt
when switching often.
Summary
pat.jsonstores exactly one PAT with no record of which host issued it,so logging into a second environment overwrites the first. Switching
between production and staging means re-running
es auth logineverytime, in both directions.
default_project_idinconfig.jsonhas the same problem and is easierto miss: it survives the switch and then points at a project that does
not exist on the other host, so an unrelated-looking command fails.
Reproducing
Why it happens
config.PAT(internal/config/config.go:15) is a flat struct with nohost field, and
config.Saverewrites the whole file.config.Loadhasno way to tell whether the token it returns belongs to the host the
client is about to call.
Separately,
CLIConfig.DefaultProjectID(
internal/config/cli_config.go:16) is a single value shared acrossevery host.
Current state, for context
Pointing the CLI at a non-production host works today, via two
undocumented environment variables:
EVERSCRIBE_API_URL_OVERRIDE(internal/client/client.go:24)EVERSCRIBE_MANIFESTS_URL_OVERRIDE(
internal/cmds/skills/fetcher.go:21)Two hosts are involved because the API and the manifests CDN are
different services:
api-staging.everscribe.ioandstaging.everscribe.io.The SDKs have the same capability with a different mechanism: all four
default to
https://api.everscribe.ioand accept a base URLprogrammatically (
WithBaseURL,baseUrl,base_url), with noenvironment variable. That inconsistency is out of scope here but worth
knowing about.
Options considered
Discussed and deliberately not chosen yet, recorded so the next person
does not redo the analysis.
Stamp the issuing host on the PAT. One field plus a check; the
client refuses a token issued by a different host and says so. Smallest
fix, and it catches the environment-variable path too. Does not solve
default_project_id.Key the whole config by host.
api_urlselects a block containingthe token, the default project, and anything added later. Solves both
halves with one concept. Requires merging the two files and a migration,
and cannot hold two identities on the same host.
Named profiles.
es config use staging. Handles two accounts on onehost. Same migration cost, and introduces a profile concept that can
disagree with the host setting.
Separate config directories selected by a
--profileflag, with theexisting files as the default profile. Appears to be the cheapest: the
file formats do not change,
config.Load()keeps its signature, and thechange is confined to the two path-building functions
(
config.Path,config.ConfigPath) plus a persistent flag. Nomigration, since existing installs already are the default profile. The
cost is that switching becomes stateful and forgettable, which argues
for
es config currentprinting the active profile and host.Deciding factor
Whether two identities on the same host is ever needed. If not, keying
by host is conceptually tidier; if so, profiles or separate directories
are required.
Workaround
None needed for one-shot use: export the two variables, work in that
shell, and re-authenticate when switching back. The friction is only felt
when switching often.