Launch Arkor Studio locally.
Boots Studio, the local web UI, on http://localhost:4000. Studio is where you click Run training to spawn arkor start against your src/arkor/index.ts, watch the run stream in, and chat with the resulting adapter in the Playground.
arkor dev itself does not start a training run; it only serves the UI plus a small loopback API the SPA talks to.
arkor dev [options]pnpm devnpm run devyarn devbun dev| Flag | Default | Description |
|---|---|---|
-p, --port <port> | 4000 | Port to bind. The displayed URL uses localhost, but the listener binds 127.0.0.1 directly so it cannot end up IPv6-only on hosts where /etc/hosts lists ::1 before 127.0.0.1. The CLI parses the value as `Number(opts.port) |
--open | off | Open the Studio URL in a browser after the server is up. |
~/.arkor/credentials.json does not exist, the CLI always tries to bootstrap an anonymous session: it calls /v1/auth/cli/config, then requests an anonymous token from /v1/auth/anonymous. The pre-bootstrap line depends on whether the deployment advertises OAuth: when OAuth is configured the CLI prints No credentials on file. Bootstrapping an anonymous session. Run `arkor login --oauth` to sign in to your account instead. so you can upgrade to a real account whenever you want; on anon-only deployments it prints No credentials on file. Requesting an anonymous token. instead, omitting the OAuth hint because arkor login --oauth would fail there. Either way, it never auto-launches the OAuth flow. Once the token lands, arkor dev prints Anonymous id: <id>. Arkor Cloud uses this id to recognise this client across sessions. Keep `<home>/.arkor/credentials.json` to stay signed in as the same anonymous identity. (the path is the resolved credentialsPath(), typically ~/.arkor/credentials.json on Linux and macOS). Only when the deployment advertises OAuth, a follow-up warn (Anonymous sessions aren't guaranteed to persist. Sign in with `arkor login --oauth` to tie future work to your Arkor Cloud account.) fires alongside the success line so the upgrade hint is visible at issuance time. On anon-only deployments the warn is suppressed because pointing at arkor login --oauth would surface a command that fails. Transport failures (fetch failed) are handled differently depending on when they hit. If /v1/auth/cli/config already succeeded and /v1/auth/anonymous then fails the same way, the CLI warns and continues; the Studio server retries on the first /api/credentials hit. If /v1/auth/cli/config itself is unreachable, the same transport error is rethrown and arkor dev exits fast (restore connectivity and re-run). If /v1/auth/anonymous is rejected with a 4xx (for example because anonymous sign-in is disabled on this deployment), it surfaces an error wrapping the HTTP status and pointing at arkor login --oauth (full message: Failed to bootstrap an anonymous session (HTTP <status>). This deployment may require sign-in. Run `arkor login --oauth` and try again.).index.html as <meta name="arkor-studio-token"> so the same-origin SPA can read it. Cross-origin tabs cannot read the meta and are rejected by the /api/* middleware.127.0.0.1:<port>. The Host header guard accepts both 127.0.0.1 and localhost, so the URL the CLI prints (http://localhost:<port>) works without surprising DNS-rebinding fallout.~/.arkor/studio-token (mode 0600) so the studio-app Vite dev server (pnpm --filter @arkor/studio-app dev) can pick it up. Binding first means a doomed second launch on a busy port never touches the file (see "Port collision" below). If writing fails (read-only $HOME, locked-down umask), arkor dev continues; only the standalone Vite dev workflow is affected.When the process exits (normal exit, SIGINT, SIGTERM, or SIGHUP) the studio-token file is removed on a best-effort basis. A crash can leave the file on disk; the next arkor dev rotates it.
The Studio server enforces three checks on every /api/* request:
Host header must be 127.0.0.1 or localhost (defense against DNS rebinding).X-Arkor-Studio-Token header. The job-event stream also accepts ?studioToken=... because EventSource cannot send custom headers; mutation routes do not accept query-string tokens. Token comparison is timingSafeEqual.* would let "simple" cross-origin POSTs (text/plain, urlencoded) skip preflight. Without a token, the middleware rejects them.This means arkor dev is safe on a shared dev machine: another tab cannot read the meta, a stale tab from a previous launch holds an old token that no longer matches, and an attacker page in a different origin cannot forge requests.
Behavior depends on whether you passed --port explicitly.
--port (using the 4000 default). If 4000 is taken (another arkor dev left running, an unrelated dev server, etc.), arkor dev retries on 4001, 4002, and so on, up to 10 attempts total (counting the initial attempt on the default port). Before each retry it logs a warning, Port <port> is in use, trying <port + 1> instead. Pass --port to pin a specific one., and once bound it prints the port it actually landed on: Arkor Studio running on http://localhost:<port>. If every port in that range is busy, it exits non-zero with Port <requested> is already in use, and no free port was found in <requested>-<last>. Pass --port to choose one explicitly.--port <n>. No fallback happens. If <n> is taken, arkor dev exits non-zero immediately with Port <n> is already in use. Another `arkor dev` may be running; pass --port to choose a different one. This keeps --port predictable: if you asked for a specific port, arkor dev never silently substitutes a different one.The token file (~/.arkor/studio-token) is written only after the port binds successfully, so a failed launch attempt on a busy port (in either case above) never overwrites or deletes the token a healthy already-running instance depends on.
| Symptom | What it means | Fix |
|---|---|---|
Port <n> is already in use. Another `arkor dev` may be running; pass --port to choose a different one. | You passed --port <n> explicitly and it's taken (the underlying error is EADDRINUSE). No fallback happens for an explicit port. | Stop whatever else is bound to it, or use --port <other>. |
Port <requested> is already in use, and no free port was found in <requested>-<last>. Pass --port to choose one explicitly. | You didn't pass --port, and every port from the 4000 default through the 10th attempt was taken. | Stop one of the competing processes, or pass --port <other> explicitly. |
Could not reach <baseUrl> (fetch failed). Studio will keep running and retry on first /api/credentials hit. | /v1/auth/cli/config already succeeded, but the follow-up /v1/auth/anonymous hit a transport error. The Studio server starts and will retry. | Bring connectivity back; the SPA recovers on its next /api/credentials poll without restarting arkor dev. |
TypeError: fetch failed (or an equivalent transport error that exits arkor dev immediately) | /v1/auth/cli/config itself was unreachable, so the deployment mode could not be determined and the CLI fails fast. | Restore connectivity and re-run arkor dev. |
No credentials on file. Bootstrapping an anonymous session. Run `arkor login --oauth` to sign in to your account instead. | First arkor dev on this machine when the deployment advertises OAuth. The CLI bootstraps anonymous so Studio can start immediately; the message is informational, not an error. | Nothing required. To upgrade to a real account, run arkor login --oauth separately (it overwrites ~/.arkor/credentials.json) and refresh Studio. |
No credentials on file. Requesting an anonymous token. | Same as above on anon-only deployments (no OAuth advertised in /v1/auth/cli/config). The CLI omits the arkor login --oauth hint because that command would fail there. | Nothing required. |
Anonymous id: <id>. Arkor Cloud uses this id to recognise this client across sessions. Keep `<home>/.arkor/credentials.json` to stay signed in as the same anonymous identity. | Informational follow-up after the anonymous bootstrap completes. Surfaces the cloud-side identifier and where it lives (the path is the resolved credentialsPath(), typically ~/.arkor/credentials.json on Linux and macOS). | Nothing required. Back up the credentials file if you want to keep using the same anonymous identity from another machine. |
Anonymous sessions aren't guaranteed to persist. Sign in with `arkor login --oauth` to tie future work to your Arkor Cloud account. | Persistence nudge fired alongside the success message when the deployment is known to support OAuth. Anonymous work has no SLA on the cloud-api side, so the CLI surfaces the upgrade path before you invest real work. Suppressed on anon-only deployments. | Optional: run arkor login --oauth to tie future work to your account. Existing anonymous work stays under its current id; there is no migration path today. |
Failed to bootstrap an anonymous session (HTTP <status>). This deployment may require sign-in. Run `arkor login --oauth` and try again. | /v1/auth/anonymous rejected the request with a 4xx, so anonymous bootstrap cannot proceed. | Run arkor login --oauth to complete the browser flow, then re-run arkor dev. |
Could not write ~/.arkor/studio-token (...). The Studio at http://localhost:<port> is unaffected, but the Vite SPA dev workflow will see 403s on /api/*. | $HOME is read-only or umask blocks 0600. The bundled Studio still works; only the standalone Vite dev workflow is affected. | Run from a writable home, or only use the bundled Studio served by arkor dev. |
HTTP 403 with { "error": "Studio API is loopback-only" } (in browser devtools) | The Host header is something other than 127.0.0.1 / localhost. | Reach Studio via http://localhost:<port> or http://127.0.0.1:<port>. Reverse proxies or 0.0.0.0-bound shells will be rejected by design. |
HTTP 403 with { "error": "Missing or invalid studio token" } (in browser devtools) | The CSRF token in the page does not match the current launch. Usually a stale tab from a previous arkor dev. | Reload the tab. Token rotates on every launch. |
Default port:
pnpm devnpm run devyarn devbun devCustom port plus auto-open:
pnpm dev --port 5000 --opennpm run dev -- --port 5000 --openyarn dev --port 5000 --openbun dev --port 5000 --openarkor login for upgrading from an anonymous session to OAuth~/.arkor/ and .arkor/ layoutLicense
This page is licensed under the MIT License. Keep its copyright and permission notice in all copies.
Copyright (c) 2026 Arkor