Skip to content

feat(ENG-967): agent mode for arkor dev (--agent) so coding agents can drive Arkor over HTTP - #201

Open
k-taro56 wants to merge 21 commits into
mainfrom
eng-967
Open

feat(ENG-967): agent mode for arkor dev (--agent) so coding agents can drive Arkor over HTTP#201
k-taro56 wants to merge 21 commits into
mainfrom
eng-967

Conversation

@k-taro56

@k-taro56 k-taro56 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #141 (CLAUDECODE strict mode for the scaffolders). ENG-967.

What

arkor dev --agent runs the same bundled Studio server headlessly so a coding agent (Claude Code or any other) can operate Arkor through direct HTTP calls, without the browser UI. The Studio SPA stays fully served the whole time, so a human can still open the URL in a browser.

How it works

  • Session file (the agent's token channel). The per-launch CSRF token is written as JSON ({ token, url, port, pid }) to a unique per-session file at <project>/.arkor/agent/session-<pid>-<uuid>.json (agent dir 0700, file 0600, atomic temp+rename; parent .arkor keeps the default mode). The path is printed to stdout as the stable greppable line Arkor Studio agent session file: <path>. Unlike the best-effort ~/.arkor/studio-token, this write is fail-hard: without it the server would be silently unusable to its own caller. The shutdown handlers (exit/SIGINT/SIGTERM/SIGHUP) unlink exactly this path plus its deterministic .tmp sibling; there is deliberately no pid-liveness sweep (pids are namespace-local, so on a bind-mount-shared project a live foreign-container session would look dead and get reaped).
  • GET /api/status. New token-exempt, secrets-free probe endpoint (still behind the loopback bind and Host-header guard): status, server: "arkor-studio", version, mode, url, pid, cwd, and the endpoint list. It never reads credentials and never executes user code. The exemption is an exact-path, GET-only carve-out that fails closed.
  • Claude Code strict gate. Under CLAUDECODE=1, plain arkor dev prints a re-invocation block to stderr and exits 1 via the ClaudeCodeStrictExit sentinel, asking for --agent. The flag itself needs no env var, so other coding agents opt in the same way.
  • Port collision: normal mode adopts, agent mode never does. A plain arkor dev on a busy port probes http://127.0.0.1:<port>/api/status with NO token (nothing is disclosed to an unverified occupant; the response is byte-capped and redirects are blocked) and connects only when the occupant is an Arkor Studio serving the same project (cwd must be absolute, compared via realpath): it prints Arkor Studio already running on <url> and exits 0. Anything else falls back to the port-in-use error. Adoption is deliberately unauthenticated: an impostor can never obtain the CSRF token or reach POST /api/train; the worst case is a nuisance denial or --open redirect, and the trust model is documented. arkor dev --agent always hard-errors on a busy port (an agent session needs its own server and session file) and advises --port.
  • Agent-facing URLs are the http://127.0.0.1:<port> literal (session file and /api/status echo), because an agent's HTTP client may not do Happy-Eyeballs; the human-facing stdout line and --open keep localhost.
  • Supporting changes. --port now requires a plain decimal integer in 1..65535 (rejected up front instead of coercing); new ExpectedCliError in cli-internal lets bin.ts print routine failures (invalid port, busy port, session-file write failure, anon-bootstrap 4xx) as clean one-liners without a minified stack; telemetry longRunning accepts a post-resolve predicate so the short-lived adopt outcome emits cli_command_completed; credential-bootstrap hard failures are deferred to serve time so an offline first run can still adopt a running Studio.

Tests

  • Unit: dev.ts session lifecycle, hard-fail, adopt/probe matrix (timeout, redirect, byte cap, relative/absolute cwd, different project), parseDevPort edges, gate wiring, /api/status contract (including the full endpoint list and secrets-free assertion), telemetry predicate; cli-internal formatter.
  • e2e/cli: strict gate exits 1 with nothing persisted; --agent in help; ExpectedCliError prints as a one-liner with no stack.
  • e2e/studio: new agent spec against real processes (session file shape and modes, status probe, 403 without token, SPA reachability, SIGINT cleanup, plain-dev adoption, different-project non-adoption). Both harnesses strip CLAUDECODE so the gate cannot leak in from a Claude Code session.

Docs

EN and JA in lockstep: cli/dev reference (options, agent mode, strict mode, port-collision and adoption trust model, errors, examples), guides/cli/dev, CLI overview env table, studio/concepts pages, plus the AGENTS.md security section. Anchors reuse only the #141-verified Mintlify slugs.

The branch went through 8 adversarial self-review rounds (probe hardening, Windows guards, mutation-verified load-bearing tests, doc-precision fixes); origin/main is merged in.


Summary by cubic

Add agent mode to arkor dev so coding agents can drive Studio over HTTP without the browser, and add a token‑exempt /api/status probe for safe port adoption. Implements ENG-967 while keeping the Studio UI available for humans.

  • New Features

    • arkor dev --agent: runs Studio headlessly for agents. Writes { token, url, port, pid } to <project>/.arkor/agent/session-<pid>-<uuid>.json (dir 0700, file 0600, atomic temp+rename) and prints “Arkor Studio agent session file: ”.
    • Agent URLs use http://127.0.0.1:<port>; the human-facing line and --open keep localhost.
    • GET /api/status: token‑exempt, loopback‑guarded, secrets‑free. Returns server/version/mode/url/pid/cwd and endpoint list. Used to confirm an occupant without sending the token.
    • CLAUDECODE strict gate: with CLAUDECODE=1, plain arkor dev exits with a message asking for --agent (no token/session file written).
    • Port collision rules: plain arkor dev adopts only when the occupant is Arkor Studio serving the same project; --agent never adopts and errors with a hint to use --port.
  • Refactors

    • Port validation is strict (1..65535, plain decimal). Bind failures surface as one‑line ExpectedCliErrors (no stack).
    • Adoption hardening: probe uses 127.0.0.1, 1.5s timeout, 64 KiB body cap, redirect: "manual", and rejects relative, /proc/*, /dev/fd/*, or UNC \\host\share\... occupant cwd before realpath comparison.
    • Static asset handler containment check guards the token‑free GET * route against path traversal on Windows.
    • Telemetry: dev reports completed when it connects-and-exits; function‑form longRunning is guarded.
    • Session cleanup unlinks the exact session file and its .tmp sibling; no pid sweep. Server closes on adopt.
    • Project slug generation: truncate then trim to avoid trailing -.
    • Docs (EN + JA) updated: agent mode, /api/status carve‑out, dev loop picks up source edits via 5s polling, adoption trust model, errors, CLAUDECODE gate, and npm packages/arkor/README.md.

Written for commit 682cab8. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added arkor dev --agent for headless Studio sessions with secure per-session connection files.
    • Studio now detects source edits about every five seconds while Overview is open.
    • Normal development mode can connect to an existing Studio for the same project.
    • Added token-free status checks while keeping other API routes protected.
  • Bug Fixes

    • Improved port validation, error messages, session cleanup, and asset security.
    • Increased retained job event history to 500 entries.
  • Documentation

    • Expanded English and Japanese CLI, Studio security, project structure, and agent-mode guidance.

k-taro56 added 13 commits July 21, 2026 23:59
Follow-up to PR #141 (CLAUDECODE strict mode for the scaffolders): let a
coding agent operate Arkor over direct HTTP without the Studio browser UI.

- New --agent flag on arkor dev. The same Studio server runs headlessly;
  the per-launch CSRF token is additionally written as JSON
  ({ token, url, port, pid }) to a unique per-session file at
  <project>/.arkor/agent/session-<pid>-<uuid>.json (dir 0700, file 0600,
  atomic temp+rename), and its path is printed to stdout as a stable
  greppable line. Unlike the home token this write is fail-hard: the
  session file is the agent's only token channel. The same shutdown
  handlers unlink it (no ownership check needed; the name is unique).
- New GET /api/status endpoint (token-guarded, secrets-free, never runs
  user code): status, server discriminator, version, mode, url, pid, cwd,
  and the endpoint list, so an agent can confirm the server and discover
  the API surface.
- Strict gate: under CLAUDECODE=1 a plain arkor dev exits 1 via the
  ClaudeCodeStrictExit sentinel asking for --agent. The flag itself needs
  no env var, so other coding agents opt in the same way.
- Port collision: normal mode now probes the occupant via /api/status
  with the home token and, on confirming a running Arkor Studio (often an
  agent session), prints the URL, honors --open, and exits 0 instead of
  EADDRINUSE-failing. Agent mode keeps the hard error.
- Both e2e harnesses strip CLAUDECODE from spawned children so the gate
  cannot leak into tests from a Claude Code session.
- Tests: cli-internal formatter, /api/status, dev.ts session file +
  probe paths, main.ts gate wiring, e2e/cli run-to-exit gate cases, and
  a new e2e/studio agent.spec.ts against real processes.
- Docs: EN+JA dev reference (agent mode, strict mode, port-collision
  rewrite, errors, examples), guides, CLI overview, and the AGENTS.md
  security section.
…-review)

Findings from an adversarial self-review of 6db4e87:

- Windows CI: skip the SIGINT-cleanup e2e spec on win32 (Node's Windows
  signal emulation terminates the child forcefully, so the dev.ts handler
  that unlinks the session file never runs) and guard the hard-fail unit
  test the same way (chmod maps to the read-only attribute there and does
  not block mkdir inside the directory, so the injected failure never
  happens). Both would have failed deterministically on the windows
  lanes.
- Session-file cleanup now sweeps every session-<own pid>-* entry in
  .arkor/agent/ instead of unlinking a captured path variable. This
  closes the window where a signal lands after the atomic rename placed
  the file but before the path assignment ran, and also reaps abandoned
  .tmp staging files; parallel sessions under other pids are untouched.
  New unit test covers the sweep and the other-pid exclusion.
- dev.ts now passes cwd to buildStudioApp so the server's project root
  (/api/train resolution, /api/status cwd) always matches where the
  session file lands when tests pin options.cwd; identical in production.
- Docs EN+JA: the strict-gate wording no longer claims "nothing was
  written" (first-run telemetry may create its id file); scoped to "no
  token or session file". Relocated a stale comment that predated the
  agent-mode block and updated the AGENTS.md description of the cleanup.
Follow-up hardening from an adversarial multi-angle review of the
agent-mode work (ca19a4e). Findings verified by refutation before fixing.

Probe / security (GET /api/status is now the one token-EXEMPT route,
still behind the loopback + Host guard; it is secrets-free and never
runs user code):
- The port-collision probe no longer transmits the CSRF token to the
  port occupant before confirming it is Arkor Studio (token-disclosure
  regression). It hits the token-free /api/status over 127.0.0.1
  (matching the bind, not localhost, avoiding the ::1-first hazard).
- Adoption now requires the occupant to serve THIS project (its
  /api/status cwd, realpath-compared, equals the launch project root),
  so a plain `arkor dev` in project B never silently attaches to
  project A's Studio on the same default port.
- Dropping the shared-home-token dependency also fixes the
  last-writer-wins multi-session failure.

Session-file lifecycle:
- Compute the session path up front and unlink exactly that file (+ its
  deterministic .tmp) on shutdown, instead of a pid-prefix sweep that
  would delete a co-located live session sharing the pid (two containers
  bind-mounting one project, both pid 1).
- Create the parent .arkor with the default mode; only the agent leaf is
  0700 (a recursive mkdir mode:0700 had tightened .arkor itself).
- Reap dead-pid session files on startup so a crashed prior session does
  not strand a stale token; docs recommend `ls -t` (newest) over head-1.

Test infra / telemetry:
- seedFixture realpaths the tmp dirs so the child-derived session path
  matches on macOS (/var -> /private/var) and Windows 8.3 short names.
- `dev` telemetry longRunning is now a predicate: the connect-and-exit-0
  outcome emits cli_command_completed; the serving outcome stays long.
- Harness env sanitiser + spawnDevToExit helper de-duplicate the spawn
  scaffolding; agent.spec uses them and adds a different-project case.

Also folds in the dev-command error-output cleanup this depends on:
ExpectedCliError (bin.ts prints it stack-free) backing both parseDevPort
(reject an invalid --port instead of coercing to 4000) and the agent
session-file hard-fail. Docs (EN + JA) and AGENTS.md updated.
Findings from a second adversarial review of 1692adb, verified by
refutation.

Correctness:
- Remove the startup reapDeadAgentSessions sweep. Its process.kill(pid,0)
  liveness probe is pid-namespace-local, so when the project dir is a
  shared bind-mount across containers a LIVE foreign-container session
  file looks dead (ESRCH) and would be deleted out from under it, exactly
  the multi-container case the surrounding code claims to support. The
  documented `ls -t` (newest) recipe already prevents an agent from
  picking a stale token, so the sweep was redundant and only added risk.
- The EADDRINUSE `portInUse` was a bare Error, so bin.ts dumped a
  minified dist stack for the most common dev failure. Make it an
  ExpectedCliError like the sibling agent-write hard-fail so bin.ts
  prints the actionable line alone.
- Reject a non-absolute occupant cwd in probeExistingStudio: a hostile
  local occupant returning `cwd: "."` would otherwise resolve against the
  prober's own root and bypass the same-project adoption guard.

Robustness:
- withTelemetry now guards the function-form longRunning predicate: a
  throwing predicate no longer falls through to the failure path and
  mislabels a succeeded command.
- spawnDevToExit gets a timeout+SIGKILL so a future caller that spawns a
  bind-and-serve launch cannot hang the suite.

Docs/comments:
- server.ts /api/status comment no longer claims the token guard applies
  (it is now token-exempt); notes the body must stay secrets-free.
- studio/overview.mdx (+ JA) documents the /api/status token-exempt
  carve-out; guides/cli/dev.mdx (+ JA) adds the same-project qualifier to
  the connect summary; cli/dev.mdx (+ JA) and AGENTS.md drop the removed
  reap claim.

Tests: drop the reap test (replaced by a no-sweep assertion), add a
relative-cwd probe-rejection case and parseDevPort boundary coverage.
…le studio docs

Workflow review of 28c64cc found:
- The new "relative cwd" regression test passed whether or not the
  isAbsolute guard existed: it ran with a /tmp `projectDir` while the
  worker's process.cwd() is the package dir, so realpathSync(".") never
  matched projectRoot regardless of the guard. Pin projectRoot to
  process.cwd() so the `.`-bypass actually manifests; verified the test
  now fails when the guard is reverted and passes with it.
- Stale token-required claims outside the files the prior commit touched:
  docs/concepts/studio.mdx (+ JA) said the token is required on "every
  request", and the architecture-diagram labels in concepts/studio.mdx
  and studio/overview.mdx (+ JA) still read "CSRF-token gated". Add the
  GET /api/status token-exempt carve-out to all four so the docs match
  the finalized model.
…docs)

An 8-angle audit of the whole changeset (2 adversarial verifiers per
finding, several confirmed by live mutation testing) surfaced:

Test effectiveness (the tests were not load-bearing; each fix is now
mutation-verified to fail when its target regresses):
- main.test.ts's withTelemetry mock dropped the options arg, so main.ts's
  adopt-path telemetry wiring (adopted = result.adopted; longRunning:
  () => !adopted) had ZERO coverage. The mock now captures and evaluates
  longRunning like the real wrapper; two tests pin the serving (long-
  running) vs connect-and-exit (completed) outcomes.
- The probe "non-200" test used a body that also failed the discriminator,
  so it did not isolate the res.ok guard. It now sends a matching body so
  only res.ok can reject it.
- The throwing-longRunning-predicate guard in telemetry.ts had no test;
  added one.
- The CLAUDECODE gate e2e now asserts the "nothing persisted" contract
  (no home token, no session dir).

Agent-facing URL:
- The session file url and the /api/status echo now use the 127.0.0.1
  literal the server bound (not localhost) so a non-Happy-Eyeballs agent
  client reaches it on an IPv6-localhost-first host. Human stdout / --open
  keep localhost. Docs recipe reads url from the session file.

Robustness/consistency:
- ExpectedCliError forwards ErrorOptions (preserves cause); the OAuth-only
  anonymous-bootstrap failure is now an ExpectedCliError so bin.ts prints
  it stack-free.
- The e2e agent-readiness regex requires the trailing newline so a
  chunk-split stdout line cannot yield a truncated session path.

Docs: fixed the stale --port coercion row (EN+JA) to match parseDevPort;
added .arkor/agent/ to the project-structure inventory (EN+JA); documented
the connect-adoption loopback-trust tradeoff (no token sent, so worst case
is denial/redirect, never token disclosure or RCE) in dev.mdx (EN+JA) and
AGENTS.md.
…d-bearing tests, docs)

Two more full-changeset audit rounds (8 angles x 2 verifiers each) to
convergence. Every code fix is mutation-verified: the new test fails when
the target regresses and passes when restored.

Correctness / hardening:
- probeExistingStudio reads /api/status with a hard 64 KiB BYTE cap
  (readCapped), not just the 1.5s time bound, so an untrusted loopback
  occupant cannot stream an unbounded body into the probing process.
- parseDevPort now requires a plain decimal-integer string (/^\d+$/), so
  Number()'s hex/exponent/decimal/whitespace coercions no longer bind a
  surprising port while the error copy promises "an integer".
- Non-EADDRINUSE pre-bind failures (EACCES on a privileged port, now that
  --port permits 1-1023, EADDRNOTAVAIL, ...) reject an ExpectedCliError so
  bin.ts prints one clean line instead of a minified dist stack.
- The OAuth-only anonymous-bootstrap failure is an ExpectedCliError too
  (ErrorOptions cause preserved).

Agent-facing URL: the session file url and the /api/status echo are the
127.0.0.1 literal the server bound (not localhost); the JSON example in
the docs (EN+JA) and the curl recipe now match, and the e2e asserts the
/api/status echo.

Test effectiveness (each mutation-verified load-bearing): the probe
res.ok guard, the byte cap, realpathSync canonicalization (symlink), the
adopt-path telemetry wiring, the throwing-longRunning guard, parseDevPort
strictness, the bind-error wrap, and the CLAUDECODE-gate "nothing
persisted" side effect. Corrected two comments that overstated which
sibling test pins a given behavior.

Docs: fixed the stale --port coercion row; documented the connect-
adoption loopback-trust tradeoff; added .arkor/agent/ to the project-
structure inventory; softened the "newest file is always this launch's"
claim for concurrent sessions; noted .tmp staging remnants are safe to
delete. All EN+JA.
- Assert the probe passes an AbortSignal: the 1.5s timeout was a load-
  bearing guard (Node fetch has no default timeout, so an occupant that
  accepts TCP but never responds would hang `arkor dev`) with zero
  coverage; deleting it now fails the connect test (mutation-verified).
- Assert the agent-mode busy-port path writes no `.arkor/agent` (pins the
  bind-first ordering of the session-file write).
- Tighten the e2e session-file location check to dirname === agentDir
  (not a loose prefix that would accept a sibling like `.arkor/agentX`).
- Relocate the probeExistingStudio JSDoc back above its function (the
  round-3 readCapped insertion had orphaned it above readCapped).

The remaining audit findings are the accepted, in-code-documented design
tradeoffs (token-exempt /api/status metadata disclosure; unauthenticated
connect-adoption of a loopback peer, which sends no token so the worst
case is denial/redirect, never RCE) and are not defects.
…t strictness)

- docs/cli/dev.mdx (EN+JA): the "Loopback and CSRF model" three-checks
  list still claimed the token is required on every /api/* with no
  GET /api/status carve-out, contradicting the same page. Added the
  exemption (the sibling studio/concepts pages already had it).
- Errors table (EN+JA): documented the new "Could not bind port <port>"
  ExpectedCliError (EACCES on a privileged port, EADDRNOTAVAIL).
- Test coverage: the agent hard-fail test now asserts the rejection is an
  ExpectedCliError (not just any throw); a new e2e spawns the real bin
  with an invalid --port and asserts bin.ts prints a clean one-liner with
  no V8/dist stack, covering the ExpectedCliError HANDLING branch that had
  none. Both mutation-verified.
- parseDevPort: reject leading-zero forms (`080`) too via /^[1-9]\d*$/, so
  the canonical-decimal-integer contract is exact.

Remaining audit findings are the accepted, documented tradeoffs
(token-exempt /api/status metadata; unauthenticated connect-adoption of a
loopback peer) plus one niche edge (a first-EVER `arkor dev` that is also
offline cannot adopt an existing Studio because the credential bootstrap
runs first) that is left as-is: the primary connect case is an agent
session that already bootstrapped credentials.
…tract (self-review r6)

- dev.ts: defer credential-bootstrap failure so an offline first-run launch can
  still adopt an already-running Studio; only rethrow when this process serves.
  Normalise the captured error to an Error at the catch site.
- dev.test.ts: add a fake-timer behavioral test that the 1500ms probe timeout
  actually fires (mutation-verified: an unbounded signal hangs the test), plus
  two deferred-bootstrap tests (adopt-when-offline vs reject-when-serving) and a
  redirect:manual assertion on the probe fetch.
- server.test.ts: assert the FULL /api/status endpoints array (contract), not a
  spot-check.
- server.ts: correct the studioToken JSDoc for the token-exempt /api/status.
- seedFixture.ts: wrap cleanup rmSync in try/catch (Windows EPERM/EBUSY), match
  e2e/cli.
- AGENTS.md: soften the 'ls -t newest is always the live launch' overclaim for
  concurrent sessions.
…iew r7)

Round-7 audit (adversarial, 2/2 verifiers CONFIRMED) caught that vitest fake
timers do NOT drive AbortSignal.timeout: the previous test's vi.useFakeTimers()/
advanceTimersByTimeAsync scaffolding was inert and its comment was factually
wrong. The test only passed by burning ~1.5s of REAL wall-clock, silently
adding that to every suite run.

Rewrite to spy on AbortSignal.timeout instead: assert it is called with exactly
1500 (an unbounded 'new AbortController().signal' never calls it) and fire the
abort synchronously so the test runs in ~25ms (was ~1535ms). Mutation-verified
load-bearing against BOTH an unbounded signal and a wrong duration (15000).
…verage)

Round-8 adversarial pass over the full branch (initial implementation plus
rounds 1-7). No functional or security defects survived; this lands the
remaining copy and test-hardening nits, after merging origin/main so the
branch no longer diffs as a version downgrade:

- server.ts: StudioServerOptions.url JSDoc said "Public URL the CLI
  printed (e.g. http://localhost:4000)", steering a future reader toward
  echoing the human-facing localhost form; it now states the agent-facing
  127.0.0.1 literal contract and points at the url/agentUrl split.
- docs/ja/cli/dev.mdx: the CSRF-model item 2 still presented
  ?studioToken= as a general header alternative; now scoped to the
  job-event stream with the mutation-route restriction, matching EN.
- Port error copy: "--port must be a plain decimal integer between 1 and
  65535" (main.ts, e2e assertion, docs EN+JA): the old wording was false
  for in-range non-canonical forms (080, 4e3, 500.0), which the validator
  rejects on purpose.
- AGENTS.md + dev.mdx EN/JA: the "a stale leftover is never newest"
  claim is only true for sessions that crashed before yours started; a
  later-started crashed session can leave a newer file. Reworded.
- dev.test.ts: the bootstrap-hard-fail-when-serving test now pins
  server.close() with a close spy (the swallowing try/catch made that
  branch mutation-invisible); fixed a stale above/below comment in dev.ts.
Copilot AI review requested due to automatic review settings July 24, 2026 00:51
@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing

@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR adds headless arkor dev --agent support with per-session credentials, token-free status probing, strict port and Claude Code handling, same-project Studio adoption, security checks, telemetry updates, E2E coverage, and documentation updates.

Changes

Agent-mode Studio workflow

Layer / File(s) Summary
CLI contracts and validation
packages/cli-internal/*, packages/arkor/src/cli/main.*, packages/arkor/src/bin.ts, packages/arkor/src/core/telemetry.*, e2e/cli/*
Adds strict port validation, --agent handling, Claude Code enforcement, clean expected-error output, and adoption-aware telemetry.
Agent sessions and adoption
packages/arkor/src/cli/commands/dev.*, AGENTS.md, docs/cli/dev.mdx, docs/ja/cli/dev.mdx
Adds atomic session files, cleanup, status probing, credential-bootstrap handling, and same-project port adoption.
Studio status and API security
packages/arkor/src/studio/*, docs/concepts/studio.mdx, docs/ja/concepts/studio.mdx, docs/studio/*, docs/ja/studio/*
Adds token-exempt GET /api/status, retains token checks for other API routes, validates Host and loopback access, and blocks static path traversal.
Validation and supporting documentation
e2e/studio/*, docs/*, README*, packages/arkor/README.md, packages/arkor/src/core/projectState.*
Adds agent-mode integration coverage, polling and security documentation, corrected links, command-name updates, and slug-truncation regression coverage.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CodingAgent
  participant ArkorCLI
  participant StudioServer
  participant SessionFile
  CodingAgent->>ArkorCLI: run arkor dev --agent
  ArkorCLI->>StudioServer: start headless Studio
  ArkorCLI->>SessionFile: write token, URL, port, and PID
  ArkorCLI-->>CodingAgent: print session path
  CodingAgent->>StudioServer: GET /api/status
  StudioServer-->>CodingAgent: return safe metadata
  CodingAgent->>StudioServer: call token-protected /api/* route
  StudioServer-->>CodingAgent: return API response
Loading

Possibly related PRs

  • arkorlab/arkor#193 — Both PRs modify arkor dev startup, token persistence, cleanup, and port-collision handling.

Suggested reviewers: copilot, soleil-colza

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 100.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the main change: adding agent mode to arkor dev for coding-agent HTTP control.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch eng-967
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch eng-967

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.76978% with 17 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/arkor/src/cli/commands/dev.ts 87.50% 8 Missing and 5 partials ⚠️
packages/arkor/src/studio/server.ts 81.81% 1 Missing and 1 partial ⚠️
packages/cli-internal/src/errors.ts 0.00% 2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@github-code-quality

github-code-quality Bot commented Jul 24, 2026

Copy link
Copy Markdown

Code Coverage Overview

Languages: TypeScript

TypeScript / code-coverage/arkor

The overall coverage in commit 682cab8 in the eng-967 branch remains at 98%, unchanged from commit ddfbd05 in the main branch.

Show a code coverage summary of the most impacted files.
File main ddfbd05 eng-967 682cab8 +/-
src/cli/commands/dev.ts 98% 96% -2%
src/core/telemetry.ts 98% 98% 0%
src/cli/main.ts 100% 100% 0%
src/studio/server.ts 96% 97% +1%

TypeScript / code-coverage/create-arkor

The overall coverage in commit 682cab8 in the eng-967 branch remains at 60%, unchanged from commit ddfbd05 in the main branch.

TypeScript / code-coverage/cli-internal

The overall coverage in commit 682cab8 in the eng-967 branch remains at 97%, unchanged from commit ddfbd05 in the main branch.

TypeScript / code-coverage/studio-app

The overall coverage in commit 682cab8 in the eng-967 branch remains at 53%, unchanged from commit ddfbd05 in the main branch.


Updated August 02, 2026 01:26 UTC

@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds an HTTP-driven agent mode for the local Studio server.

  • Writes a secured, per-launch session file containing the agent token and connection details.
  • Adds a token-exempt status probe and same-project Studio adoption for normal development mode.
  • Introduces strict Claude Code gating, port validation, lifecycle cleanup, telemetry handling, tests, and bilingual documentation.

Confidence Score: 5/5

The PR appears safe to merge because no blocking failures eligible for this follow-up review remain.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/arkor/src/cli/commands/dev.ts Implements agent session persistence, strict port handling, existing-Studio probing and adoption, deferred credential failure, and shutdown cleanup.
packages/arkor/src/studio/server.ts Adds the GET-only token exemption and response contract for the secrets-free Studio status endpoint.
packages/arkor/src/cli/main.ts Wires the agent option and strict port parsing into the dev command and adjusts telemetry for adopted sessions.
packages/arkor/src/core/telemetry.ts Supports deciding long-running telemetry behavior from a command's resolved result.
packages/cli-internal/src/claude-code.ts Extends Claude Code strict-mode handling to require agent mode for the dev command.
packages/cli-internal/src/errors.ts Introduces a typed expected CLI error for concise handling of routine failures.

Sequence Diagram

sequenceDiagram
  participant Agent as Coding agent
  participant CLI as arkor dev --agent
  participant Session as .arkor/agent/session-*.json
  participant Studio as Studio server
  CLI->>Studio: Bind 127.0.0.1:port
  CLI->>Session: Atomically write token, URL, port, and PID
  CLI-->>Agent: Print session file path
  Agent->>Session: Read connection details
  Agent->>Studio: GET /api/status without token
  Studio-->>Agent: Return secrets-free status
  Agent->>Studio: "Call protected /api/* with token header"
  Studio-->>Agent: Return requested response
Loading

Reviews (2): Last reviewed commit: "fix(ENG-967): full-scope audit round 16" | Re-trigger Greptile

@drift-check drift-check 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.

Documentation drift

Code Review Bot flagged 3 possible documentation drift(s). These are advisory.

  • README.md (info): The code introduces a new '--agent' flag for 'arkor dev' (as evidenced by tests in 'e2e/cli/src/arkor-dev.test.ts' and 'e2e/studio/src/harness/studioServer.ts'), but this flag is missing from the 'CLI' table in 'README.md'. — suggested: In the 'CLI' table in 'README.md', update the 'arkor dev' row to mention the '--agent' flag, e.g., 'arkor dev [--agent]' | Launch the local Studio web UI (use --agent for coding agents) |
  • README.md (info): The code in 'packages/arkor/src/cli/main.ts' adds a new '--agent' flag to the 'arkor dev' command, but this flag is not listed in the CLI table in 'README.md'. — suggested: Add the '--agent' flag to the 'arkor dev' entry in the CLI table in 'README.md', for example: 'arkor dev' | Launch the local Studio web UI (use '--agent' for headless mode).
  • README.md (info): The 'CLI' table in 'README.md' lists 'arkor dev' but does not mention the new '--agent' flag introduced in the code (e.g., in 'packages/cli-internal/src/claude-code.ts'). — suggested: In the 'CLI' table in 'README.md', update the 'arkor dev' row to: '| arkor dev | Launch the local Studio web UI (use --agent for headless agent mode) |'

Results are for commit dd5e221. On newer commits, the bot's summary comment reflects the latest run.

@drift-check

drift-check Bot commented Jul 24, 2026

Copy link
Copy Markdown

Code Review Bot

No comment/code divergences or documentation drift detected. Reviewed 48 file(s); skipped 2.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds arkor dev --agent, enabling coding agents to securely control the local Studio API while retaining browser access.

Changes:

  • Adds secure per-session token files and a token-free status endpoint.
  • Handles strict mode, port validation, collision adoption, and telemetry.
  • Adds comprehensive unit/E2E coverage and bilingual documentation.

Reviewed changes

Copilot reviewed 31 out of 31 changed files in this pull request and generated no comments.

Show a summary per file
File Description
packages/cli-internal/src/index.ts Exports new CLI helpers.
packages/cli-internal/src/errors.ts Adds expected user-facing error type.
packages/cli-internal/src/claude-code.ts Formats agent-mode guidance.
packages/cli-internal/src/claude-code.test.ts Tests guidance formatting.
packages/arkor/src/studio/server.ts Adds the status API contract.
packages/arkor/src/studio/server.test.ts Tests status security and metadata.
packages/arkor/src/core/telemetry.ts Supports outcome-dependent telemetry.
packages/arkor/src/core/telemetry.test.ts Tests telemetry predicates.
packages/arkor/src/cli/main.ts Wires agent mode and port validation.
packages/arkor/src/cli/main.test.ts Tests CLI dispatch and strict mode.
packages/arkor/src/cli/commands/dev.ts Implements sessions and port adoption.
packages/arkor/src/cli/commands/dev.test.ts Tests agent lifecycle and probing.
packages/arkor/src/bin.ts Prints expected errors without stacks.
e2e/studio/src/specs/agent.spec.ts Exercises the agent-mode contract.
e2e/studio/src/harness/studioServer.ts Adds agent-aware process helpers.
e2e/studio/src/harness/seedFixture.ts Improves temporary-path handling.
e2e/studio/src/harness/fixture.ts Adds an agent Studio fixture.
e2e/cli/src/arkor-dev.test.ts Tests run-to-exit CLI behavior.
docs/studio/overview.mdx Documents the status exemption.
docs/ja/studio/overview.mdx Mirrors Studio security documentation.
docs/guides/cli/dev.mdx Introduces agent usage.
docs/ja/guides/cli/dev.mdx Mirrors the agent usage guide.
docs/concepts/studio.mdx Updates the Studio security model.
docs/ja/concepts/studio.mdx Mirrors Studio concepts.
docs/concepts/project-structure.mdx Documents session files.
docs/ja/concepts/project-structure.mdx Mirrors session-file documentation.
docs/cli/overview.mdx Advertises agent mode and strict behavior.
docs/ja/cli/overview.mdx Mirrors the CLI overview.
docs/cli/dev.mdx Adds the full agent-mode reference.
docs/ja/cli/dev.mdx Mirrors the CLI reference.
AGENTS.md Records security-critical architecture rules.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 31 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="docs/guides/cli/dev.mdx">

<violation number="1" location="docs/guides/cli/dev.mdx:64">
P3: The pronoun "it" in "exits when it is a Studio" has an unclear antecedent. Rephrase to make the subject explicit, for example: "exits 0 when the occupant is a Studio serving the same project".</violation>
</file>

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread docs/guides/cli/dev.mdx

## Working with a coding agent?

`arkor dev --agent` runs the same server headlessly for coding agents (Claude Code and others): it writes a per-session JSON token file to `.arkor/agent/` inside the project, prints its path to stdout, and the agent drives Arkor through the `/api/*` endpoints directly (start with `GET /api/status`). Under `CLAUDECODE=1`, a plain `arkor dev` refuses to start and asks for `--agent` explicitly. The Studio UI stays available in a browser the whole time, and a second plain `arkor dev` on the same port connects to the running instance and exits when it is a Studio serving the same project (otherwise you get the usual port-in-use error). Details in the "Agent mode" section of the [`arkor dev` reference](/cli/dev).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: The pronoun "it" in "exits when it is a Studio" has an unclear antecedent. Rephrase to make the subject explicit, for example: "exits 0 when the occupant is a Studio serving the same project".

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/guides/cli/dev.mdx, line 64:

<comment>The pronoun "it" in "exits when it is a Studio" has an unclear antecedent. Rephrase to make the subject explicit, for example: "exits 0 when the occupant is a Studio serving the same project".</comment>

<file context>
@@ -59,6 +59,10 @@ bun dev --port 5000
 
+## Working with a coding agent?
+
+`arkor dev --agent` runs the same server headlessly for coding agents (Claude Code and others): it writes a per-session JSON token file to `.arkor/agent/` inside the project, prints its path to stdout, and the agent drives Arkor through the `/api/*` endpoints directly (start with `GET /api/status`). Under `CLAUDECODE=1`, a plain `arkor dev` refuses to start and asks for `--agent` explicitly. The Studio UI stays available in a browser the whole time, and a second plain `arkor dev` on the same port connects to the running instance and exits when it is a Studio serving the same project (otherwise you get the usual port-in-use error). Details in the "Agent mode" section of the [`arkor dev` reference](/cli/dev).
+
 ## Reference
</file context>
Suggested change
`arkor dev --agent` runs the same server headlessly for coding agents (Claude Code and others): it writes a per-session JSON token file to `.arkor/agent/` inside the project, prints its path to stdout, and the agent drives Arkor through the `/api/*` endpoints directly (start with `GET /api/status`). Under `CLAUDECODE=1`, a plain `arkor dev` refuses to start and asks for `--agent` explicitly. The Studio UI stays available in a browser the whole time, and a second plain `arkor dev` on the same port connects to the running instance and exits when it is a Studio serving the same project (otherwise you get the usual port-in-use error). Details in the "Agent mode" section of the [`arkor dev` reference](/cli/dev).
`arkor dev --agent` runs the same server headlessly for coding agents (Claude Code and others): it writes a per-session JSON token file to `.arkor/agent/` inside the project, prints its path to stdout, and the agent drives Arkor through the `/api/*` endpoints directly (start with `GET /api/status`). Under `CLAUDECODE=1`, a plain `arkor dev` refuses to start and asks for `--agent` explicitly. The Studio UI stays available in a browser the whole time, and a second plain `arkor dev` on the same port connects to the running instance and exits 0 when the occupant is a Studio serving the same project (otherwise you get the usual port-in-use error). Details in the "Agent mode" section of the [`arkor dev` reference](/cli/dev).

k-taro56 added 5 commits July 25, 2026 21:49
…ent (self-review r10)

Round-10 audit (re-run on the latest model) found two real items the earlier
rounds missed:

1. The adopt path's `server.close()` was NOT load-bearing: mockServeAddrInUse
   accepts a closeSpy but no call site passed one, and dev.ts wraps the call in
   try/catch, so deleting it left all 470 tests green. Inject the spy in the
   connect test and assert it. Mutation-verified: removing the close call now
   fails the test.

2. The `redirect: "manual"` rationale comment claimed a 3xx yields an
   "opaque" response. That is browser-fetch semantics; verified empirically on
   Node 24 that undici returns an ordinary response (type "basic", real 302
   status, readable body) because opaqueredirect filtering only applies to
   navigate-mode requests. The security claim is unchanged; the comment now
   states what actually happens and why `res.ok` rejects it before any body
   read.

Also refreshed a stale cross-reference to the probe-timeout test renamed in
fc84107.
…tatus to the npm README (self-review r11)

Round-11 audit found two real documentation defects (2/2 verifiers CONFIRMED
each; 18 of 24 votes refuted the other candidates):

1. The r6 change that DEFERRED the credential-bootstrap failure left
   docs/cli/dev.mdx factually wrong in two places (and identically in the JA
   mirror): launch-sequence step 1 and the `TypeError: fetch failed` errors row
   both still promised the error is rethrown and `arkor dev` "exits fast", and
   the deferral was documented nowhere. On a first-run OFFLINE launch with an
   Arkor Studio already serving this project on the port, the shipped code
   instead exits 0 with `Arkor Studio already running on <url>`, pinned by the
   dev.test.ts offline-adopt test. Both spots now state that the error is
   re-raised only if this launch actually serves, and why the deferral exists.

2. packages/arkor/README.md (the published npm page, and the file scaffolded
   AGENTS.md points coding agents at first) itemizes the Studio security model
   with an exception list detailed enough to read as exhaustive, but was never
   updated for the token-exempt `GET /api/status` this changeset added to every
   other doc surface. Added the carve-out with its rationale.

Verified the new doc claims against the code path and both bootstrap tests. The
root README.md / README.ja.md pair carries only a one-line summary with no
exception list, so it is not falsified and needs no edit.
… r12)

PRE-EXISTING inaccuracy, not introduced by ENG-967 (the two comments are
byte-identical on main and this changeset does not touch those lines). Fixed
here because the file is already in this PR's diff and the ordering the comment
misstates is the same reasoning ENG-967's EADDRINUSE/adopt path relies on.

The comment claimed `arkor dev` prints the ready line BEFORE
`http.Server.listen()` finishes binding, leaving an ECONNREFUSED window that
waitForPort exists to close. That is inverted: @hono/node-server's
`serve(opts, cb)` is `server.listen(port, hostname, cb)`, so cb IS the
listening handler and dev.ts writes the ready line from inside it, post-bind
(agent mode waits on the even-later session-file line). No such window exists.

Keep the poll (it is nearly free and guards future ordering changes) but state
what it actually is, and warn against inferring a pre-bind callback: dev.ts sets
`bound = true` inside that callback and its EADDRINUSE vs post-bind failure
split depends on that being a real post-bind signal.

e2e/studio: 16 passed.
# Conflicts:
#	packages/arkor/src/bin.ts
…cope)

The audit scope was widened per the repo owner: this branch is a full review of
the `arkor dev` / Studio / agent-mode surface, so "pre-existing" is no longer a
reason to dismiss a finding. 42 candidates, 33 survived adversarial 2-vote
verification, ~25 distinct after dedup. All fixed:

CODE / BEHAVIOUR
- dev.ts: close a bypass of the adopt-path same-project guard. `/proc/self/cwd`
  is absolute (clearing the isAbsolute check) but realpath-resolves against the
  PROBING process, so a hostile occupant could report it and be adopted without
  knowing where the project lives. Reject process-relative magic namespaces
  (/proc, /dev/fd) before resolving. Mutation-verified.
- main.ts: `arkor dev --help` printed the port default twice ('(default: 4000)'
  in the description plus Commander's own). Drop the hardcoded copy.

TEST GAPS (all mutation-verified load-bearing)
- Atomic temp+rename of the agent session file was pinned by nothing: the
  "no .tmp strays" test passes with a plain writeFile. Record rename calls via a
  hoisted pass-through mock and assert the staging path is a .tmp sibling in the
  same directory.
- The GET-only narrowing of the /api/status token exemption had no method-scope
  test; dropping `method === "GET"` left the suite green. Assert POST/PUT/PATCH/
  DELETE still 403.
- The EADDRINUSE rejection pinned only the message, not the ExpectedCliError
  class that keeps bin.ts from dumping a minified stack.

WRONG COMMENTS / DOCS (verified against code)
- `arkor train` does not exist: the CLI registers `start`, and /api/train spawns
  `arkor start`. Fixed in AGENTS.md and in two USER-FACING scaffolder warnings
  plus comments (scaffold.ts, create-arkor/bin.ts, init.ts, server.ts).
- dev.ts shutdown docblock: the /api/train kill hook is one shared 'exit'
  listener, not one registered per child.
- dev.test.ts: the home studio-token is not what makes the port-collision probe
  work (the probe sends no token at all).
- claude-code.ts: the isClaudeCode docblock described the gate as scaffolder-only
  with a --yes escape; `arkor dev` is a third consumer with neither.
- e2e studioServer.ts: readMetaToken pointed at server.ts:85-90, which now holds
  unrelated JSDoc. Reference `injectStudioToken` by name instead.

DOCS ACCURACY (EN + JA)
- concepts/studio.mdx repeated the pre-deferral "exits fast" claim.
- cli/dev.mdx: the deferral covers EVERY bootstrap failure (4xx wrap, raw
  rejection, local fs errors), not just the config-unreachable transport error;
  and the 4xx sign-in hint only fires when the deployment advertises OAuth.
- cli/dev.mdx: `--agent` is still a foreground never-exiting server an agent
  must background, and "safe on a shared dev machine" overstated the CSRF token:
  it closes the browser surface, not another local user who can GET / and read
  the token out of index.html.
- quickstart: the CLAUDECODE=1 section covered only the scaffolders while step 3
  tells the reader to run `pnpm dev`, which now exits 1 without --agent.
- studio/overview: search and status filtering ARE shipped (JobsList.tsx); only
  pagination is missing, as the JA side already said.
- guides/cli/dev: the Playground only offers completed jobs' final adapters
  (Playground.tsx filters status === 'completed'), not mid-run checkpoints.
- README.md/README.ja.md disagreed on the Host-guard scope; the guard is
  app.use('*'), so it covers static HTML too.
- ja/studio/overview omitted that mutation routes reject query-string tokens.
- Broken anchors: six #srcarkor links (Mintlify preserves '/', so the id is
  src/arkor/) and two JA links dropping the full-width parens.
- npm README: documented --agent and the CLAUDECODE gate, replaced the false
  'hot reload' claim (there is no watcher), and noted error_message is sent
  unscrubbed and can contain local paths.
- AGENTS.md: the generated-files list omitted packages/*/README.ja.md.

Verified: typecheck 10/10, lint 7/7 clean, 486 arkor + 226 cli-internal + 205
studio-app + 47 create-arkor unit tests, 16 e2e-studio, 109 e2e-cli, oxfmt and
em-dash sweeps clean.
k-taro56 added 3 commits July 26, 2026 12:41
34 candidates, 21 survived adversarial verification. Two of them were errors I
introduced in round 13.

SECURITY (Windows-only, verified empirically rather than assumed)
- server.ts: add a containment check to the token-FREE `GET *` static handler,
  which joined the request path onto assetsDir unchecked. I measured what
  actually reaches the handler on Hono 4.12 / Node 24 before claiming a vector:
    /../x      -> c.req.path "/x"       WHATWG URL parsing collapses it
    /%2e%2e/x  -> c.req.path "/x"       encoded dots are collapsed too
    /..%2fx    -> c.req.path "/..%2fx"  decodeURI keeps reserved %2F
    /..%5cx    -> c.req.path "/..\\x"    %5C decodes (not reserved)
  So this is NOT reachable on POSIX (a backslash is an ordinary filename char
  there); it escapes on Windows, where resolve() treats it as a separator. The
  test pins the contract everywhere and is mutation-detecting on Windows CI
  only, which the comment says outright rather than implying broader coverage.

- dev.ts: my round-13 /proc/self/cwd carve-out was overstated. A prefix test
  cannot see through a symlink, so it is a cheap filter, not a boundary; the
  comment now says so and points at the real reason adoption is safe (the probe
  sends no token). Renamed to isUntrustedPeerPath and added UNC rejection, so a
  peer-supplied \\host\share cannot make us open an outbound SMB/NTLM
  connection from a blocking realpathSync outside the 1.5s abort budget.

TESTS (both mutation-verified load-bearing)
- The studio-token persistence test asserted neither the warn nor that
  persistence actually failed; it passed with the warn deleted.
- /api/* token denial was only tested on single-segment paths, so narrowing the
  middleware to /api/:one left every nested route (job events, all deployment
  :id and key routes) unauthenticated with the suite green.

DOCS (all verified against the code)
- concepts/studio.mdx + README: the dev loop was documented backwards in BOTH
  directions. readManifestSummary calls runBuild, and RunTraining polls
  /api/manifest every 5s, so that poll rebuilds the very .arkor/build/index.mjs
  that Run training executes: edits go live in ~5s with no page refresh. The
  old "refresh the page or you run old code" advice and my round-13 "re-run
  arkor build" bullet were both wrong.
- Telemetry never emits three events per invocation; a serving arkor dev emits
  only cli_command_started (longRunning suppresses completed).
- "No credentials on file. Requesting an anonymous token." also prints when
  fetchCliConfig fails, not only on anon-only deployments.
- studio/overview: the event log keeps 500 entries, not 50 (jobs.mdx and
  JobDetail.tsx both say 500).
- Mirrored the round-13 search/filter sentence to the JA side; restored the
  section anchor the JA lifecycle page dropped.
- Root README/README.ja.md now mention --agent and the CLAUDECODE=1 refusal;
  docs/cli/overview.mdx (EN+JA) now discloses that error_message is sent
  unscrubbed and can contain local paths.

Also removed packages/arkor/src/studio/tmp-verify.test.ts, an untracked scratch
file an audit agent left behind.

Verified: typecheck 10/10, lint 7/7, 488 arkor + 226 cli-internal + 205
studio-app + 47 create-arkor unit, 16 e2e-studio, 109 e2e-cli, oxfmt + em-dash
sweeps clean.
…rap claims (full-scope round 15)

Full-scope audit (pre-existing issues in scope) round 15. All claims below were
re-verified against the code before editing.

- Dev loop: the docs said edits are only picked up by reloading the Run
  training page. Wrong: Overview (`#/`, which hosts Run training) chains a
  5s `/api/manifest` poll (Overview.tsx:40, RunTraining.tsx:51) and that
  rebuild writes the same `.arkor/build/index.mjs` Run training executes, so
  edits land in ~5s with no refresh. Corrected in concepts/project-structure,
  concepts/studio, quickstart and studio/jobs (EN + JA).
- Shared machines: "You can run Studio safely on a shared dev machine"
  overstated the guarantee. Loopback + Host guard close the browser and remote
  surfaces, but any local process can `GET /` and read the token out of
  index.html. Now stated as trusting every local process (EN + JA).
- Credential bootstrap: an unreachable /v1/auth/cli/config does not by itself
  stop the bootstrap (it only decides whether OAuth is advertised); bootstrap
  fails only if /v1/auth/anonymous also fails, and that failure is deferred and
  re-raised only if this launch serves, so the port-adopt path still exits 0
  (EN + JA).
- Documented the `128 + signal` exit codes (130/143/129) and the
  `~/.arkor/telemetry-id` file (0600, never created under DO_NOT_TRACK or
  ARKOR_TELEMETRY_DISABLED) (EN + JA).
- JA studio/overview: scope the Host guard to ALL requests including static
  HTML, matching EN.

typecheck 10/10, lint 7/7, unit 489 arkor + 226 cli-internal + 205 studio-app
+ 47 create-arkor, e2e-cli 113, e2e-studio 16, oxfmt clean, no em dashes.
Ten survivors from the round-16 audit (pre-existing issues in scope). Every
code fix below is mutation-verified: the production change was reverted and the
new test confirmed to fail.

Code:
- projectState: the project-slug dash-trim ran BEFORE the 40-char truncation,
  so the cut could re-introduce the trailing hyphen the trim exists to remove
  (a directory whose 40th char is a separator yielded `<39 chars>-`).
  Truncate first, then trim. Regression test added.

Tests that were not load-bearing:
- The entire /api/train child-reaping mechanism was uncovered: deleting the
  refcounted `process.on("exit", killLiveTrainChildren)` attach, the kill
  itself, or the detach left server.test.ts green, even though dev.ts's
  shutdown handlers rely on it to avoid orphaning a training child on
  `docker stop`. Added a test with a long-lived child; all three mutations
  now fail it.
- isUntrustedPeerPath: only the /proc branch was exercised (and only on
  Linux). The /dev/fd and UNC branches cannot be driven through the probe into
  a false adoption, so they are now pinned by direct unit tests, including
  near-miss paths (/procfs-notreally, /dev/fdisk) that a raw-prefix test would
  wrongly reject. Documented that the UNC branch is Windows-only because
  path.posix.normalize collapses a leading //.
- The double-cleanup assertion was `not.toThrow()`, which cannot detect
  removal of the `cleaned` guard (every fs call in the closure is already
  individually try/caught). Re-create the token with our OWN content so the
  ownership check cannot mask it, then assert the second call leaves it alone.
- e2e runCli gained an optional timeout that SIGKILLs the child. The
  CLAUDECODE gate test spawns the one command that serves forever; without a
  cap a gate regression hung the suite and left a server bound to port 4000.

Docs (EN + JA, all claims re-verified against code):
- concepts/studio scoped the deferred bootstrap re-raise to one sub-case,
  contradicting cli/dev's "every bootstrap failure is deferred".
- The fatal `TypeError: fetch failed` needs BOTH calls to fail, not just an
  unreachable /v1/auth/cli/config (that alone is never fatal).
- The wrapped 4xx message is conditional on the deployment advertising OAuth;
  added the raw `Failed to acquire anonymous token` row it falls back to.
- Added the post-bind warnings the Errors table omitted (`Studio server error
  after startup`, `Could not set permissions`). EN/JA row parity 19/19.
- project-structure claimed `arkor dev` unconditionally starts a server and
  writes the token; noted the adopt path exits 0 without either.

typecheck 10/10, lint 7/7, unit 494 arkor + 226 cli-internal + 205 studio-app
+ 47 create-arkor, e2e-cli 113, e2e-studio 16, oxfmt clean, no em dashes.
Copilot AI review requested due to automatic review settings August 2, 2026 01:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 50 out of 50 changed files in this pull request and generated no new comments.

Suppressed comments (1)

packages/arkor/src/cli/commands/dev.ts:511

  • The existing-Studio path is still blocked on this awaited cloud bootstrap. Both fetchCliConfig and requestAnonymousToken use fetch without a timeout, so if the cloud endpoint accepts a connection but never responds, a plain arkor dev never reaches the local bind failure or /api/status probe and cannot adopt the already-running Studio—the offline adoption behavior this change intends to provide. A successful bootstrap can also write credentials.json even though the launch ultimately only adopts. Attempt the bind/probe before awaiting credential bootstrap, and run bootstrap only after this process has successfully bound and will serve.

@coderabbitai coderabbitai 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.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
docs/cli/dev.mdx (1)

78-78: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

The /api/status section omits the unauthenticated cwd and pid disclosure in both languages. Both pages describe GET /api/status as secrets-free without noting that it returns the absolute project path and the process id to any local process with no token. The shared root cause is one missing sentence in the paired documentation.

  • docs/cli/dev.mdx#L78-L78: add a sentence stating that cwd and pid are readable by every local process, and cross-reference the local-user trust boundary at line 110.
  • docs/ja/cli/dev.mdx#L78-L78: add the equivalent Japanese sentence and cross-reference line 110.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/cli/dev.mdx` at line 78, The /api/status documentation must disclose
that its unauthenticated cwd and pid fields are readable by every local process.
Update the status sections at docs/cli/dev.mdx:78-78 and
docs/ja/cli/dev.mdx:78-78 with equivalent English and Japanese sentences,
respectively, and cross-reference the local-user trust-boundary discussion at
line 110 in each document.

Source: Coding guidelines

packages/arkor/src/studio/server.ts (2)

322-339: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive the /api/status endpoint list from the registered routes.

The endpoints array duplicates every API route already registered in this file. Build it from Hono’s registered routes so adding or removing a route cannot leave GET /api/status with a stale discovery contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/arkor/src/studio/server.ts` around lines 322 - 339, The /api/status
endpoints list is manually duplicated and can become stale. Update the status
response construction around the endpoints array to derive endpoint entries from
Hono’s registered routes, reusing the server’s route registry and preserving the
existing route representation so newly added or removed routes are reflected
automatically.

736-740: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The comment misdescribes noUsableStateMessage. It has one consumer, not two.

The comment says the copy is "shared by the two 'no usable scope' exits below" and that "read/mutate answer both identically". noUsableStateMessage is used only at line 777, in the no-scope 404 exit. The second exit, the stale-anonymous-scope path at line 845, returns ANON_STATE_MISMATCH_MESSAGE with status 409. The two exits return different copy and different statuses, by design.

Correct the comment so it does not send a future reader looking for a second use site.

📝 Proposed comment fix
-    // Copy shared by the two "no usable scope" exits below (missing/invalid
-    // state, and a stale anonymous scope dropped after credential resolution)
-    // so read/mutate answer both identically.
+    // Copy for the missing/invalid-state exit below (404). The other "no
+    // usable scope" exit, a stale anonymous scope dropped after credential
+    // resolution, deliberately answers differently: ANON_STATE_MISMATCH_MESSAGE
+    // with 409, because that state file exists and may be hand-maintained.
     const noUsableStateMessage =
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/arkor/src/studio/server.ts` around lines 736 - 740, Update the
comment immediately above noUsableStateMessage to describe only its single use
in the no-scope 404 exit; remove the claim that it is shared by two exits or
that read/mutate respond identically, while leaving the message and surrounding
behavior unchanged.
docs/guides/cli/dev.mdx (1)

62-64: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add the required five-tab command group for agent mode.

arkor dev --agent is an unscripted CLI subcommand. Document it in a CodeGroup with pnpm, npm, yarn, yarn run, and bun. Keep the npm command in the required npm arkor dev --agent form.

[guidelines_check]
[guidelines_check_end]

As per coding guidelines, “docs/{cli,guides/cli}/**/*.mdx: Represent unscripted CLI subcommands as a five-tab CodeGroup: pnpm, npm, yarn, yarn run, and bun; keep the npm tab as npm arkor <subcommand>.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/guides/cli/dev.mdx` around lines 62 - 64, Replace the inline `arkor dev
--agent` command in the agent-mode documentation with a five-tab `CodeGroup`
containing `pnpm`, `npm`, `yarn`, `yarn run`, and `bun`; use the required `npm
arkor dev --agent` form for the npm tab and preserve the surrounding
explanation.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/cli/overview.mdx`:
- Line 25: Scrub the home-directory prefix and project root from error messages
in the telemetry implementation at packages/arkor/src/core/telemetry.ts before
sending them to PostHog. Update docs/cli/overview.mdx lines 25-25 to describe
the scrubbed behavior instead of verbatim, unscrubbed transmission, and apply
the equivalent wording change in docs/ja/cli/overview.mdx lines 25-25.

In `@docs/ja/cli/dev.mdx`:
- Line 105: Update the Japanese documentation sentence near the CSRF token
guidance to remove the 「タイミング攻撃に対して安全です」 claim, leaving only the accurate
statement that comparison uses timingSafeEqual. Keep the remaining Japanese text
synchronized with the corresponding English documentation.

In `@packages/arkor/src/cli/commands/dev.test.ts`:
- Around line 39-59: Reset the hoisted renameCalls array in the file-wide
beforeEach so each test only observes renames from its own execution. Remove the
redundant local renameCalls.length reset in the session-file test, while
preserving the existing rename tracking and assertions.

In `@packages/arkor/src/cli/commands/dev.ts`:
- Around line 362-376: The comment in isUntrustedPeerPath must distinguish the
Windows-only forward-slash UNC form from the backslash form, which reaches the
// guard after replaceAll on every platform. In
packages/arkor/src/cli/commands/dev.ts lines 362-376, update the comment
accordingly. In packages/arkor/src/cli/commands/dev.test.ts lines 157-163, move
the String.raw backslash UNC assertion into an ungated test and leave only the
//host/share assertion behind the Windows platform gate.

In `@packages/arkor/src/studio/server.ts`:
- Around line 1168-1173: Update the asset-serving path around the target
containment check to resolve the requested target with the already imported
realpath helper before validating containment, so symlinks cannot escape
assetsRoot. Apply the same real-path containment strategy used by the /api/train
handler while preserving the existing null return for targets outside the asset
root and reading the validated path.

---

Outside diff comments:
In `@docs/cli/dev.mdx`:
- Line 78: The /api/status documentation must disclose that its unauthenticated
cwd and pid fields are readable by every local process. Update the status
sections at docs/cli/dev.mdx:78-78 and docs/ja/cli/dev.mdx:78-78 with equivalent
English and Japanese sentences, respectively, and cross-reference the local-user
trust-boundary discussion at line 110 in each document.

In `@docs/guides/cli/dev.mdx`:
- Around line 62-64: Replace the inline `arkor dev --agent` command in the
agent-mode documentation with a five-tab `CodeGroup` containing `pnpm`, `npm`,
`yarn`, `yarn run`, and `bun`; use the required `npm arkor dev --agent` form for
the npm tab and preserve the surrounding explanation.

In `@packages/arkor/src/studio/server.ts`:
- Around line 322-339: The /api/status endpoints list is manually duplicated and
can become stale. Update the status response construction around the endpoints
array to derive endpoint entries from Hono’s registered routes, reusing the
server’s route registry and preserving the existing route representation so
newly added or removed routes are reflected automatically.
- Around line 736-740: Update the comment immediately above noUsableStateMessage
to describe only its single use in the no-scope 404 exit; remove the claim that
it is shared by two exits or that read/mutate respond identically, while leaving
the message and surrounding behavior unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bafcfe2b-4dcd-44e4-9993-d5ddcd4bc73f

📥 Commits

Reviewing files that changed from the base of the PR and between dd5e221 and 682cab8.

📒 Files selected for processing (40)
  • AGENTS.md
  • README.ja.md
  • README.md
  • docs/cli/build-and-start.mdx
  • docs/cli/dev.mdx
  • docs/cli/overview.mdx
  • docs/concepts/project-structure.mdx
  • docs/concepts/studio.mdx
  • docs/guides/cli/dev.mdx
  • docs/ja/cli/build-and-start.mdx
  • docs/ja/cli/dev.mdx
  • docs/ja/cli/overview.mdx
  • docs/ja/concepts/lifecycle.mdx
  • docs/ja/concepts/project-structure.mdx
  • docs/ja/concepts/studio.mdx
  • docs/ja/quickstart.mdx
  • docs/ja/sdk/create-arkor.mdx
  • docs/ja/sdk/trainer-control.mdx
  • docs/ja/studio/jobs.mdx
  • docs/ja/studio/overview.mdx
  • docs/quickstart.mdx
  • docs/sdk/create-arkor.mdx
  • docs/studio/jobs.mdx
  • docs/studio/overview.mdx
  • e2e/cli/src/arkor-dev.test.ts
  • e2e/cli/src/spawn-cli.ts
  • e2e/studio/src/harness/studioServer.ts
  • packages/arkor/README.md
  • packages/arkor/src/bin.ts
  • packages/arkor/src/cli/commands/dev.test.ts
  • packages/arkor/src/cli/commands/dev.ts
  • packages/arkor/src/cli/commands/init.ts
  • packages/arkor/src/cli/main.ts
  • packages/arkor/src/core/projectState.test.ts
  • packages/arkor/src/core/projectState.ts
  • packages/arkor/src/studio/server.test.ts
  • packages/arkor/src/studio/server.ts
  • packages/cli-internal/src/claude-code.ts
  • packages/cli-internal/src/scaffold.ts
  • packages/create-arkor/src/bin.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (199)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: install · yarn-berry · macos-latest · node 24.12.0
  • GitHub Check: install · bun · windows-latest · node 24.0.0
  • GitHub Check: install · npm · macos-latest · node 24.0.0
  • GitHub Check: install · yarn · windows-latest · node >=24.12.0 <25
  • GitHub Check: install · pnpm-9 · windows-latest · node >=26.0.0 <27
  • GitHub Check: install · pnpm-10 · windows-latest · node >=26.0.0 <27
  • GitHub Check: install · yarn-berry · macos-latest · node 22.22.0
  • GitHub Check: install · pnpm-10 · macos-latest · node 22.22.0
  • GitHub Check: install · bun · macos-latest · node 22.22.0
  • GitHub Check: install · yarn · macos-latest · node 22.22.0
  • GitHub Check: install · bun · windows-latest · node >=26.0.0 <27
  • GitHub Check: install · pnpm-11 · macos-latest · node 22.22.0
  • GitHub Check: install · yarn · macos-latest · node >=22.22.0 <23
  • GitHub Check: install · yarn · windows-latest · node >=26.0.0 <27
  • GitHub Check: install · npm · windows-latest · node >=26.0.0 <27
  • GitHub Check: install · pnpm-11 · windows-latest · node >=26.0.0 <27
  • GitHub Check: install · yarn-berry · windows-latest · node >=26.0.0 <27
  • GitHub Check: install · npm · macos-latest · node 22.22.0
  • GitHub Check: install · npm · windows-latest · node 26.0.0
  • GitHub Check: install · yarn · windows-latest · node 26.0.0
  • GitHub Check: install · pnpm-9 · windows-latest · node 26.0.0
  • GitHub Check: install · bun · windows-latest · node 26.0.0
  • GitHub Check: install · pnpm-11 · windows-latest · node 24.12.0
  • GitHub Check: install · yarn-berry · windows-latest · node 26.0.0
  • GitHub Check: install · pnpm-10 · windows-latest · node 26.0.0
  • GitHub Check: install · pnpm-11 · windows-latest · node 26.0.0
  • GitHub Check: install · pnpm-11 · windows-latest · node >=24.12.0 <25
  • GitHub Check: install · yarn-berry · windows-latest · node >=24.12.0 <25
  • GitHub Check: install · yarn · windows-latest · node 24.12.0
  • GitHub Check: install · yarn-berry · windows-latest · node 24.0.0
  • GitHub Check: install · bun · windows-latest · node 24.12.0
  • GitHub Check: install · npm · windows-latest · node >=24.12.0 <25
  • GitHub Check: install · bun · windows-latest · node >=24.12.0 <25
  • GitHub Check: install · yarn-berry · windows-latest · node 24.12.0
  • GitHub Check: install · pnpm-10 · windows-latest · node >=24.12.0 <25
  • GitHub Check: install · pnpm-9 · windows-latest · node 24.12.0
  • GitHub Check: install · pnpm-9 · windows-latest · node >=24.12.0 <25
  • GitHub Check: install · npm · windows-latest · node 24.0.0
  • GitHub Check: install · pnpm-10 · windows-latest · node 24.0.0
  • GitHub Check: install · npm · windows-latest · node 24.12.0
  • GitHub Check: install · yarn · windows-latest · node 24.0.0
  • GitHub Check: install · pnpm-10 · windows-latest · node 24.12.0
  • GitHub Check: install · pnpm-11 · windows-latest · node 24.0.0
  • GitHub Check: install · yarn · windows-latest · node >=22.22.0 <23
  • GitHub Check: install · pnpm-9 · windows-latest · node >=22.22.0 <23
  • GitHub Check: install · pnpm-9 · windows-latest · node 24.0.0
  • GitHub Check: install · yarn-berry · windows-latest · node 22.22.0
  • GitHub Check: install · npm · windows-latest · node >=22.22.0 <23
  • GitHub Check: install · bun · windows-latest · node >=22.22.0 <23
  • GitHub Check: install · pnpm-9 · windows-latest · node 22.22.0
  • GitHub Check: install · yarn · windows-latest · node 22.22.0
  • GitHub Check: install · pnpm-10 · windows-latest · node 22.22.0
  • GitHub Check: install · bun · windows-latest · node 22.22.0
  • GitHub Check: install · pnpm-11 · windows-latest · node 22.22.0
  • GitHub Check: install · pnpm-11 · windows-latest · node >=22.22.0 <23
  • GitHub Check: install · yarn-berry · windows-latest · node >=22.22.0 <23
  • GitHub Check: install · pnpm-10 · windows-latest · node >=22.22.0 <23
  • GitHub Check: install · npm · windows-latest · node 22.22.0
  • GitHub Check: install · pnpm-9 · macos-latest · node 26.0.0
  • GitHub Check: install · yarn · macos-latest · node >=24.12.0 <25
  • GitHub Check: install · yarn · macos-latest · node >=26.0.0 <27
  • GitHub Check: install · pnpm-11 · macos-latest · node >=24.12.0 <25
  • GitHub Check: install · pnpm-11 · macos-latest · node 26.0.0
  • GitHub Check: install · bun · macos-latest · node 24.12.0
  • GitHub Check: install · npm · macos-latest · node 24.12.0
  • GitHub Check: install · pnpm-10 · macos-latest · node >=24.12.0 <25
  • GitHub Check: install · pnpm-9 · windows-latest · node >=24.12.0 <25
  • GitHub Check: install · yarn · macos-latest · node 24.0.0
  • GitHub Check: install · yarn-berry · macos-latest · node 24.0.0
  • GitHub Check: install · pnpm-10 · macos-latest · node 24.12.0
  • GitHub Check: install · bun · macos-latest · node 24.0.0
  • GitHub Check: install · pnpm-9 · macos-latest · node >=22.22.0 <23
  • GitHub Check: install · pnpm-11 · macos-latest · node 24.0.0
  • GitHub Check: install · npm · windows-latest · node >=24.12.0 <25
  • GitHub Check: install · pnpm-9 · windows-latest · node >=26.0.0 <27
  • GitHub Check: install · yarn-berry · windows-latest · node 26.0.0
  • GitHub Check: install · bun · windows-latest · node >=26.0.0 <27
  • GitHub Check: install · yarn-berry · windows-latest · node >=26.0.0 <27
  • GitHub Check: install · pnpm-10 · macos-latest · node 22.22.0
  • GitHub Check: install · pnpm-11 · windows-latest · node >=24.12.0 <25
  • GitHub Check: install · yarn · windows-latest · node >=26.0.0 <27
  • GitHub Check: install · bun · windows-latest · node >=24.12.0 <25
  • GitHub Check: install · pnpm-10 · windows-latest · node 24.12.0
  • GitHub Check: install · yarn · windows-latest · node 26.0.0
  • GitHub Check: install · npm · windows-latest · node >=26.0.0 <27
  • GitHub Check: install · pnpm-11 · windows-latest · node >=26.0.0 <27
  • GitHub Check: install · pnpm-10 · windows-latest · node >=26.0.0 <27
  • GitHub Check: install · npm · windows-latest · node 26.0.0
  • GitHub Check: install · pnpm-10 · windows-latest · node 26.0.0
  • GitHub Check: install · bun · windows-latest · node 26.0.0
  • GitHub Check: install · pnpm-9 · windows-latest · node 26.0.0
  • GitHub Check: install · pnpm-11 · windows-latest · node 26.0.0
  • GitHub Check: install · yarn · windows-latest · node >=24.12.0 <25
  • GitHub Check: install · pnpm-11 · windows-latest · node 24.12.0
  • GitHub Check: install · yarn · windows-latest · node 24.12.0
  • GitHub Check: install · bun · windows-latest · node 24.0.0
  • GitHub Check: install · yarn-berry · windows-latest · node >=24.12.0 <25
  • GitHub Check: install · pnpm-9 · windows-latest · node 24.12.0
  • GitHub Check: install · npm · windows-latest · node 24.0.0
  • GitHub Check: install · yarn-berry · windows-latest · node 24.12.0
  • GitHub Check: install · npm · windows-latest · node 24.12.0
  • GitHub Check: install · pnpm-10 · windows-latest · node >=24.12.0 <25
  • GitHub Check: install · bun · windows-latest · node 24.12.0
  • GitHub Check: install · pnpm-10 · windows-latest · node 24.0.0
  • GitHub Check: install · yarn-berry · windows-latest · node 24.0.0
  • GitHub Check: install · yarn · windows-latest · node 24.0.0
  • GitHub Check: install · pnpm-11 · windows-latest · node 24.0.0
  • GitHub Check: install · bun · windows-latest · node >=22.22.0 <23
  • GitHub Check: install · npm · windows-latest · node >=22.22.0 <23
  • GitHub Check: install · npm · windows-latest · node 22.22.0
  • GitHub Check: install · pnpm-9 · windows-latest · node 24.0.0
  • GitHub Check: install · yarn · windows-latest · node 22.22.0
  • GitHub Check: install · pnpm-10 · windows-latest · node >=22.22.0 <23
  • GitHub Check: install · pnpm-9 · windows-latest · node 22.22.0
  • GitHub Check: install · pnpm-10 · windows-latest · node 22.22.0
  • GitHub Check: install · yarn-berry · windows-latest · node 22.22.0
  • GitHub Check: install · yarn · windows-latest · node >=22.22.0 <23
  • GitHub Check: install · yarn-berry · windows-latest · node >=22.22.0 <23
  • GitHub Check: install · pnpm-11 · windows-latest · node 22.22.0
  • GitHub Check: install · pnpm-9 · windows-latest · node >=22.22.0 <23
  • GitHub Check: install · pnpm-11 · windows-latest · node >=22.22.0 <23
  • GitHub Check: install · bun · windows-latest · node 22.22.0
  • GitHub Check: copilot-pull-request-reviewer
  • GitHub Check: Seer Code Review
  • GitHub Check: typecheck · lint · test · build · windows-latest · node 26.0.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=24.8.0 <24.10.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=26.0.0 <27
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node 22.22.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=24.10.0 <24.12.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=24.0.0 <24.1.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node 26.0.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=24.5.0 <24.8.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node 24.12.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=24.8.0 <24.10.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=26.0.0 <27
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=24.1.0 <24.3.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=22.22.0 <23
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=24.0.0 <24.1.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=26.0.0 <27
  • GitHub Check: typecheck · lint · test · build · macos-latest · node 24.12.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=22.22.0 <23
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=24.12.0 <25
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=24.12.0 <25
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=24.8.0 <24.10.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node 22.22.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node 22.22.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=24.12.0 <25
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=24.3.0 <24.5.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node 24.12.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=24.3.0 <24.5.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=24.3.0 <24.5.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=24.10.0 <24.12.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node 26.0.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=24.1.0 <24.3.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=24.10.0 <24.12.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=24.0.0 <24.1.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=24.5.0 <24.8.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=24.1.0 <24.3.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=22.22.0 <23
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=24.5.0 <24.8.0
  • GitHub Check: coverage · upload to Codecov
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=24.12.0 <25
  • GitHub Check: typecheck · lint · test · build · macos-latest · node 24.12.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node 26.0.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=24.8.0 <24.10.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=24.10.0 <24.12.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=24.5.0 <24.8.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=22.22.0 <23
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=24.0.0 <24.1.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=24.3.0 <24.5.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=24.1.0 <24.3.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=24.12.0 <25
  • GitHub Check: typecheck · lint · test · build · windows-latest · node 26.0.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=24.8.0 <24.10.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node 24.12.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node 22.22.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=24.10.0 <24.12.0
  • GitHub Check: typecheck · lint · test · build · macos-latest · node >=26.0.0 <27
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=26.0.0 <27
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node 26.0.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=24.12.0 <25
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=24.8.0 <24.10.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node 22.22.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node 24.12.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=24.3.0 <24.5.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node 22.22.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=24.3.0 <24.5.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=22.22.0 <23
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=24.0.0 <24.1.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=24.5.0 <24.8.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=24.0.0 <24.1.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=26.0.0 <27
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=24.1.0 <24.3.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=22.22.0 <23
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=24.1.0 <24.3.0
  • GitHub Check: typecheck · lint · test · build · windows-latest · node >=24.5.0 <24.8.0
  • GitHub Check: typecheck · lint · test · build · ubuntu-latest · node >=24.10.0 <24.12.0
  • GitHub Check: coverage · upload to Codecov
🧰 Additional context used
📓 Path-based instructions (14)
packages/**/*.{js,jsx,ts,tsx,mjs,cjs}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Avoid using the em dash character (U+2014) in comments, string literals, and template literals in any package file; this includes CLI runtime messages, generated-file template bodies, and test names.

Files:

  • packages/create-arkor/src/bin.ts
  • packages/arkor/src/core/projectState.test.ts
  • packages/arkor/src/cli/commands/init.ts
  • packages/arkor/src/core/projectState.ts
  • packages/arkor/src/bin.ts
  • packages/arkor/src/cli/main.ts
  • packages/cli-internal/src/claude-code.ts
  • packages/arkor/src/studio/server.test.ts
  • packages/cli-internal/src/scaffold.ts
  • packages/arkor/src/studio/server.ts
  • packages/arkor/src/cli/commands/dev.ts
  • packages/arkor/src/cli/commands/dev.test.ts
**/*.{js,jsx,ts,tsx,mjs,cjs}

📄 CodeRabbit inference engine (CONTRIBUTING.ja.md)

Do not use em dashes (U+2014) in comments, string literals, or template literals anywhere in the codebase; use colons, periods, commas, parentheses, spaced hyphens (" - "), or rephrasing instead.

Files:

  • packages/create-arkor/src/bin.ts
  • packages/arkor/src/core/projectState.test.ts
  • packages/arkor/src/cli/commands/init.ts
  • packages/arkor/src/core/projectState.ts
  • packages/arkor/src/bin.ts
  • e2e/cli/src/arkor-dev.test.ts
  • e2e/cli/src/spawn-cli.ts
  • packages/arkor/src/cli/main.ts
  • packages/cli-internal/src/claude-code.ts
  • packages/arkor/src/studio/server.test.ts
  • packages/cli-internal/src/scaffold.ts
  • packages/arkor/src/studio/server.ts
  • e2e/studio/src/harness/studioServer.ts
  • packages/arkor/src/cli/commands/dev.ts
  • packages/arkor/src/cli/commands/dev.test.ts
**/*.{js,jsx,ts,tsx,json,css,html}

📄 CodeRabbit inference engine (AGENTS.md)

Use oxfmt for formatting; do not manually override its whitespace, wrapping, quote, or trailing-comma decisions.

Files:

  • packages/create-arkor/src/bin.ts
  • packages/arkor/src/core/projectState.test.ts
  • packages/arkor/src/cli/commands/init.ts
  • packages/arkor/src/core/projectState.ts
  • packages/arkor/src/bin.ts
  • e2e/cli/src/arkor-dev.test.ts
  • e2e/cli/src/spawn-cli.ts
  • packages/arkor/src/cli/main.ts
  • packages/cli-internal/src/claude-code.ts
  • packages/arkor/src/studio/server.test.ts
  • packages/cli-internal/src/scaffold.ts
  • packages/arkor/src/studio/server.ts
  • e2e/studio/src/harness/studioServer.ts
  • packages/arkor/src/cli/commands/dev.ts
  • packages/arkor/src/cli/commands/dev.test.ts
{README.md,README.ja.md,CONTRIBUTING.md,CONTRIBUTING.ja.md,docs/**/*.md,docs/**/*.mdx}

📄 CodeRabbit inference engine (AGENTS.md)

Keep paired English and Japanese documentation synchronized in the same change.

Files:

  • docs/sdk/create-arkor.mdx
  • docs/ja/sdk/create-arkor.mdx
  • docs/cli/build-and-start.mdx
  • docs/cli/overview.mdx
  • README.md
  • README.ja.md
  • docs/guides/cli/dev.mdx
  • docs/studio/overview.mdx
  • docs/ja/cli/build-and-start.mdx
  • docs/ja/studio/jobs.mdx
  • docs/ja/concepts/lifecycle.mdx
  • docs/quickstart.mdx
  • docs/ja/studio/overview.mdx
  • docs/concepts/project-structure.mdx
  • docs/ja/cli/overview.mdx
  • docs/ja/concepts/studio.mdx
  • docs/studio/jobs.mdx
  • docs/concepts/studio.mdx
  • docs/ja/concepts/project-structure.mdx
  • docs/ja/sdk/trainer-control.mdx
  • docs/ja/quickstart.mdx
  • docs/ja/cli/dev.mdx
  • docs/cli/dev.mdx
docs/**/*.mdx

📄 CodeRabbit inference engine (AGENTS.md)

Use Mintlify's verified heading IDs when creating cross-page anchors; confirm rendered IDs with curl rather than assuming GitHub slugification.

Files:

  • docs/sdk/create-arkor.mdx
  • docs/ja/sdk/create-arkor.mdx
  • docs/cli/build-and-start.mdx
  • docs/cli/overview.mdx
  • docs/guides/cli/dev.mdx
  • docs/studio/overview.mdx
  • docs/ja/cli/build-and-start.mdx
  • docs/ja/studio/jobs.mdx
  • docs/ja/concepts/lifecycle.mdx
  • docs/quickstart.mdx
  • docs/ja/studio/overview.mdx
  • docs/concepts/project-structure.mdx
  • docs/ja/cli/overview.mdx
  • docs/ja/concepts/studio.mdx
  • docs/studio/jobs.mdx
  • docs/concepts/studio.mdx
  • docs/ja/concepts/project-structure.mdx
  • docs/ja/sdk/trainer-control.mdx
  • docs/ja/quickstart.mdx
  • docs/ja/cli/dev.mdx
  • docs/cli/dev.mdx
**/*.{md,mdx,yaml,yml}

📄 CodeRabbit inference engine (AGENTS.md)

Do not format Markdown/MDX or YAML files with oxfmt; these are excluded because documentation anchors and intentional YAML layout must remain hand-managed.

Files:

  • docs/sdk/create-arkor.mdx
  • docs/ja/sdk/create-arkor.mdx
  • docs/cli/build-and-start.mdx
  • docs/cli/overview.mdx
  • README.md
  • README.ja.md
  • docs/guides/cli/dev.mdx
  • docs/studio/overview.mdx
  • docs/ja/cli/build-and-start.mdx
  • docs/ja/studio/jobs.mdx
  • docs/ja/concepts/lifecycle.mdx
  • docs/quickstart.mdx
  • docs/ja/studio/overview.mdx
  • docs/concepts/project-structure.mdx
  • docs/ja/cli/overview.mdx
  • docs/ja/concepts/studio.mdx
  • docs/studio/jobs.mdx
  • docs/concepts/studio.mdx
  • packages/arkor/README.md
  • AGENTS.md
  • docs/ja/concepts/project-structure.mdx
  • docs/ja/sdk/trainer-control.mdx
  • docs/ja/quickstart.mdx
  • docs/ja/cli/dev.mdx
  • docs/cli/dev.mdx
packages/arkor/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

packages/arkor/src/**/*.{ts,tsx}: Studio /api/* requests must validate X-Arkor-Studio-Token using a timing-safe comparison; EventSource requests may provide the token through ?studioToken=.
Keep GET /api/status token-exempt only as a secrets-free, loopback- and Host-guarded probe; it must not execute user code.
Enforce a 127.0.0.1/localhost Host-header allow-list for Studio /api/* routes and do not configure CORS.
Persist the home Studio token with mode 0600; persistence failure must only warn and must not prevent server startup.
On shutdown, delete the home token only after verifying it still contains this process's token; clean up on exit, SIGINT, SIGTERM, and SIGHUP, using exit codes 130, 143, and 129 for those signals.
In --agent mode, write { token, url, port, pid } atomically to <project>/.arkor/agent/session-<pid>-<uuid>.json, using directory mode 0700 and file mode 0600; agent-session write failure is fatal.
Capture the exact agent session path before writing it and remove only that path plus its deterministic .tmp sibling on shutdown; do not sweep or reap other session files.
Agent session URLs and /api/status URLs must use the literal http://127.0.0.1:<port>; human-facing output and --open may use localhost.
Normal mode may adopt a busy port only when unauthenticated /api/status identifies an Arkor Studio whose absolute realpath cwd equals the current project root; agent mode must never adopt an existing instance.
When CLAUDECODE=1, plain arkor dev must exit with status 1 via the ClaudeCodeStrictExit sentinel and request --agent; --agent itself does not require the environment variable.
Treat the frozen _kind: "arkor" manifest returned by createArkor as an opaque value for tooling, not as a programmable client.
arkor build must emit .arkor/build/index.mjs with bare specifiers and node_modules dependencies externalized.

Files:

  • packages/arkor/src/core/projectState.test.ts
  • packages/arkor/src/cli/commands/init.ts
  • packages/arkor/src/core/projectState.ts
  • packages/arkor/src/bin.ts
  • packages/arkor/src/cli/main.ts
  • packages/arkor/src/studio/server.test.ts
  • packages/arkor/src/studio/server.ts
  • packages/arkor/src/cli/commands/dev.ts
  • packages/arkor/src/cli/commands/dev.test.ts
packages/*/src/**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

SDK, CLI, and scaffolder logic changes must include Vitest tests; CLI flow changes should also consider an e2e/cli scenario.

Files:

  • packages/arkor/src/core/projectState.test.ts
  • packages/arkor/src/studio/server.test.ts
  • packages/arkor/src/cli/commands/dev.test.ts
docs/{cli,guides/cli}/**/*.mdx

📄 CodeRabbit inference engine (AGENTS.md)

Represent unscripted CLI subcommands as a five-tab CodeGroup: pnpm, npm, yarn, yarn run, and bun; keep the npm tab as npm arkor <subcommand>.

Files:

  • docs/cli/build-and-start.mdx
  • docs/cli/overview.mdx
  • docs/guides/cli/dev.mdx
  • docs/cli/dev.mdx
*.{yml,yaml,json,html,md}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Avoid using the em dash character (U+2014) in YAML, Markdown, JSON, and HTML files.

Files:

  • README.md
  • README.ja.md
  • AGENTS.md
e2e/**/*.test.ts

📄 CodeRabbit inference engine (AGENTS.md)

CLI and Studio E2E tests must run against the built dist/bin.mjs; standalone E2E runs require a prior root build and must not add concurrent rebuild hooks.

Files:

  • e2e/cli/src/arkor-dev.test.ts
e2e/cli/**/*.{ts,json}

📄 CodeRabbit inference engine (AGENTS.md)

Install-matrix tests must pack a fresh arkor-*.tgz per case and pass a relative file:../<tgz> override; do not replace it with an absolute Windows path or job-wide fallback.

Files:

  • e2e/cli/src/arkor-dev.test.ts
  • e2e/cli/src/spawn-cli.ts
AGENTS.md

📄 CodeRabbit inference engine (CLAUDE.md)

Maintain AGENTS.md as a living document with current agent status and architectural decisions

Files:

  • AGENTS.md
packages/arkor/src/cli/commands/dev.ts

📄 CodeRabbit inference engine (AGENTS.md)

Generate a fresh 32-byte base64url Studio CSRF token for each arkor dev launch and pass it to the Studio server.

Files:

  • packages/arkor/src/cli/commands/dev.ts
🪛 ast-grep (0.45.0)
e2e/cli/src/spawn-cli.ts

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

packages/arkor/src/studio/server.ts

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

e2e/studio/src/harness/studioServer.ts

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)


[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🪛 LanguageTool
docs/cli/overview.mdx

[style] ~25-~25: Consider using the typographical ellipsis character here instead.
Context: ...absolute paths (your project directory, ~/.arkor/...). See [Environment variables](#environ...

(ELLIPSIS)

docs/concepts/project-structure.mdx

[style] ~83-~83: The word ‘caveat’ is a legal term. To make your text as clear as possible to all readers, do not use this foreign term unless it is used with its legal meaning. Possible alternatives are “caution” or “warning”.
Context: ... exists, do not edit it by hand. One caveat after arkor logout: logout removes on...

(CAVEAT)

packages/arkor/README.md

[style] ~178-~178: Consider using the typographical ellipsis character here instead.
Context: ...(for example the project directory or ~/.arkor/...). Opt out below if that matters to you...

(ELLIPSIS)

docs/cli/dev.mdx

[style] ~50-~50: Consider a different adverb to strengthen your wording.
Context: ...edentials.json` does not exist, the CLI always tries to bootstrap an anonymous session...

(ALWAYS_CONSTANTLY)


[style] ~50-~50: This word has been used in one of the immediately preceding sentences. Using a synonym could make your text more interesting to read, unless the repetition is intentional.
Context: ...Studio that already serves this project needs no credentials of its own, so that case...

(EN_REPEATEDWORDS_NEED)


[style] ~63-~63: ‘exact same’ might be wordy. Consider a shorter alternative.
Context: ...--agent) arkor dev --agent` runs the exact same Studio server headlessly for coding age...

(EN_WORDINESS_PREMIUM_EXACT_SAME)

🔇 Additional comments (49)
packages/cli-internal/src/claude-code.ts (2)

12-19: LGTM!


272-285: 🗄️ Data Integrity & Integration

Check the formatClaudeCodeAgentModeMessage output against the documented stderr block.

The implementation emits the expected arkor dev first line, but the exact formatting is not shown in the available context. Confirm it is byte-identical to docs/cli/dev.mdx lines 89-96 and docs/ja/cli/dev.mdx lines 89-96, including the leading whitespace and trailing newline.

packages/arkor/src/cli/main.ts (2)

268-309: LGTM!


300-304: 🎯 Functional Correctness

Reject the edge-case numeric port coercion too.

parseDevPort validates the canonical /^[1-9]\d*$/ string before computing the number, so Number() cannot coerce hidden forms. "065" is rejected, and the listed samples are covered.

packages/arkor/src/bin.ts (1)

24-43: LGTM!

packages/arkor/src/cli/commands/dev.ts (2)

5-5: LGTM!

Also applies to: 53-60, 396-453, 463-466


439-449: 🩺 Stability & Availability

No change needed. readCapped reads from res.body with a reader, increments a byte counter, cancels the stream when the cap is exceeded, and only rejoins buffered chunks after each chunk is within the limit.

AGENTS.md (1)

70-77: LGTM!

Also applies to: 119-119

docs/cli/dev.mdx (2)

50-59: LGTM!

Also applies to: 61-77, 79-98, 100-141


123-141: 📐 Maintainability & Code Quality

No changes needed: the agent-mode examples already use the required five-tab CLI CodeGroup in both English and Japanese pages.

docs/ja/cli/dev.mdx (1)

50-59: LGTM!

Also applies to: 61-77, 79-98, 100-104, 106-141

packages/arkor/src/cli/commands/dev.test.ts (1)

13-14: LGTM!

Also applies to: 75-75, 109-156, 164-165, 699-706, 775-788, 808-808, 927-937, 982-1001, 1064-1081, 1114-1136, 1301-1342, 1416-1433

packages/arkor/src/cli/commands/init.ts (1)

239-239: LGTM!

packages/cli-internal/src/scaffold.ts (2)

41-41: LGTM!

Also applies to: 455-455, 494-494


485-485: 📐 Maintainability & Code Quality

No stale arkor train wording remains.

README.ja.md (1)

47-47: LGTM!

Also applies to: 155-155

README.md (1)

47-47: LGTM!

Also applies to: 148-148

packages/arkor/README.md (1)

90-90: LGTM!

Also applies to: 103-116, 127-132, 167-178

packages/arkor/src/studio/server.ts (5)

213-236: LGTM!


289-299: LGTM!

Also applies to: 349-353, 397-404, 422-432, 630-635, 678-686, 699-727, 833-846, 920-927, 969-975


1145-1147: LGTM!


876-876: LGTM!


48-59: 🗄️ Data Integrity & Integration

No client support for the scopeMissing envelope was found.

scopeMissingResponse returns { deployments: [], scopeMissing: true }, but the tracked packages/arkor source has no scopeMissing consumer. The Endpoints tab will render an empty deployment list without the intended “create your first endpoint” guidance unless the client contract and UI branch are added.

packages/arkor/src/studio/server.test.ts (6)

159-204: LGTM!


268-320: LGTM!


397-413: LGTM!

Also applies to: 469-484


826-900: LGTM!


508-585: LGTM!

Also applies to: 1362-1396, 1458-1542, 1875-1915, 2953-2978, 3035-3105


1073-1121: 📐 Maintainability & Code Quality

No change needed. The auto-anonymous bootstrap suite already saves globalThis.fetch and restores it in afterEach, including for this stale-scope test.

docs/concepts/studio.mdx (1)

17-17: LGTM!

Also applies to: 23-27, 33-35, 45-45

docs/ja/concepts/studio.mdx (1)

17-17: LGTM!

Also applies to: 25-25, 35-35, 45-45

docs/ja/studio/overview.mdx (2)

29-29: LGTM!

Also applies to: 39-40


51-51: 📐 Maintainability & Code Quality

Confirm the SPA event-log retention constant against 500 entries

The English paired page already matches the JA 500-entry event-log wording and client-side filtering wording. The only remaining required check is the SPA/client source: if 500 is not based on a retained constant, update it to match the documented 500-entry retention.

packages/create-arkor/src/bin.ts (1)

546-546: LGTM!

docs/studio/overview.mdx (1)

51-51: LGTM!

Also applies to: 63-63

e2e/studio/src/harness/studioServer.ts (1)

14-46: LGTM!

Also applies to: 92-108, 221-262, 271-316, 318-332, 385-397, 430-430, 457-457, 466-487, 514-519, 531-531

e2e/cli/src/arkor-dev.test.ts (1)

25-35: LGTM!

e2e/cli/src/spawn-cli.ts (1)

177-203: LGTM!

Also applies to: 262-262, 462-503

docs/studio/jobs.mdx (1)

12-12: LGTM!

Also applies to: 22-22

docs/ja/concepts/project-structure.mdx (1)

78-78: LGTM!

Also applies to: 86-86, 92-92

docs/cli/build-and-start.mdx (1)

115-115: 📐 Maintainability & Code Quality

Verify each added cross-page anchor against Mintlify’s rendered heading IDs.

Mintlify IDs can differ from Markdown heading text, so check every fragment before merge with requests to https://docs.arkor.ai.

  • /concepts/project-structure#src/arkor/
  • /ja/concepts/project-structure#src/arkor/
  • /ja/sdk/overview#補助ヘルパー(上級者向け)
  • /ja/sdk/trainer-control#再接続
packages/arkor/src/core/projectState.test.ts (2)

9-13: LGTM!

Also applies to: 69-80, 108-163


328-331: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Remove the duplicated test declarations.

Line 328 declares captured twice in the same scope. TypeScript reports a block-scoped redeclaration error and does not compile this test file. Remove the repeated declaration and repeated mockImplementation() call.

Proposed fix
     const captured: { name: string; slug: string }[] = [];
-    const captured: { name: string; slug: string }[] = [];
     const createProject = vi
       .fn()
       .mockImplementation(async (input: { name: string; slug: string }) => {
-      .mockImplementation(async (input: { name: string; slug: string }) => {
         captured.push({ name: input.name, slug: input.slug });
			> Likely an incorrect or invalid review comment.
packages/arkor/src/core/projectState.ts (1)

13-26: LGTM!

Also applies to: 40-86, 117-131, 157-163

docs/ja/studio/jobs.mdx (1)

12-12: LGTM!

Also applies to: 22-22

docs/concepts/project-structure.mdx (1)

83-85: LGTM!

Also applies to: 93-99

docs/ja/quickstart.mdx (1)

78-79: LGTM!

Also applies to: 151-152, 161-161, 200-200

docs/quickstart.mdx (1)

78-79: LGTM!

Also applies to: 151-152, 161-161, 200-200

docs/sdk/create-arkor.mdx (1)

73-73: 📐 Maintainability & Code Quality

Verify the rendered Mintlify heading IDs before merging.

Set DOCS_ORIGIN to the deployed preview origin and run the anchor check; the anchors /concepts/project-structure#src/arkor/, /ja/concepts/project-structure#src/arkor/, and /ja/sdk/overview#補助ヘルパー(上級者向け) must match Mintlify-rendered heading IDs.

Comment thread docs/cli/overview.mdx
- **`arkor --version` / `-V`** prints the installed SDK version (the same `SDK_VERSION` the package exports).
- **Exit codes.** Successful commands exit 0. Most commands exit non-zero on failure (uncaught throws, build errors, etc.). `arkor whoami` is a deliberate exception: it prints the failure to stdout but still exits 0 for ordinary cloud-api errors (`Failed to fetch /v1/me (<status>)`), and only sets `process.exitCode = 1` when the cloud-api responds with 426 (SDK too old). Wrapper scripts that need to detect a `whoami` failure should grep stdout, not rely on the exit code.
- **Telemetry.** Every command runs through a small instrumentation wrapper so usage events can be sent to PostHog. See [Environment variables](#environment-variables) below for the full opt-out / debug knobs.
- **Telemetry.** Every command runs through a small instrumentation wrapper so usage events can be sent to PostHog. A failing command also reports `error_name` and the first 200 characters of the error message, sent **verbatim and unscrubbed**, so it can contain local absolute paths (your project directory, `~/.arkor/...`). See [Environment variables](#environment-variables) below for the full opt-out / debug knobs.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Unscrubbed error messages reach PostHog on both documentation pages. Both pages document that the first 200 characters of a failing command's error message are sent verbatim. The shared root cause is the telemetry implementation, which attaches the raw message containing absolute paths and therefore the OS username. Scrub the home directory prefix and the project root in packages/arkor/src/core/telemetry.ts, then update both pages.

  • docs/cli/overview.mdx#L25-L25: after the implementation scrubs paths, replace "sent verbatim and unscrubbed" with the scrubbed behavior.
  • docs/ja/cli/overview.mdx#L25-L25: apply the same wording change to 「**そのまま(スクラブ無しで)**送信される」 so the pair stays synchronized.
🧰 Tools
🪛 LanguageTool

[style] ~25-~25: Consider using the typographical ellipsis character here instead.
Context: ...absolute paths (your project directory, ~/.arkor/...). See [Environment variables](#environ...

(ELLIPSIS)

📍 Affects 2 files
  • docs/cli/overview.mdx#L25-L25 (this comment)
  • docs/ja/cli/overview.mdx#L25-L25
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/cli/overview.mdx` at line 25, Scrub the home-directory prefix and
project root from error messages in the telemetry implementation at
packages/arkor/src/core/telemetry.ts before sending them to PostHog. Update
docs/cli/overview.mdx lines 25-25 to describe the scrubbed behavior instead of
verbatim, unscrubbed transmission, and apply the equivalent wording change in
docs/ja/cli/overview.mdx lines 25-25.

Source: Coding guidelines

Comment thread docs/ja/cli/dev.mdx

1. `Host` ヘッダーは `127.0.0.1` か `localhost`(DNS リバインディング対策)。
2. CSRF トークンは `X-Arkor-Studio-Token` ヘッダーか `?studioToken=...`(カスタムヘッダーを送れない `EventSource` 用)として必須。比較には `timingSafeEqual` を使用しているため、タイミング攻撃に対して安全です。
2. CSRF トークンは `X-Arkor-Studio-Token` ヘッダーとして必須。ただし 1 つだけ例外があり、機密ゼロの `GET /api/status` プローブはトークン不要です(上記参照)。ジョブイベントストリームだけは `?studioToken=...` も受け付けます(`EventSource` はカスタムヘッダーを送れないため)。変更系ルートはクエリー文字列のトークンを受け付けません。比較には `timingSafeEqual` を使用しているため、タイミング攻撃に対して安全です。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The Japanese page adds a security claim the English page does not make.

docs/cli/dev.mdx line 105 states only that the token comparison uses timingSafeEqual. This line adds 「タイミング攻撃に対して安全です」. timingSafeEqual removes the comparison-time side channel for the token compare. It does not make the endpoint safe against timing attacks as a category. Remove the added clause so the pair stays synchronized and the claim stays accurate.

📝 Proposed fix
-2. CSRF トークンは `X-Arkor-Studio-Token` ヘッダーとして必須。ただし 1 つだけ例外があり、機密ゼロの `GET /api/status` プローブはトークン不要です(上記参照)。ジョブイベントストリームだけは `?studioToken=...` も受け付けます(`EventSource` はカスタムヘッダーを送れないため)。変更系ルートはクエリー文字列のトークンを受け付けません。比較には `timingSafeEqual` を使用しているため、タイミング攻撃に対して安全です。
+2. CSRF トークンは `X-Arkor-Studio-Token` ヘッダーとして必須。ただし 1 つだけ例外があり、機密ゼロの `GET /api/status` プローブはトークン不要です(上記参照)。ジョブイベントストリームだけは `?studioToken=...` も受け付けます(`EventSource` はカスタムヘッダーを送れないため)。変更系ルートはクエリー文字列のトークンを受け付けません。トークンの比較には `timingSafeEqual` を使用します。

As per coding guidelines: "Keep paired English and Japanese documentation synchronized in the same change."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
2. CSRF トークンは `X-Arkor-Studio-Token` ヘッダーとして必須。ただし 1 つだけ例外があり、機密ゼロの `GET /api/status` プローブはトークン不要です(上記参照)。ジョブイベントストリームだけは `?studioToken=...` も受け付けます(`EventSource` はカスタムヘッダーを送れないため)。変更系ルートはクエリー文字列のトークンを受け付けません。比較には `timingSafeEqual` を使用しているため、タイミング攻撃に対して安全です
2. CSRF トークンは `X-Arkor-Studio-Token` ヘッダーとして必須。ただし 1 つだけ例外があり、機密ゼロの `GET /api/status` プローブはトークン不要です(上記参照)。ジョブイベントストリームだけは `?studioToken=...` も受け付けます(`EventSource` はカスタムヘッダーを送れないため)。変更系ルートはクエリー文字列のトークンを受け付けません。トークンの比較には `timingSafeEqual` を使用します
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ja/cli/dev.mdx` at line 105, Update the Japanese documentation sentence
near the CSRF token guidance to remove the 「タイミング攻撃に対して安全です」 claim, leaving only
the accurate statement that comparison uses timingSafeEqual. Keep the remaining
Japanese text synchronized with the corresponding English documentation.

Source: Coding guidelines

Comment on lines +39 to +59
// Record `rename` calls so a test can pin the agent session file's atomic
// temp+rename publish. `vi.spyOn` cannot be used here (ESM module namespaces
// are non-configurable), and a `vi.fn` in the factory would be neutered by the
// `vi.restoreAllMocks()` in afterEach, so use a hoisted array plus a plain
// pass-through wrapper: behaviour is byte-identical to the real fs.
const { renameCalls } = vi.hoisted(() => ({
renameCalls: [] as [string, string][],
}));
vi.mock("node:fs/promises", async (importOriginal) => {
const actual = await importOriginal<typeof FsPromises>();
return {
...actual,
rename: async (
from: Parameters<typeof actual.rename>[0],
to: Parameters<typeof actual.rename>[1],
) => {
renameCalls.push([String(from), String(to)]);
return actual.rename(from, to);
},
};
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

renameCalls is never reset between tests, so the token atomicity assertion can pass on a stale entry.

The hoisted renameCalls array lives for the whole module. Only the session-file test clears it (line 1121). The studio-token assertion at lines 703-706 searches the array by destination studioTokenPath() after runDev returns. If an earlier test in this file already renamed to that same path, the assertion is satisfied by that earlier entry even when the current launch performed a direct write. That is the exact vacuous pass the comment at lines 699-702 says the test must prevent.

Reset the array in a global beforeEach so every test observes only its own renames.

💚 Proposed fix
 const { renameCalls } = vi.hoisted(() => ({
   renameCalls: [] as [string, string][],
 }));

Add the reset next to the existing beforeEach in this file:

beforeEach(() => {
  renameCalls.length = 0;
});

Then the local clear at line 1121 becomes redundant and can be removed.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Record `rename` calls so a test can pin the agent session file's atomic
// temp+rename publish. `vi.spyOn` cannot be used here (ESM module namespaces
// are non-configurable), and a `vi.fn` in the factory would be neutered by the
// `vi.restoreAllMocks()` in afterEach, so use a hoisted array plus a plain
// pass-through wrapper: behaviour is byte-identical to the real fs.
const { renameCalls } = vi.hoisted(() => ({
renameCalls: [] as [string, string][],
}));
vi.mock("node:fs/promises", async (importOriginal) => {
const actual = await importOriginal<typeof FsPromises>();
return {
...actual,
rename: async (
from: Parameters<typeof actual.rename>[0],
to: Parameters<typeof actual.rename>[1],
) => {
renameCalls.push([String(from), String(to)]);
return actual.rename(from, to);
},
};
});
// Record `rename` calls so a test can pin the agent session file's atomic
// temp+rename publish. `vi.spyOn` cannot be used here (ESM module namespaces
// are non-configurable), and a `vi.fn` in the factory would be neutered by the
// `vi.restoreAllMocks()` in afterEach, so use a hoisted array plus a plain
// pass-through wrapper: behaviour is byte-identical to the real fs.
const { renameCalls } = vi.hoisted(() => ({
renameCalls: [] as [string, string][],
}));
vi.mock("node:fs/promises", async (importOriginal) => {
const actual = await importOriginal<typeof FsPromises>();
return {
...actual,
rename: async (
from: Parameters<typeof actual.rename>[0],
to: Parameters<typeof actual.rename>[1],
) => {
renameCalls.push([String(from), String(to)]);
return actual.rename(from, to);
},
};
});
beforeEach(() => {
renameCalls.length = 0;
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/arkor/src/cli/commands/dev.test.ts` around lines 39 - 59, Reset the
hoisted renameCalls array in the file-wide beforeEach so each test only observes
renames from its own execution. Remove the redundant local renameCalls.length
reset in the session-file test, while preserving the existing rename tracking
and assertions.

Comment on lines +362 to +376
export function isUntrustedPeerPath(p: string): boolean {
// Normalise separators so `/proc/self/../self/cwd` and Windows-style input
// cannot sneak past a naive prefix test. Note the UNC branch below is
// effectively Windows-only: `path.posix.normalize` collapses a leading `//`
// to `/`, so on POSIX it never fires. That is fine (UNC resolution is a
// Windows hazard), but do not read it as cross-platform coverage.
const norm = normalize(p).replaceAll("\\", "/");
return (
norm === "/proc" ||
norm.startsWith("/proc/") ||
norm === "/dev/fd" ||
norm.startsWith("/dev/fd/") ||
norm.startsWith("//")
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

An inaccurate comment about POSIX UNC handling produced a Windows-only test gate. isUntrustedPeerPath calls replaceAll("\\", "/") after normalize, so on POSIX a backslash UNC string becomes //host/share and the guard returns true. The comment claims the branch never fires on POSIX, and the test was gated on win32 on that basis, leaving the branch untested on Linux and macOS.

  • packages/arkor/src/cli/commands/dev.ts#L362-L376: correct the comment to state that only the forward-slash form is Windows-only, because path.posix.normalize collapses a leading //, while the backslash form still reaches the branch through replaceAll.
  • packages/arkor/src/cli/commands/dev.test.ts#L157-L163: move the String.raw\host\share`` assertion out of the it.runIf(process.platform === "win32") block into an ungated test, and keep only the `//host/share` assertion Windows-gated.
📍 Affects 2 files
  • packages/arkor/src/cli/commands/dev.ts#L362-L376 (this comment)
  • packages/arkor/src/cli/commands/dev.test.ts#L157-L163
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/arkor/src/cli/commands/dev.ts` around lines 362 - 376, The comment
in isUntrustedPeerPath must distinguish the Windows-only forward-slash UNC form
from the backslash form, which reaches the // guard after replaceAll on every
platform. In packages/arkor/src/cli/commands/dev.ts lines 362-376, update the
comment accordingly. In packages/arkor/src/cli/commands/dev.test.ts lines
157-163, move the String.raw backslash UNC assertion into an ungated test and
leave only the //host/share assertion behind the Windows platform gate.

Comment on lines +1168 to +1173
const target = resolve(assetsRoot, cleaned);
if (target !== assetsRoot && !target.startsWith(assetsRoot + sep)) {
return null;
}
try {
const file = await readFile(join(assetsDir, cleaned));
const file = await readFile(target);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

The containment check is lexical, so a symlink inside assetsDir still escapes it.

resolve does not follow symlinks. A symlink placed under assetsRoot that points outside passes target.startsWith(assetsRoot + sep), and readFile then follows it. This is the exact bypass the /api/train handler at lines 483 to 497 defends against, and it documents the reasoning there. The two file-access paths in this file now use different containment strategies, and the weaker one is the token-free route.

Honest scope: assetsDir holds the packaged Studio build inside the installed package. An attacker who can plant a symlink there can already edit the served JS, so this is hardening, not a live exploit. The asymmetry is still worth closing, because this route is reachable without the token.

If you accept the extra syscall on the asset hot path, resolve the real path before the check.

🔒️ Proposed hardening
     const target = resolve(assetsRoot, cleaned);
     if (target !== assetsRoot && !target.startsWith(assetsRoot + sep)) {
       return null;
     }
     try {
-      const file = await readFile(target);
+      // Resolve symlinks before reading, matching the `/api/train` guard: the
+      // lexical check above cannot see a link under assetsRoot that points out.
+      const real = await realpath(target);
+      if (real !== assetsRoot && !real.startsWith(assetsRoot + sep)) {
+        return null;
+      }
+      const file = await readFile(real);

realpath is already imported in this file for the /api/train guard.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const target = resolve(assetsRoot, cleaned);
if (target !== assetsRoot && !target.startsWith(assetsRoot + sep)) {
return null;
}
try {
const file = await readFile(join(assetsDir, cleaned));
const file = await readFile(target);
const target = resolve(assetsRoot, cleaned);
if (target !== assetsRoot && !target.startsWith(assetsRoot + sep)) {
return null;
}
try {
// Resolve symlinks before reading, matching the `/api/train` guard: the
// lexical check above cannot see a link under assetsRoot that points out.
const real = await realpath(target);
if (real !== assetsRoot && !real.startsWith(assetsRoot + sep)) {
return null;
}
const file = await readFile(real);
🧰 Tools
🪛 ast-grep (0.45.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn, type ChildProcess } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/arkor/src/studio/server.ts` around lines 1168 - 1173, Update the
asset-serving path around the target containment check to resolve the requested
target with the already imported realpath helper before validating containment,
so symlinks cannot escape assetsRoot. Apply the same real-path containment
strategy used by the /api/train handler while preserving the existing null
return for targets outside the asset root and reading the validated path.

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