You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
TL;DR — Three separate resolvers decided which token a command used, and they disagreed. resolveToken ignored APIFY_TOKEN, mcp install honored it, the actor entrypoint required it. On top of that, getLoggedClient() persisted whatever token it resolved, so a one-off token overwrote the stored login. Shipped in #1431: one resolver, APIFY_TOKEN → stored login, and only apify login writes credentials. The global --token flag in the original plan was dropped — see What shipped.
Part of #1383 (Stage-1). Shipped in #1431, on top of #1417 (Stage-0 pin).
Problem
Resolver
Order
resolveToken — utils.ts:132, behind all 46 getLoggedClientOrThrow() calls
passed token → stored. No env.
resolveApifyToken — mcp/install.ts:13
--token → APIFY_TOKEN → stored
getApifyTokenFromEnvOrAuthFile — lib/actor.ts:24
APIFY_TOKEN → stored
Same shell, three answers depending on the command. --token exists on 2 of ~50 commands.
Second defect, found in the review of #1293: getLoggedClient() both resolves and persists. Once the resolver returns an env token, setToken() writes it over the stored login and rewrites username/id. The stored account is replaced by a transient env var, and it survives unset.
Decision
APIFY_TOKEN → stored login
This is not a new rule. mcp install and the actor entrypoint already worked this way; only resolveToken did not. It needs no special case for the actor CLI: inside a platform run there is no stored login, so APIFY_TOKEN wins on its own.
The original plan put a global --token in front of APIFY_TOKEN. That was dropped during implementation — see below.
What shipped
Divergences from the plan above, all deliberate:
Planned
Shipped
Why
Global --token on every command
Dropped. --token stays on login and mcp install only.
It duplicated APIFY_TOKEN — two ways to do one thing. gh has no per-command account flag either; you switch with gh auth switch or GH_TOKEN=. Per-command override is APIFY_TOKEN=x apify <cmd>.
apify login ignores APIFY_TOKEN
apify loginfails when APIFY_TOKEN is set and does not match --token. Exit 5.
A login that APIFY_TOKEN then shadows reports success for an account no other command uses. That is the #1293 confusion.
Silent fallback on a bad APIFY_TOKEN
A placeholder value (undefined, null, 0, …) is a hard error, exit 5. login warns and proceeds, so a broken shell can be fixed.
Colleague feedback: falling back hides a misconfigured CI variable and runs as the wrong account.
No warning when env overrides a login
resolveAuth warns once per command, but only when a stored login exists.
Silent in CI and platform runs, where APIFY_TOKEN is the only credential and naming it is noise.
Two stacked PRs
One PR, two commits in the required order.
Same guarantee, less overhead.
Also fixed along the way, outside the original scope:
Stale proxy password on re-login.loginWithToken stored a proxy password when the account had one and did nothing when it did not, so logging in as an account without one left the previous account's in the OS keyring. apify run then handed the Actor the wrong account's proxy credential. Pre-existing, not introduced here.
Unbounded account lookup.apify run resolves the account before it spawns anything. With the client defaults an unreachable API retried for minutes. Now maxRetries: 1, timeoutSecs: 10.
Scope as built
One resolver, lib/auth.ts. getApifyTokenFromEnvOrAuthFile (lib/actor.ts) and resolveApifyToken (mcp/install.ts) are gone.
Resolve and persist are separate:
resolveAuth() — reads only, single-flighted. APIFY_TOKEN → stored. Returns the token and its source.
loginWithToken(token) — authenticates and saves. The only credential writer in the CLI.
apify auth token prints the token that would be used, not getLocalUserInfo().token.
apify info gains a token source row, so the active credential is visible.
apify run passes the resolved token to the child and no longer overwrites an inherited APIFY_TOKEN.
Auth failures name their source and set an exit code: MissingAuth for no token, InvalidInput for a misconfigured APIFY_TOKEN.
Precedence — env added to the resolver, the other two resolvers folded in, run child env.
Never the reverse. Precedence without the guard is the #1293 data-loss bug.
Resolved questions
--token on every command, or only ones that authenticate? Neither. Dropped entirely, replaced by APIFY_TOKEN=x apify <cmd>.
A one-off token has no stored username.getCurrentUserInfo() looks it up from the API only when the token came from the environment, caches it per token, and falls back to auth.json for a stored login.
The original request, from 2019 — run one command as a different user. Closed into #445. Answered here by APIFY_TOKEN=x apify <cmd>, not by a global flag.
Note
TL;DR — Three separate resolvers decided which token a command used, and they disagreed.
resolveTokenignoredAPIFY_TOKEN,mcp installhonored it, theactorentrypoint required it. On top of that,getLoggedClient()persisted whatever token it resolved, so a one-off token overwrote the stored login. Shipped in #1431: one resolver,APIFY_TOKEN→ stored login, and onlyapify loginwrites credentials. The global--tokenflag in the original plan was dropped — see What shipped.Part of #1383 (Stage-1). Shipped in #1431, on top of #1417 (Stage-0 pin).
Problem
resolveToken—utils.ts:132, behind all 46getLoggedClientOrThrow()callsresolveApifyToken—mcp/install.ts:13--token→APIFY_TOKEN→ storedgetApifyTokenFromEnvOrAuthFile—lib/actor.ts:24APIFY_TOKEN→ storedSame shell, three answers depending on the command.
--tokenexists on 2 of ~50 commands.Second defect, found in the review of #1293:
getLoggedClient()both resolves and persists. Once the resolver returns an env token,setToken()writes it over the stored login and rewritesusername/id. The stored account is replaced by a transient env var, and it survivesunset.Decision
This is not a new rule.
mcp installand theactorentrypoint already worked this way; onlyresolveTokendid not. It needs no special case for theactorCLI: inside a platform run there is no stored login, soAPIFY_TOKENwins on its own.The original plan put a global
--tokenin front ofAPIFY_TOKEN. That was dropped during implementation — see below.What shipped
Divergences from the plan above, all deliberate:
--tokenon every command--tokenstays onloginandmcp installonly.APIFY_TOKEN— two ways to do one thing.ghhas no per-command account flag either; you switch withgh auth switchorGH_TOKEN=. Per-command override isAPIFY_TOKEN=x apify <cmd>.apify loginignoresAPIFY_TOKENapify loginfails whenAPIFY_TOKENis set and does not match--token. Exit 5.APIFY_TOKENthen shadows reports success for an account no other command uses. That is the #1293 confusion.APIFY_TOKENundefined,null,0, …) is a hard error, exit 5.loginwarns and proceeds, so a broken shell can be fixed.resolveAuthwarns once per command, but only when a stored login exists.APIFY_TOKENis the only credential and naming it is noise.Also fixed along the way, outside the original scope:
loginWithTokenstored a proxy password when the account had one and did nothing when it did not, so logging in as an account without one left the previous account's in the OS keyring.apify runthen handed the Actor the wrong account's proxy credential. Pre-existing, not introduced here.apify runresolves the account before it spawns anything. With the client defaults an unreachable API retried for minutes. NowmaxRetries: 1,timeoutSecs: 10.Scope as built
lib/auth.ts.getApifyTokenFromEnvOrAuthFile(lib/actor.ts) andresolveApifyToken(mcp/install.ts) are gone.resolveAuth()— reads only, single-flighted.APIFY_TOKEN→ stored. Returns the token and its source.loginWithToken(token)— authenticates and saves. The only credential writer in the CLI.apify auth tokenprints the token that would be used, notgetLocalUserInfo().token.apify infogains atoken sourcerow, so the active credential is visible.apify runpasses the resolved token to the child and no longer overwrites an inheritedAPIFY_TOKEN.MissingAuthfor no token,InvalidInputfor a misconfiguredAPIFY_TOKEN.Commit order
loginWithTokenadded, onlyloginpersists.runchild env.Never the reverse. Precedence without the guard is the #1293 data-loss bug.
Resolved questions
--tokenon every command, or only ones that authenticate? Neither. Dropped entirely, replaced byAPIFY_TOKEN=x apify <cmd>.username.getCurrentUserInfo()looks it up from the API only when the token came from the environment, caches it per token, and falls back toauth.jsonfor a stored login.Supersedes and related
APIFY_TOKENto work as a parameter or env var.APIFY_TOKEN=x apify <cmd>, not by a global flag.getLoggedClientpersisted the env token over the stored login.run.ts, but the hang is the child SDK blocking on a terminal-status POST. The fix may belong in the SDK.APIFY_TOKENin their docs. Tell them the outcome — support stays, precedence is now defined, and it outranks a stored login.Notes
resolveTokenhappy-path pin first, merged as test: pin auth behavior before the multi-account refactor #1417.🤖 Generated with Claude Code