Documentation Index
Fetch the complete documentation index at: https://arkor-92aeef0e-eng-353.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
arkor login, arkor logout, arkor whoami
Three short commands that read or write ~/.arkor/credentials.json. None of them touches .arkor/state.json. For anonymous workspaces, project routing is auto-created by the runtime on the first trainer.start() (or first inference call). For OAuth workspaces, .arkor/state.json has to exist before the run: today, neither arkor login nor arkor init creates it, so the practical path is to create it manually with { orgSlug, projectSlug, projectId }. The runtime points you at this fallback when it errors out on a missing state file.
arkor login
Sign in to the managed backend. With no flags the CLI asks interactively whether to sign in via Arkor Cloud’s OAuth or as an anonymous session. The interactive selector defaults to Anonymous, so accepting the default (or piping into a non-TTY) lands on the anonymous path; OAuth runs only when you explicitly select it or pass --oauth.
Synopsis
Options
| Flag | Description |
|---|
--oauth | Skip the picker and run the Arkor Cloud OAuth (Authorization Code + PKCE) flow directly. Mutually exclusive with --anonymous. Errors out with --oauth needs a browser callback that CI runners can't complete. Use --anonymous in CI. when process.env.CI is set, because PKCE needs a browser callback and the loopback would hang forever in CI. |
--anonymous | Skip the picker and request a throwaway anonymous token. Mutually exclusive with --oauth. This is also what arkor dev does at launch when no credentials exist on disk: it always bootstraps anonymous first. |
--no-browser | Disable the automatic open(url) call. The authorization URL is printed either way (Browser: <url> line); this flag only stops the CLI from launching a browser for you. Useful in headless environments (SSH, Docker). It only affects the OAuth path. |
What happens
- The CLI calls
/v1/auth/cli/config on the cloud-api to read the deployment’s OAuth settings.
- If
--anonymous is passed, the CLI requests an anonymous token from /v1/auth/anonymous and writes it to ~/.arkor/credentials.json with mode: "anon".
- If
--oauth is passed, it skips straight to the OAuth flow.
- If no flag is passed, it shows an interactive picker (
OAuth (browser) / Anonymous) with Anonymous preselected. Accepting the default, including in non-interactive contexts where the prompt is non-blocking, runs the anonymous path; choosing OAuth runs the OAuth flow.
- The OAuth flow generates a PKCE pair, starts a loopback HTTP server on one of the cloud-api-provided callback ports, opens (or prints) the authorize URL, and waits for the callback. State is verified before exchanging the code for tokens; a mismatch aborts to prevent CSRF. The resulting OAuth tokens are written to
~/.arkor/credentials.json with mode: "auth0".
The loopback server is closed in a finally block so it does not stick around if login fails.
Anonymous mode in one paragraph
Anonymous credentials let you try Arkor without an account: training runs, jobs, and any work you do are tied to the local machine via the anonymous token. The anonymous path always mints a brand-new token (and a new anonymousId) and overwrites ~/.arkor/credentials.json, so re-running arkor login --anonymous does not refresh the existing identity. Switching to OAuth (arkor login --oauth, or selecting OAuth (browser) in the picker) overwrites the credentials file the same way and does not migrate prior anonymous workspaces or jobs into the account. Merging anonymous work into an OAuth account once you sign in is on the roadmap; until that lands, run arkor login --oauth before you start the runs you want associated with the account.
Anonymous accounts are intentionally single-device: the cloud-api binds the issued token to the machine that received it (via a latest_jti rotation each time the SDK refreshes the token), so copying ~/.arkor/credentials.json to a second machine and using it from both will trip the server’s single-device guard. The losing client receives an HTTP 401 / 409 with code: "anonymous_token_single_device", which cli/main.ts surfaces as actionable guidance; deletion of the underlying anonymous row surfaces as code: "anonymous_account_not_found" the same way. The recovery hint is deployment-aware: on OAuth-supporting deployments the CLI points at arkor login --oauth so you can sign up for an account that supports multiple devices, while on anon-only deployments (where OAuth is not configured) it points at arkor login --anonymous instead — --oauth would fail there, and minting a fresh anonymous identity is the only recovery available. Note that neither path migrates existing anonymous work into the new identity; the previous workspace stays reachable only from the credentials file that issued it.
Anonymous issuance output
Both anonymous paths surface the new anonymousId and an explanation that the same id is how Arkor Cloud recognises this client across sessions, though the line shape differs by entry point. arkor login (--anonymous flag or picker → Anonymous) prints Anonymous id: <id> as the spinner stop and then a separate info line saying that keeping the credentials file (credentialsPath(), typically ~/.arkor/credentials.json on Linux and macOS) is what preserves the identity. arkor dev’s auto-bootstrap skips the spinner and emits a single info line that already embeds the id and the same explanation. The picker → Anonymous path additionally surfaces a one-line warn alongside the success message — Anonymous sessions aren't guaranteed to persist — sign in with `arkor login --oauth` to tie future work to your Arkor Cloud account. — so the upgrade hint is visible at issuance time. The explicit --anonymous shortcut suppresses that warn because it skips the /v1/auth/cli/config fetch and so cannot tell whether arkor login --oauth would succeed on this deployment; pointing at it on a rare anon-only deployment would steer users at a command that fails.
Every anonymous issuance path also surfaces the single-device limitation as a separate info line. The exact wording is gated on oauthAvailable (same contract as the persistence nudge). On OAuth-supporting deployments callers see the upgrade-flavoured Note: anonymous accounts work on this machine only. Run `arkor login --oauth` to sign up for multi-device access., while on anon-only deployments (and on arkor login --anonymous where the cfg fetch is skipped intentionally) callers see only the bare Note: anonymous accounts work on this machine only., so users on those deployments aren’t pointed at a command that would fail. arkor whoami on an anonymous identity also emits the bare variant, because it doesn’t fetch /v1/auth/cli/config to learn whether OAuth is offered.
arkor logout
Deletes ~/.arkor/credentials.json. Prompts for confirmation by default.
Synopsis
Options
| Flag | Description |
|---|
-y, --yes | Skip the confirmation prompt. |
Behavior
- If the credentials file does not exist, the CLI logs
No credentials on file. and exits.
- If the user declines the prompt, the CLI logs
Aborted. and exits without deleting.
arkor logout does not touch .arkor/state.json or the .arkor/build/ artifact. To start fully fresh, remove .arkor/ manually as well.
arkor whoami
Prints the current identity from /v1/me on the cloud-api, plus the org slugs you can reach.
Synopsis
No flags.
Output
When signed in, the command prints the JSON user object pretty-printed, then a single Orgs: <slug>, <slug>, … line if the response includes any. When ~/.arkor/credentials.json is missing, it prints Not signed in. Run \arkor login` or `arkor login —anonymous`.` and exits.
Exit codes
0: signed in, identity printed.
0: not signed in; the message is informational only.
1: the cloud-api returned 426 Upgrade Required. The CLI prints the upgrade hint (and the upgrade command for your detected package manager) and sets process.exitCode = 1 so the deprecation-warning flush in arkor’s shutdown hook still runs before the process exits.
- Other 4xx / 5xx are reported as
Failed to fetch /v1/me (<status>). Token may be expired. and exit 0.
Where the credentials live
Both modes write to the same file at ~/.arkor/credentials.json and are tagged with a mode field of either "auth0" or "anon" so the rest of the CLI (and the Studio server) knows which path to use. See Project structure for the full layout.
Token expiry
For OAuth sessions, the credentials file records both the access token and the issued refresh token, plus the expiresAt timestamp returned by the token exchange. The refresh token is stored today, but the CLI does not yet auto-refresh expired access tokens; that path is on the roadmap.
In practice that means:
-
An expired or revoked token shows up as a non-2xx response from the cloud-api.
arkor whoami no longer prints a generic “Token may be expired” hint; instead it raises a CloudApiError carrying the upstream code (when present), and the top-level handler in cli/main.ts formats two known auth-state codes as actionable guidance. Before formatting, main() does a best-effort fetch of /v1/auth/cli/config so the recovery hint matches the deployment shape:
code: "anonymous_token_single_device" →
- on OAuth-supporting deployments:
Anonymous credentials were rejected as single-device. Anonymous accounts only work on one machine. Sign up for an account that supports multiple devices: arkor login --oauth (exit 1).
- on anon-only deployments:
Anonymous credentials were rejected as single-device. Anonymous accounts only work on one machine. This deployment does not advertise OAuth, so the only recovery is to mint a new anonymous identity (your previous workspace data cannot be recovered): arkor login --anonymous (exit 1).
code: "anonymous_account_not_found" → analogous OAuth-vs-anon-only split, ending in arkor login --oauth or arkor login --anonymous.
Errors without a known code (and any non-CloudApiError exceptions) are rethrown out of main(). bin.ts wraps the top-level await main(...) in a try/catch that logs err.stack ?? err.message to stderr and sets process.exitCode = 1 — so you’ll see something like CloudApiError: cloud-api 503 followed by a stack frame, not just the upstream error body. (The explicit catch is there to avoid the bundled minified frame Node’s default unhandled-rejection handler would surface, and to keep the stderr flush deterministic across the supported Node range.)
-
The fix for an OAuth session is to re-run
arkor login --oauth, which goes through the full PKCE flow again and overwrites ~/.arkor/credentials.json with fresh tokens.
Anonymous tokens have a server-side 90-day TTL, but the CLI does not yet auto-refresh them; that wiring lives in @arkor/cloud-api-client’s getToken() and is on the SDK roadmap. If an anonymous session starts failing today, run arkor login --anonymous to mint a new one (this issues a new anonymousId, so it is effectively a different workspace).
Common errors
| Message | Where | What it means | Fix |
|---|
Pick one of --oauth / --anonymous, not both. | arkor login | Both mode flags were passed. | Pass at most one. |
--oauth needs a browser callback that CI runners can't complete. Use --anonymous in CI. | arkor login --oauth (with process.env.CI set) | PKCE depends on a loopback redirect from a browser, which CI cannot satisfy. | Use --anonymous in CI. For local headless flows that still have a reachable browser (e.g. ... --no-browser | tee logs), unset CI for the run. |
State mismatch — aborting to prevent CSRF | arkor login --oauth | The state returned to the loopback callback does not match the one the CLI generated (almost always because the browser hit a stale tab from a previous login attempt). | Re-run arkor login --oauth and complete the flow in the freshly opened tab. |
Auth0 did not return a refresh token. Make sure the Application has 'offline_access' scope enabled. | arkor login --oauth | The OAuth token exchange succeeded but the response had no refresh_token, so the CLI cannot keep the session alive past the access token’s lifetime. Usually a deployment-side misconfiguration. | Enable the offline_access scope on the OAuth (Auth0) application, then re-run arkor login --oauth. |
No credentials on file. | arkor logout | ~/.arkor/credentials.json does not exist. Nothing to delete. | Run arkor login first if you wanted to sign in. |
Not signed in. Run \arkor login` or `arkor login —anonymous`.` | arkor whoami | Same condition as above, surfaced from a different command. | Same fix. |
Anonymous credentials were rejected as single-device. … (followed by either arkor login --oauth or arkor login --anonymous depending on deployment) | arkor whoami and any other authenticated command | The cloud-api rejected the request with code: "anonymous_token_single_device" (HTTP 401 from the userAuth jti check or HTTP 409 from the rotate-jti CAS). Either the credentials file was copied to a second device, or another local process refreshed past this token within the recovery window. | Follow the command in the message: arkor login --oauth to sign up for an OAuth account on supporting deployments, or arkor login --anonymous to mint a new anonymous identity on anon-only deployments. Either path is a new identity; existing anonymous work cannot be migrated. The CLI exits 1. |
Your anonymous credentials are no longer valid. … (followed by either arkor login --oauth or arkor login --anonymous) | arkor whoami and any other authenticated command | The cloud-api rejected the request with code: "anonymous_account_not_found" (HTTP 401). The underlying anonymous_users row was deleted (admin / cascade / explicit revocation). | Same as above — follow the deployment-aware command in the message. The previous anonymous workspace cannot be recovered. The CLI exits 1. |
CloudApiError: cloud-api <status> (and a stack trace) | arkor whoami and any other authenticated command | A non-200 / non-426 cloud-api response without a structured auth-state code (transient 5xx, an unmapped 4xx, etc.). cli/main.ts rethrows it; bin.ts catches it at the top of the stack and renders it with console.error(err.stack ?? err.message) before setting process.exitCode = 1. | Inspect the upstream message at the top of the trace; if it looks like a transport/server failure, retry. For an expired OAuth access token, re-run arkor login --oauth. For an expired anonymous token, re-run arkor login --anonymous (mints a new anonymousId). |
426 Upgrade Required (with upgrade hint) | arkor whoami (and other cloud-api calls) | The deployment requires a newer SDK version. The CLI prints the upgrade command for your detected package manager and sets process.exitCode = 1. | Upgrade the arkor package and re-run. |