Conversation
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.
|
ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing |
WalkthroughThe PR adds headless ChangesAgent-mode Studio workflow
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
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
✨ Simplify code
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. Comment |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
Code Coverage OverviewLanguages: TypeScript TypeScript / code-coverage/arkorThe overall coverage in commit 682cab8 in the Show a code coverage summary of the most impacted files.
TypeScript / code-coverage/create-arkorThe overall coverage in commit 682cab8 in the TypeScript / code-coverage/cli-internalThe overall coverage in commit 682cab8 in the TypeScript / code-coverage/studio-appThe overall coverage in commit 682cab8 in the Updated |
Greptile SummaryAdds an HTTP-driven agent mode for the local Studio server.
Confidence Score: 5/5The PR appears safe to merge because no blocking failures eligible for this follow-up review remain. No blocking failure remains.
|
| 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
Reviews (2): Last reviewed commit: "fix(ENG-967): full-scope audit round 16" | Re-trigger Greptile
There was a problem hiding this comment.
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.
Code Review BotNo comment/code divergences or documentation drift detected. Reviewed 48 file(s); skipped 2. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
|
|
||
| ## 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). |
There was a problem hiding this comment.
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>
| `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). |
…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.
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.
There was a problem hiding this comment.
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
fetchCliConfigandrequestAnonymousTokenusefetchwithout a timeout, so if the cloud endpoint accepts a connection but never responds, a plainarkor devnever reaches the local bind failure or/api/statusprobe and cannot adopt the already-running Studio—the offline adoption behavior this change intends to provide. A successful bootstrap can also writecredentials.jsoneven 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.
There was a problem hiding this comment.
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 winThe
/api/statussection omits the unauthenticatedcwdandpiddisclosure in both languages. Both pages describeGET /api/statusas 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 thatcwdandpidare 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 winDerive the
/api/statusendpoint list from the registered routes.The
endpointsarray duplicates every API route already registered in this file. Build it from Hono’s registered routes so adding or removing a route cannot leaveGET /api/statuswith 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 winThe 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".
noUsableStateMessageis used only at line 777, in the no-scope 404 exit. The second exit, the stale-anonymous-scope path at line 845, returnsANON_STATE_MISMATCH_MESSAGEwith 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 winAdd the required five-tab command group for agent mode.
arkor dev --agentis an unscripted CLI subcommand. Document it in aCodeGroupwithpnpm,npm,yarn,yarn run, andbun. Keep the npm command in the requirednpm arkor dev --agentform.[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, andbun; keep the npm tab asnpm 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
📒 Files selected for processing (40)
AGENTS.mdREADME.ja.mdREADME.mddocs/cli/build-and-start.mdxdocs/cli/dev.mdxdocs/cli/overview.mdxdocs/concepts/project-structure.mdxdocs/concepts/studio.mdxdocs/guides/cli/dev.mdxdocs/ja/cli/build-and-start.mdxdocs/ja/cli/dev.mdxdocs/ja/cli/overview.mdxdocs/ja/concepts/lifecycle.mdxdocs/ja/concepts/project-structure.mdxdocs/ja/concepts/studio.mdxdocs/ja/quickstart.mdxdocs/ja/sdk/create-arkor.mdxdocs/ja/sdk/trainer-control.mdxdocs/ja/studio/jobs.mdxdocs/ja/studio/overview.mdxdocs/quickstart.mdxdocs/sdk/create-arkor.mdxdocs/studio/jobs.mdxdocs/studio/overview.mdxe2e/cli/src/arkor-dev.test.tse2e/cli/src/spawn-cli.tse2e/studio/src/harness/studioServer.tspackages/arkor/README.mdpackages/arkor/src/bin.tspackages/arkor/src/cli/commands/dev.test.tspackages/arkor/src/cli/commands/dev.tspackages/arkor/src/cli/commands/init.tspackages/arkor/src/cli/main.tspackages/arkor/src/core/projectState.test.tspackages/arkor/src/core/projectState.tspackages/arkor/src/studio/server.test.tspackages/arkor/src/studio/server.tspackages/cli-internal/src/claude-code.tspackages/cli-internal/src/scaffold.tspackages/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.tspackages/arkor/src/core/projectState.test.tspackages/arkor/src/cli/commands/init.tspackages/arkor/src/core/projectState.tspackages/arkor/src/bin.tspackages/arkor/src/cli/main.tspackages/cli-internal/src/claude-code.tspackages/arkor/src/studio/server.test.tspackages/cli-internal/src/scaffold.tspackages/arkor/src/studio/server.tspackages/arkor/src/cli/commands/dev.tspackages/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.tspackages/arkor/src/core/projectState.test.tspackages/arkor/src/cli/commands/init.tspackages/arkor/src/core/projectState.tspackages/arkor/src/bin.tse2e/cli/src/arkor-dev.test.tse2e/cli/src/spawn-cli.tspackages/arkor/src/cli/main.tspackages/cli-internal/src/claude-code.tspackages/arkor/src/studio/server.test.tspackages/cli-internal/src/scaffold.tspackages/arkor/src/studio/server.tse2e/studio/src/harness/studioServer.tspackages/arkor/src/cli/commands/dev.tspackages/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.tspackages/arkor/src/core/projectState.test.tspackages/arkor/src/cli/commands/init.tspackages/arkor/src/core/projectState.tspackages/arkor/src/bin.tse2e/cli/src/arkor-dev.test.tse2e/cli/src/spawn-cli.tspackages/arkor/src/cli/main.tspackages/cli-internal/src/claude-code.tspackages/arkor/src/studio/server.test.tspackages/cli-internal/src/scaffold.tspackages/arkor/src/studio/server.tse2e/studio/src/harness/studioServer.tspackages/arkor/src/cli/commands/dev.tspackages/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.mdxdocs/ja/sdk/create-arkor.mdxdocs/cli/build-and-start.mdxdocs/cli/overview.mdxREADME.mdREADME.ja.mddocs/guides/cli/dev.mdxdocs/studio/overview.mdxdocs/ja/cli/build-and-start.mdxdocs/ja/studio/jobs.mdxdocs/ja/concepts/lifecycle.mdxdocs/quickstart.mdxdocs/ja/studio/overview.mdxdocs/concepts/project-structure.mdxdocs/ja/cli/overview.mdxdocs/ja/concepts/studio.mdxdocs/studio/jobs.mdxdocs/concepts/studio.mdxdocs/ja/concepts/project-structure.mdxdocs/ja/sdk/trainer-control.mdxdocs/ja/quickstart.mdxdocs/ja/cli/dev.mdxdocs/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.mdxdocs/ja/sdk/create-arkor.mdxdocs/cli/build-and-start.mdxdocs/cli/overview.mdxdocs/guides/cli/dev.mdxdocs/studio/overview.mdxdocs/ja/cli/build-and-start.mdxdocs/ja/studio/jobs.mdxdocs/ja/concepts/lifecycle.mdxdocs/quickstart.mdxdocs/ja/studio/overview.mdxdocs/concepts/project-structure.mdxdocs/ja/cli/overview.mdxdocs/ja/concepts/studio.mdxdocs/studio/jobs.mdxdocs/concepts/studio.mdxdocs/ja/concepts/project-structure.mdxdocs/ja/sdk/trainer-control.mdxdocs/ja/quickstart.mdxdocs/ja/cli/dev.mdxdocs/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.mdxdocs/ja/sdk/create-arkor.mdxdocs/cli/build-and-start.mdxdocs/cli/overview.mdxREADME.mdREADME.ja.mddocs/guides/cli/dev.mdxdocs/studio/overview.mdxdocs/ja/cli/build-and-start.mdxdocs/ja/studio/jobs.mdxdocs/ja/concepts/lifecycle.mdxdocs/quickstart.mdxdocs/ja/studio/overview.mdxdocs/concepts/project-structure.mdxdocs/ja/cli/overview.mdxdocs/ja/concepts/studio.mdxdocs/studio/jobs.mdxdocs/concepts/studio.mdxpackages/arkor/README.mdAGENTS.mddocs/ja/concepts/project-structure.mdxdocs/ja/sdk/trainer-control.mdxdocs/ja/quickstart.mdxdocs/ja/cli/dev.mdxdocs/cli/dev.mdx
packages/arkor/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
packages/arkor/src/**/*.{ts,tsx}: Studio/api/*requests must validateX-Arkor-Studio-Tokenusing a timing-safe comparison;EventSourcerequests may provide the token through?studioToken=.
KeepGET /api/statustoken-exempt only as a secrets-free, loopback- and Host-guarded probe; it must not execute user code.
Enforce a127.0.0.1/localhostHost-header allow-list for Studio/api/*routes and do not configure CORS.
Persist the home Studio token with mode0600; 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--agentmode, write{ token, url, port, pid }atomically to<project>/.arkor/agent/session-<pid>-<uuid>.json, using directory mode0700and file mode0600; agent-session write failure is fatal.
Capture the exact agent session path before writing it and remove only that path plus its deterministic.tmpsibling on shutdown; do not sweep or reap other session files.
Agent session URLs and/api/statusURLs must use the literalhttp://127.0.0.1:<port>; human-facing output and--openmay uselocalhost.
Normal mode may adopt a busy port only when unauthenticated/api/statusidentifies an Arkor Studio whose absolute realpathcwdequals the current project root; agent mode must never adopt an existing instance.
WhenCLAUDECODE=1, plainarkor devmust exit with status 1 via theClaudeCodeStrictExitsentinel and request--agent;--agentitself does not require the environment variable.
Treat the frozen_kind: "arkor"manifest returned bycreateArkoras an opaque value for tooling, not as a programmable client.
arkor buildmust emit.arkor/build/index.mjswith bare specifiers andnode_modulesdependencies externalized.
Files:
packages/arkor/src/core/projectState.test.tspackages/arkor/src/cli/commands/init.tspackages/arkor/src/core/projectState.tspackages/arkor/src/bin.tspackages/arkor/src/cli/main.tspackages/arkor/src/studio/server.test.tspackages/arkor/src/studio/server.tspackages/arkor/src/cli/commands/dev.tspackages/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/cliscenario.
Files:
packages/arkor/src/core/projectState.test.tspackages/arkor/src/studio/server.test.tspackages/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, andbun; keep the npm tab asnpm arkor <subcommand>.
Files:
docs/cli/build-and-start.mdxdocs/cli/overview.mdxdocs/guides/cli/dev.mdxdocs/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.mdREADME.ja.mdAGENTS.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-*.tgzper case and pass a relativefile:../<tgz>override; do not replace it with an absolute Windows path or job-wide fallback.
Files:
e2e/cli/src/arkor-dev.test.tse2e/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 devlaunch 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 & IntegrationCheck the
formatClaudeCodeAgentModeMessageoutput against the documented stderr block.The implementation emits the expected
arkor devfirst line, but the exact formatting is not shown in the available context. Confirm it is byte-identical todocs/cli/dev.mdxlines 89-96 anddocs/ja/cli/dev.mdxlines 89-96, including the leading whitespace and trailing newline.packages/arkor/src/cli/main.ts (2)
268-309: LGTM!
300-304: 🎯 Functional CorrectnessReject the edge-case numeric port coercion too.
parseDevPortvalidates the canonical/^[1-9]\d*$/string before computing the number, soNumber()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 & AvailabilityNo change needed.
readCappedreads fromres.bodywith 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 QualityNo 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 QualityNo stale
arkor trainwording 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 & IntegrationNo client support for the
scopeMissingenvelope was found.
scopeMissingResponsereturns{ deployments: [], scopeMissing: true }, but the tracked packages/arkor source has noscopeMissingconsumer. 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 QualityNo change needed. The
auto-anonymous bootstrapsuite already savesglobalThis.fetchand restores it inafterEach, 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 QualityConfirm 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
500is 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 QualityVerify 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 winRemove the duplicated test declarations.
Line 328 declares
capturedtwice in the same scope. TypeScript reports a block-scoped redeclaration error and does not compile this test file. Remove the repeated declaration and repeatedmockImplementation()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 QualityVerify the rendered Mintlify heading IDs before merging.
Set
DOCS_ORIGINto 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.
| - **`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. |
There was a problem hiding this comment.
🔒 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
|
|
||
| 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` を使用しているため、タイミング攻撃に対して安全です。 |
There was a problem hiding this comment.
📐 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.
| 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
| // 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); | ||
| }, | ||
| }; | ||
| }); |
There was a problem hiding this comment.
🎯 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.
| // 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.
| 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("//") | ||
| ); | ||
| } |
There was a problem hiding this comment.
🎯 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, becausepath.posix.normalizecollapses a leading//, while the backslash form still reaches the branch throughreplaceAll.packages/arkor/src/cli/commands/dev.test.ts#L157-L163: move theString.raw\host\share`` assertion out of theit.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.
| 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); |
There was a problem hiding this comment.
🔒 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.
| 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.
Follow-up to #141 (CLAUDECODE strict mode for the scaffolders). ENG-967.
What
arkor dev --agentruns 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
{ token, url, port, pid }) to a unique per-session file at<project>/.arkor/agent/session-<pid>-<uuid>.json(agent dir0700, file0600, atomic temp+rename; parent.arkorkeeps the default mode). The path is printed to stdout as the stable greppable lineArkor 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.tmpsibling; 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.CLAUDECODE=1, plainarkor devprints a re-invocation block to stderr and exits 1 via theClaudeCodeStrictExitsentinel, asking for--agent. The flag itself needs no env var, so other coding agents opt in the same way.arkor devon a busy port probeshttp://127.0.0.1:<port>/api/statuswith 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 (cwdmust be absolute, compared via realpath): it printsArkor 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 reachPOST /api/train; the worst case is a nuisance denial or--openredirect, and the trust model is documented.arkor dev --agentalways hard-errors on a busy port (an agent session needs its own server and session file) and advises--port.http://127.0.0.1:<port>literal (session file and/api/statusecho), because an agent's HTTP client may not do Happy-Eyeballs; the human-facing stdout line and--openkeeplocalhost.--portnow requires a plain decimal integer in 1..65535 (rejected up front instead of coercing); newExpectedCliErrorin 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; telemetrylongRunningaccepts a post-resolve predicate so the short-lived adopt outcome emitscli_command_completed; credential-bootstrap hard failures are deferred to serve time so an offline first run can still adopt a running Studio.Tests
/api/statuscontract (including the full endpoint list and secrets-free assertion), telemetry predicate; cli-internal formatter.--agentin help; ExpectedCliError prints as a one-liner with no stack.CLAUDECODEso the gate cannot leak in from a Claude Code session.Docs
EN and JA in lockstep:
cli/devreference (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 devso coding agents can drive Studio over HTTP without the browser, and add a token‑exempt/api/statusprobe 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(dir0700, file0600, atomic temp+rename) and prints “Arkor Studio agent session file: ”.http://127.0.0.1:<port>; the human-facing line and--openkeeplocalhost.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=1, plainarkor devexits with a message asking for--agent(no token/session file written).arkor devadopts only when the occupant is Arkor Studio serving the same project;--agentnever adopts and errors with a hint to use--port.Refactors
1..65535, plain decimal). Bind failures surface as one‑lineExpectedCliErrors (no stack).127.0.0.1, 1.5s timeout, 64 KiB body cap,redirect: "manual", and rejects relative,/proc/*,/dev/fd/*, or UNC\\host\share\...occupantcwdbefore realpath comparison.GET *route against path traversal on Windows.devreports completed when it connects-and-exits; function‑formlongRunningis guarded..tmpsibling; no pid sweep. Server closes on adopt.-./api/statuscarve‑out, dev loop picks up source edits via 5s polling, adoption trust model, errors, CLAUDECODE gate, and npmpackages/arkor/README.md.Written for commit 682cab8. Summary will update on new commits.
Summary by CodeRabbit
New Features
arkor dev --agentfor headless Studio sessions with secure per-session connection files.Bug Fixes
Documentation