Skip to main content

arkor dev

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.

Synopsis

Options

Behavior

Launch sequence

  1. Credential bootstrap. If ~/.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.).
  2. CSRF token. A 32-byte token (base64url, ~43 chars) is generated for this launch. It is injected into 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.
  3. Listener. Hono on 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.
  4. Token persistence (best-effort, after a successful bind). Once the listener is up, the token is written to ~/.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.

Loopback and CSRF model

The Studio server enforces three checks on every /api/* request:
  1. The Host header must be 127.0.0.1 or localhost (defense against DNS rebinding).
  2. The CSRF token must be present as the 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.
  3. CORS is intentionally not configured: the SPA is same-origin so CORS adds no value, and reflecting * 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.

Port collision

arkor dev does not auto-pick a free port. If the chosen port is already taken (another arkor dev left running, an unrelated dev server, etc.), the listener’s EADDRINUSE is caught and arkor dev exits non-zero with Port <port> is already in use. Another `arkor dev` may be running; pass --port to choose a different one. Pick a different port with -p <port>, or stop whatever else is bound to it. The token file (~/.arkor/studio-token) is written only after the port binds successfully, so a failed second launch on a busy port never overwrites or deletes the token a healthy first instance depends on.

Errors

Examples

Default port:
Custom port plus auto-open:

See also