Skip to content

feat(cli): let multi-account users act on another account per command [RED-1122] [ship] - #1528

Merged
sorccu merged 16 commits into
mainfrom
san-antonio/checkly-slack-review
Oct 13, 2026
Merged

sorccu merged 16 commits into
mainfrom
san-antonio/checkly-slack-review

Conversation

@hlenke

@hlenke hlenke commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Problem

RED-1122: after a device-flow login (#1484), a user with several accounts gets select_account from every command, including whoami. The only way out the CLI suggests is login --account-id, which stores a default, so an agent doing cross-account work (reproducing in one account, looking up a CI test session in another) switched the default four times. A cross-account lookup just said "No such test session." Separately, an agent had to wait for the user to say "done" after relaying the login code.

The stored login key belongs to the user, so CHECKLY_ACCOUNT_ID=<id> can pick any of their accounts. On main, though, only the stored-login path knew this; everywhere else (login, the inline login, error messages) a CHECKLY_ACCOUNT_ID was treated as half of an API-key pair.

Changes

Accounts and credentials

  • Only CHECKLY_API_KEY counts as environment credentials (config.getCredentialSource(): login | account_override | environment). A CHECKLY_ACCOUNT_ID alone picks the account for the user's login, for every command while it is set. The CLI can't tell a one-command prefix from an export or a .env entry.
    • Without a stored login, an authenticated command starts the device login and then uses that account, storing no default. CI keeps an error, which now names both options.
    • login no longer refuses. Without --account-id it finishes without asking for an account, since the variable already names it.
    • An id the login doesn't have fails with the list of accounts it does have.
    • login, switch and whoami say when the variable takes precedence over the stored default. login and switch reason about the stored login, and activateAccount checks the account it activates.
    • switch still asks for a default after an inline login under the variable, which stores none.
    • --help describes CHECKLY_ACCOUNT_ID on its own.
  • select_account offers a per-command choice, next to making the account the default (login --account-id <id>). An inline login passes in its own command line.
  • switch without --account-id no longer draws a menu nobody can answer (agent mode, CI, no terminal). An agent gets select_account with the stored defaultAccount; CI and terminal-less runs get an error listing the accounts.
  • whoami
    • Shows the default account and the user's other accounts, and says where the account comes from.
    • --output json reports user, account, accountSource, defaultAccount and otherAccounts. defaultAccount is null for API key credentials.

Login

  • checkly login --wait waits for the approval of the code an earlier run printed, so the user doesn't have to say "done". login_required.next points to it.

    • If the code expires while answers still come from a proxy or captive portal, the login reports network_error instead of a bare expired_token. pollForTokens decides this, so it applies to every login that polls, and a code that simply went unapproved after such answers cleared stays expired_token.
    • If another run used the same code, it waits briefly for that run's key.
    • It only forgets the code it polled.
  • Agent success lines carry defaultAccount (the stored default, or null): a login under CHECKLY_ACCOUNT_ID returns logged_in without storing one. The "account no longer available" notice stays in its message.

Not found

  • Not-found errors name the account searched and say how to look in another, when the key comes from a login. With --output json, they also carry hint and searchedAccount: {id, name} fields.
  • BaseCommand.withNotFoundHint() covers commands with their own not-found message (checks delete, rca run).

Docs

  • Skill + initialize reference: --wait, a "Working with several accounts" section (listing, one-command use, CHECKLY_ACCOUNT_ID in the shell or .env, changing the default, not-found lookups), and a note that a name the user gave counts as their choice if it matches exactly one account. skills/checkly/SKILL.md is synced.

Deliberately left out

  • Global --account-id flag: the name collides with account usage series --account-id (a filter) and with the persisting flag on login/switch.
  • Remembering the account across re-logins: logout clears the config, and a fresh machine has nothing to remember. A re-login under CHECKLY_ACCOUNT_ID after the stored login became unusable stores no default either; an agent login spans two runs, and the first has already dropped the old one.
  • Cross-account lookup by ID: a follow-up. Agents probe accounts one at a time today.
  • Public docs (checkly-login, checkly-whoami, checkly-switch, authentication): docs(cli): document multi-account use, login --wait and whoami --output json [RED-1122] docs#566, to merge once this ships.

How this was tested

Automated

  • Unit:
    • Credential sources (config.getCredentialSource).
    • validateAuthentication by source, including the account list for a wrong CHECKLY_ACCOUNT_ID.
    • login: --wait approval, network failure, both races and the key-wait timeout; the inline and explicit login under CHECKLY_ACCOUNT_ID; refusal only for API keys; the override notices.
    • switch: agent, CI and terminal-less paths; the stored default under the override; the warning; choosing a default after an inline login under the override.
    • pollForTokens: network_error only while unexpected answers continue at expiry.
    • whoami: text and JSON for all sources.
    • AuthCommand.init in CI with only CHECKLY_ACCOUNT_ID.
    • AuthCommand.catch and CommandStyle.longError hints in text and JSON, checks delete, rca run.
  • e2e (login-device-flow.spec.ts, fake Auth0 + API, 9 cases):
    • login --wait.
    • CHECKLY_ACCOUNT_ID per command.
    • A project .env pinning the account on a fresh machine.
    • The inline login under the variable.
    • A wrong id listing the accounts.
    • CI without a key.
  • Local: pnpm test (4262 passed), pnpm lint.

Scenario matrix

12 scripted cases were run against both a main build and this PR, using a fake API. A login key works with all of the user's accounts; an API key works with one.

Case main PR
Not logged in, .env pins an account "CHECKLY_API_KEY is not set" Starts the login; then uses that account
CI, CHECKLY_ACCOUNT_ID only "CHECKLY_API_KEY is not set" Names both: log in, or add CHECKLY_API_KEY
Logged in, CHECKLY_ACCOUNT_ID=x checkly login Refuses (env_credentials) already_logged_in plus a note that the variable wins
Logged in, mistyped CHECKLY_ACCOUNT_ID NotFoundError: Account not found Lists the accounts the login works with
switch (agent mode or CI) Draws an arrow-key menu, exits 0 Agent: select_account with defaultAccount; CI: error listing the accounts
switch --account-id under CHECKLY_ACCOUNT_ID Switches silently Switches and warns that the variable takes precedence
Session in another account, --output json {"error","detail"} Adds hint and searchedAccount
API key credentials login refuses Unchanged

Blind agent benchmark

Each fresh Claude agent got a realistic user request, a sandbox shell, the skill of the build under test, and a fake Auth0 + Checkly API. The fake "user" approves a login 20 s after the code is issued and never replies. Each run had its own server, port and HOME, and both builds were installed from packed tarballs outside any git repo. Every run was graded by a script against the fake API's request log and the CLI's saved config, plus a check that the project's .env was left unchanged.

Scenario Build Pass Avg commands Avg time
U1: fresh machine, project .env pins the account: "three most recent sessions for this project" main 5/5 9.2 64 s
PR 5/5 6.2 36 s
U2: .env pins account A, default is B: "make Staging my default, then show its newest sessions" main 5/5 15.2 111 s
PR 5/5 5.8 44 s
U3: wrong account id given: "look up ts-ci-123 in acc-e2e-old" main 5/5 15.0 81 s
PR 5/5 5.6 28 s
U4: API key credentials (regression) main 3/3 4.7 11 s
PR 3/3 5.0 16 s
Earlier scenarios (cross-account lookup, fresh login, slow approver, change default; regression) PR 8/8 — —

On main the agents got there through workarounds:

  • U1: they blanked CHECKLY_ACCOUNT_ID or ran login outside the project to get past "CHECKLY_API_KEY is not set", waited with sleep, and 2 of 5 stored a default as a side effect.
  • U2: they hit the menu and read help pages.
  • U3: they guessed account ids.

With the PR, every agent followed the CLI's next entries and messages.

Agent feedback led to the last two commits:

  • 5/5 U1 runs stopped at select_account after login --wait, even though .env already named the account. A rerun of U1 (5/5) after the fix no longer hit it.
  • whoami nudged agents towards storing a default they didn't need.

🤖 Generated with Claude Code

@sorccu sorccu left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, this is a clear improvement for agents working across accounts, and the blind-test write-up is convincing. I ran a multi-lens review (correctness, intent, simplicity) with adversarial verification of the major findings. The inline comments cover the code-level items. The summary:

Major (verified)

  • CHECKLY_ACCOUNT_ID override vs. existing auth branches (see config.ts): only whoami and the not-found hint know about account_override. Error messages in api.ts, the inline-login trigger and login's env_credentials refusal still treat the prefix as API-key env credentials. These paths predate this PR, but the new skill guidance sends agents straight into them.
  • Public docs not updated or tracked: docs/cli/checkly-login.mdx (no --wait), checkly-whoami.mdx (no --output json, default, or other accounts), checkly-switch.mdx (no non-TTY/agent behaviour) and authentication.mdx (presents CHECKLY_ACCOUNT_ID only as half of an API-key pair) all describe the old behaviour. Please add a docs PR or a follow-up ticket and link it here.

Minor: the inline comments cover the default reported under the override in switch/whoami, races in --wait, a missing onUnexpectedAnswers, test gaps, and duplicated hint formatting.

PR description:

  • Please replace the linear.app link with the bare RED-1122 ID. This repo is public, and PR bodies stay public.
  • The test list still mentions config.hasAccountOverride, which the last commit replaced with getCredentialSource.

* credentials from `CHECKLY_API_KEY` / `CHECKLY_ACCOUNT_ID`, whose key
* belongs to one account.
*/
getCredentialSource (): CredentialSource {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Major: account_override is only consulted by whoami and notFoundHint(). Every other auth branch still uses hasEnvVarsConfigured(), so the CHECKLY_ACCOUNT_ID=<id> prefix that the skill now recommends is still handled as "API-key credentials from the environment" there:

  1. rest/api.ts:85 blanks the hint when the id is mistyped or belongs to an account the user isn't in. The agent then gets a bare Forbidden/NotFound instead of Account "x" is not available with the stored login. That is exactly the cross-account case this PR targets.
  2. With no stored key (after logout, or on a fresh machine), authCommand.ts:32 skips the inline login and rest/api.ts:66 reports "CHECKLY_API_KEY is not set". The agent is pointed at API keys instead of the device flow.
  3. login.ts:270 refuses with env_credentials and says to delete the variables, although the override is just a one-command prefix.

All three behaviours already exist on main, but the new guidance makes them much easier to hit. Suggest routing at least the api.ts hint and missing-key logic through getCredentialSource(), or documenting these cases in the skill.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 8e1e0c8 and c3514a2. Only CHECKLY_API_KEY now counts as environment credentials; config.getCredentialSource() (login | account_override | environment) is the single check. A CHECKLY_ACCOUNT_ID alone picks the account for the user's login, for every command while it's set, since the CLI can't tell a prefix from an export or .env.

  1. api.ts: an id the login doesn't have now fails with the accounts it does have: "Account "x" from CHECKLY_ACCOUNT_ID is not available with your login. This login works with: …".
  2. No stored key: an authenticated command starts the inline device login, then uses the variable's account without storing a default. CI keeps an error, now naming both options (log in, or set CHECKLY_API_KEY as well).
  3. login only refuses for CHECKLY_API_KEY; with the variable it finishes without asking for an account. login/switch reason about the stored login and warn that the variable takes precedence over the default they store; whoami says where the account comes from.

Comment thread packages/cli/src/commands/switch.ts Outdated
private reportChoices (accounts: Account[], mode: CliMode): never {
if (mode === 'agent') {
this.log(selectAccountLine({
message: `The default account is "${this.account.name}". Ask the user which account to switch to, then run `

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Under CHECKLY_ACCOUNT_ID=B npx checkly switch, this.account is the override (B), so this line says The default account is "B" and currentAccount is B, while the stored default is still A. An agent may then skip switch --account-id B or report the wrong default to the user. Read the default from config.data.store as whoami does, or word this as the account in use for this command.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 8e1e0c8: the default now comes from config.data.store, and the field is defaultAccount (null when none is stored). Under CHECKLY_ACCOUNT_ID the message also says the variable takes precedence. Covered by "reports the stored default, not the account CHECKLY_ACCOUNT_ID picks for this command" in switch.spec.ts.

Comment thread packages/cli/src/commands/whoami.ts Outdated
account: { id: account.id, name: account.name, plan: account.planDisplayName ?? null, addons: addonNames },
accountSource,
// The account `checkly login` stored; none until one is chosen.
defaultAccount: defaultAccountId

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Text output hides the default when accountSource === 'environment' (env credentials ignore it), but JSON still reports a stale stored defaultAccount. With CHECKLY_API_KEY=k CHECKLY_ACCOUNT_ID=B, the output is accountSource: "environment" alongside defaultAccount: {id: A}, which contradicts the skill's description of defaultAccount. Suggest emitting null in that case, matching the text branch.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 8e1e0c8: defaultAccount is null for accountSource: "environment", matching the text output. Test added in whoami.spec.ts.

): Promise<Credentials | 'stored'> {
let tokens
try {
tokens = await deviceFlow.pollForTokens(pending)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

pollForTokens is called here without onUnexpectedAnswers, unlike the interactive path at L577. If a proxy or captive portal answers the token endpoint, --wait blocks silently for the whole expires_in and then fails with expired_token, giving no hint that the network is the cause. Suggest passing the callback and emitting a JSON progress/network_error line, or stopping early.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 8e1e0c8: --wait now passes onUnexpectedAnswers. It keeps polling, like the interactive path, since a captive portal can clear. If the code expires after such answers, it reports network_error naming the answer and a likely proxy or captive portal, instead of a bare expired_token. Test: "names a proxy or captive portal when the code expires while answers come from something else".

Comment thread packages/cli/src/commands/login.ts Outdated
* used), so it is done either way. Returns 'stored' when another run used
* it and stored its key; rethrows otherwise.
*/
#pendingCodeFailed (error: unknown): 'stored' {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Two races that --wait now makes much more likely, because a poller is held open for minutes:

  • Stale failure: another run (an inline login using #collectPendingAuthorization) consumes the code first and is still exchanging tokens for the API key. This poller's next request gets invalid_grant, config.getApiKey() is still empty, and it prints an error for a login that succeeds a moment later. Consider re-checking for the key over a short bounded window before rethrowing.
  • Deleting a newer code: the deletes here and at L655 are unconditional. If another run replaced the near-expiry code X with a new code Y (the MIN_RELAY_TIME_MS path), this run's eventual expired_token deletes Y, which the user may be approving at that moment. Only delete when the stored deviceCode matches the one polled.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Both fixed in 8e1e0c8:

  • Stale failure: on invalid_grant with no key yet, it waits up to 15 s for the other run's key before failing.
  • Deleting a newer code: the stored code is only forgotten if its deviceCode is the one this run polled (#forgetPendingAuthorization), on every path, including the near-expiry one.

Tests cover both, plus the case where no key turns up in time.

this.style.shortError(`Your Checkly configuration file is not valid.`)
return this.exit(1)
}
const hint = err instanceof NotFoundError ? this.notFoundHint() : undefined

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is the path most commands depend on for the hint (e.g. test-sessions get on the wrong account), but no spec covers it. Removing it would leave the suite green. Suggest a spec that calls catch() with a NotFoundError, both with and without environment credentials.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Added AuthCommand.catch specs in auth-command-login.spec.ts: the hint is added for a login key, and the error is left alone for API key credentials and for errors other than not-found.

} catch (err: any) {
if (err instanceof NotFoundError) {
this.style.shortError(`Check "${args.id}" not found. It may have already been deleted.`)
this.style.shortError([`Check "${args.id}" not found. It may have already been deleted.`, this.notFoundHint()]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This hand-built [msg, this.notFoundHint()].filter(Boolean).join(…) is repeated in rca/run.ts, with a different separator (' ' here, '. ' there) and no test for the rca one. A withNotFoundHint(message) helper on BaseCommand would remove the duplication, and future custom not-found messages would be less likely to forget the hint.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 8e1e0c8: BaseCommand.withNotFoundHint(message) is now used by catch(), checks delete and rca run, with one separator. The new rca-run.spec.ts covers the rca message.

}

longError (title: string, message: string | Error) {
const hint = message instanceof NotFoundError ? this.c.notFoundHint() : undefined

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In JSON mode the hint is English prose appended to detail, so an agent has to parse a sentence to learn which account was searched. A structured field (e.g. searchedAccount: {id, name}, or a separate hint) would be a stable contract. It would also fit the structured choices/next in select_account, and the planned follow-up of listing other accounts.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in 8e1e0c8: JSON errors now carry hint and searchedAccount: {id, name} as their own fields, and detail stays the API message. Both are left out when there's nothing to hint. The skill documents them.

@hlenke

hlenke commented Oct 11, 2026

Copy link
Copy Markdown
Member Author

Thanks for the thorough review, @sorccu. I addressed all items; the replies are on each thread. Summary:

Major

  • CHECKLY_ACCOUNT_ID vs. the other auth branches. Only CHECKLY_API_KEY counts as environment credentials now. A CHECKLY_ACCOUNT_ID alone picks the account for the user's login, on every command while it's set, whether it comes from a prefix, the shell or .env. There are four guardrails:
    • CI keeps an error, now naming both options.
    • login and switch warn that the variable takes precedence over the default they store.
    • whoami says where the account comes from.
    • A login under the variable stores no default.
  • Public docs: docs(cli): document multi-account use, login --wait and whoami --output json [RED-1122] docs#566 updates checkly-login, checkly-whoami, checkly-switch and authentication. It's marked to merge after this ships.

Minor: all fixed. See the threads.

PR description: the linear.app link is replaced with the bare ID, and the test list is current.

Verification: described in the PR description. It covers:

  • a scripted 12-case matrix against main and this PR;
  • 49 blind agent runs on new multi-account use cases (all passed; with this PR, roughly a third of the commands and time, and no workarounds);
  • regressions for the API key and earlier scenarios.

Two follow-ups came from those agent runs: c3514a2 (explicit login under CHECKLY_ACCOUNT_ID no longer stops at select_account) and 8654192 (whoami no longer nudges agents towards an unneeded default).

@sorccu

sorccu commented Oct 13, 2026

Copy link
Copy Markdown
Member

Thanks for the thorough follow-up, @hlenke. Every thread is addressed, and the CHECKLY_ACCOUNT_ID handling now follows one clear rule. I re-reviewed 4d1fb714..34c28c8b (correctness, intent and simplicity lenses). There are no critical or major findings. These minors remain:

  1. switch under CHECKLY_ACCOUNT_ID with no stored login. The inline login picks no default, but the loggedInInline early return in switch.ts prints "Logged in to B" and exits. The default the user asked to set is never chosen, so they have to run switch a second time.
  2. login --wait reports a non-approval as a network problem. unexpectedAnswer is set once and never cleared. A brief captive-portal blip early on therefore turns a code that simply expired unapproved into network_error ("a proxy may be in the way").
  3. The skill says a login success means the account "is now the default". A login under CHECKLY_ACCOUNT_ID returns the same line but stores no default, and the JSON gives an agent no way to tell the two cases apart.
  4. Logging in again under CHECKLY_ACCOUNT_ID drops the previous default. It also drops the "no longer available" notice from the agent-mode message.
  5. Smaller items:
    • No test checks that CI with only CHECKLY_ACCOUNT_ID set keeps failing instead of starting a device login.
    • checkly --help still describes CHECKLY_ACCOUNT_ID only as half of an API-key pair.
    • The initialize reference doesn't mention pinning an account through .env.
    • config.ts reads the stored accountId in three places.
    • The match-then-delete in #forgetPendingAuthorization is best-effort, not atomic. That's fine, but worth a comment.

The branch is also behind main.

I'll take the PR from here: rebase it, make these final changes myself, and update the description when I push.

hlenke and others added 16 commits October 14, 2026 03:52
… [RED-1122]

After a device-flow login, a user with several accounts could only get
past `select_account` by storing a default with `login --account-id`, so
cross-account work kept rewriting it. The stored login key belongs to the
user, and `CHECKLY_ACCOUNT_ID=<id>` already runs one command against any
of their accounts; nothing pointed to it.

- `select_account` offers `CHECKLY_ACCOUNT_ID=<id> <original command>`
  next to `login --account-id <id>`; inline logins pass their command line.
- `checkly login --wait` polls the code an earlier run showed, so an agent
  relays the code and waits instead of asking the user to say when done.
  `login_required.next` points to it.
- Not-found errors from a login key name the account searched and how to
  look in another; `whoami` lists the other accounts and says when
  `CHECKLY_ACCOUNT_ID` picks the account for this command only.
- Skill and initialize reference document both.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… [RED-1122]

A blind agent test of the multi-account flow found two gaps: `whoami`,
the command the skill sends agents to for checking the account, only
printed prose, and nothing said what the default is after a
multi-account login.

`whoami --output json` reports the user, the account, `accountSource`
(`login`, `account_override` or `environment`), the stored
`defaultAccount` (null until chosen) and the other accounts. The skill
says no account is the default until one is chosen.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…terminal [RED-1122]

`checkly switch` without `--account-id` drew its account menu even in
agent and CI mode or without a terminal, where nobody can answer it;
a blind agent test saw it exit having done nothing. It now prints the
`select_account` line `checkly login` uses (plus `currentAccount`) to
an agent and fails with the accounts and `--account-id` elsewhere.

The skill now says an account name or id the user gave that matches
exactly one choice counts as their choice, and points to `switch`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… whoami [RED-1122]

Findings from 40 blind agent runs:
- The skill only mentioned `switch --account-id` inside the post-login
  `select_account` bullet, so agents guessed whether it also changes an
  existing default and whether it needs confirmation. A "Working with
  several accounts" section now covers listing, one-command use,
  changing the default and not-found lookups.
- "Wait with a timeout of at least expires_in" is impossible in shells
  capped below 900s; run `login --wait` in the background and rerun it.
- Plain `whoami` did not say whether the account is the default; it
  now prints a `Default account:` line (none for env credentials).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…1122]

Cleanups from a /simplify pass over the branch:
- `selectAccountLine()` and `formatAccounts()` build the agent line and
  account lists for both `login` and `switch`, so the two copies of the
  `select_account` contract cannot drift (their `when` text already had).
- `config.getCredentialSource()` replaces `hasAccountOverride()` and the
  raw `CHECKLY_API_KEY` checks in `whoami` and the not-found hint.
- One helper handles a refused stored device code for both the one-shot
  and the `--wait` collection; `--wait` is passed instead of stored.
- `switch` detects the CLI mode once; `whoami` fetches the user and the
  accounts in parallel and reads the stored default once.
- `checks delete` and `rca run`, which print their own not-found message,
  add the hint too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…RED-1122]

Review feedback: only whoami and the not-found hint knew that a
`CHECKLY_ACCOUNT_ID` without `CHECKLY_API_KEY` picks an account for the
user's login; every other path treated it as API key credentials. The CLI
cannot tell a one-command prefix from an export or a `.env` entry, so this
now holds for every command while the variable is set:

- Only `CHECKLY_API_KEY` counts as environment credentials. Without it, an
  authenticated command starts the login (CI keeps an error naming both
  ways to authenticate), `login` no longer refuses, and an inline login
  uses the variable's account for that command without storing a default.
- An id the login does not have fails listing the accounts it does have.
- `login` reasons about the stored login, not the variable, and `login`,
  `switch` and `whoami` say the variable takes precedence over the default.
  `switch` reports the stored `defaultAccount`; `activateAccount` checks
  the account it activates.
- `whoami --output json` reports no default for API key credentials.
- `--wait` names a proxy or captive portal when the code expires while
  answers come from something else, waits briefly for the key of a run
  that used the same code, and only forgets the code it polled.
- Not-found hints: `withNotFoundHint()` for commands with their own
  message, `hint` and `searchedAccount` JSON fields, and tests for the
  `catch()` path and `rca run`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…an account [RED-1122]

Blind agent runs (5/5) hit `select_account` (exit 1) from `login --wait`
when the project's `.env` already pinned `CHECKLY_ACCOUNT_ID`, and had to
infer that they could go on. The variable names the account commands use,
so an explicit login now finishes like the inline one: it uses that
account and stores no default (`--account-id` still stores one).

The skill now says when `whoami` reports `defaultAccount: null` and that a
command-line prefix wins over the value in `.env`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ks the account [RED-1122]

Two of five blind agent runs read whoami's "Default account: none; choose
one with ..." as a hint to store a default they did not need, since the
project's .env already picked the account.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… [RED-1122]

The reworded warning wraps inside "environment variables", so the plain
substring match in the login e2e failed on Ubuntu and Windows. Collapse
the continuation prefixes (› / ») before matching, in logout too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… auth [RED-1122]

Cleanups from a second /simplify pass:
- `helpers/accounts.ts` holds `formatAccounts()`, `accountOverrideNotice()`
  and `accountOverrideNotAvailable()`. The override notice was built four
  times, account lists three ways, and the "not available" message twice
  with different wording (login now uses the API's).
- `config.getStoredAccount()` replaces raw `config.data.store` casts in
  `whoami` and `switch`.
- `validateAuthentication` throws the unauthorized error once.
- Clearer control flow: `whoami`'s default line and `login`'s wait for
  another run's key; `login` reads the stored login once.
- The override notice on login's `select_account` path is gone: that path
  is unreachable while `CHECKLY_ACCOUNT_ID` is set.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…OUNT_ID [RED-1122]

A login that `checkly switch` starts inline while CHECKLY_ACCOUNT_ID is set
stores no default, so switch must still ask for one (or list the choices
to an agent) instead of returning as if the login had chosen it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nswers continue [RED-1122]

`login --wait` blamed a proxy or captive portal for any expiry once
unexpected answers had occurred, even after the login server answered
again and the code simply went unapproved. `pollForTokens` now decides:
the expiry is a `network_error` only while the run of unexpected answers
is still going, for every caller.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e [RED-1122]

A login under CHECKLY_ACCOUNT_ID returns the same success line as one that
stores a default, so the skill's claim that a success makes the account
the default was wrong there. Success lines now carry `defaultAccount`
(the stored default, or null), and the skill describes it.

That login also keeps the notice saying why the stored login was no
longer usable; `#withNotice` puts the notice in front of every agent
message that can follow it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t [RED-1122]

A lone CHECKLY_ACCOUNT_ID no longer stops the inline login, so the CI
check is all that keeps a job missing its CHECKLY_API_KEY secret from
waiting on a device code nobody sees.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…alize [RED-1122]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Also marks the device-code cleanup as best effort: its compare and delete
are separate file operations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sorccu
sorccu force-pushed the san-antonio/checkly-slack-review branch from 34c28c8 to dd38da2 Compare October 13, 2026 20:22
@sorccu
sorccu marked this pull request as ready for review October 13, 2026 20:23
@sorccu sorccu changed the title feat(cli): let multi-account users act on another account per command [RED-1122] feat(cli): let multi-account users act on another account per command [RED-1122] [ship] Oct 13, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Auto-approved: ship/show PR from a same-repo branch.

@sorccu
sorccu merged commit 32feaaa into main Oct 13, 2026
29 of 31 checks passed
@sorccu
sorccu deleted the san-antonio/checkly-slack-review branch October 13, 2026 21:04
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.

2 participants