Retrofit of https://github.com/ccheever/llp. Ten LLP docs capture the
design rationale (trust model, engine pipeline, runtimes, config,
verification, sources/reporters, CLI, templates); 161 @ref comments link
code to the sections that explain it. ref-check (copied from upstream,
dependency-free) validates refs and metadata on every push and PR.
Every rationale claim is tagged [observed] with its evidence.
* Claude Code CLI engine: run reviews on a Claude Max/Team subscription via `claude -p`
OpenCode has no Anthropic subscription OAuth support, so reviews on a
Claude Max/Team plan were impossible. New auth mode "claude-code" runs
each pass as a single `claude -p --output-format json` subprocess:
- Engine seam reused: promptAndParse/promptAgent/assertModelsResolvable
dispatch on handle.engine; consumers unchanged.
- Subscription forced: ANTHROPIC_API_KEY/AUTH_TOKEN stripped from the
child env; CLAUDE_CODE_OAUTH_TOKEN injected from tokenEnv when set.
- Trust-isolated, read-only passes: --safe-mode, --strict-mcp-config,
dontAsk permission mode, Read path-scoped to the review tree, no
Bash/Edit/Write/Web tools.
- Rate/usage-limit detection feeds the existing RateLimitWatch/backoff;
subscription-aware concurrency cap (3) applies.
- ecr setup-auth walks through `claude setup-token`; ecr doctor checks
the CLI and subscription login.
* Cleanup pass: crash-safe spawn/stream error paths, discriminated engine dispatch, env-allowlist and process-group-kill test coverage, doc drift fixes
* Strip out-of-tree symlinks when materializing the PR-head read root
* Harden untrusted-tree trust boundary: address AI review + security audit
Fixes the review's critical finding plus five further issues surfaced by a
full-repo security audit, all in the same threat class — the reviewed PR tree
is attacker-controlled and its content must never resolve a binary, reach a raw
fs read, forge reviewer state, or leak host secrets.
Review findings:
- claudeSubscriptionActive no longer spawns a bare `claude` with inherited
cwd/env; it takes the resolved, pathInside-checked CLI path + allowlisted
env and probes from tmpdir. All three call sites routed through it.
- doctor folds routed scope configs into the engine set (resolveEngines), so
an Anthropic-only scope no longer passes with the Claude checks skipped.
- review scopes buildEngineMap to the selected agents, so a run whose passes
never use Claude no longer preflights (and fails on) a missing Claude CLI.
Audit fixes (platform-independent — live on POSIX):
- verify: confine evidencePresence's read to the review tree (pathInside),
blocking absolute/`..` reads of host secrets and the present/absent oracle.
- auth: a non-anthropic entry naming CLAUDE_CODE_OAUTH_TOKEN/ANTHROPIC_AUTH_TOKEN
is now refused, so the subscription token can't be forwarded to a foreign
provider.
- prompts: flatten finding.title/rationale through sanitizeUntrusted so an
injected finding can't forge verifier prompt boundaries.
- github reporter: identify our own comment by author + marker, not the
public marker alone, so a forged comment can't carry review state forward.
- ci: collapse subprocess argv/stderr in public PR failure comments.
Bare-name spawn hardening (defense-in-depth; the cwd-hijack is Windows-only, as
POSIX execvp does not search cwd, but the tool is published and `.` may sit in
PATH):
- Resolve git/gh to trusted absolute paths (resolveTrustedTool) before every
spawn, and taskkill via an absolute System32 path.
- The `opencode`/`claude` direct-spawn callers in doctor/setup-auth resolve an
absolute path (resolveOpencodeCli/resolveClaudeCli). startOpencode keeps the
SDK's own spawn: injecting an absolute path there would mean maintaining a
fork of the SDK's server bootstrap, not worth it for a Windows-only threat on
a POSIX-supported tool; the accepted residual is documented in place.
Regression tests added for each; typecheck, lint, fmt:check clean; 328 tests pass.
* ci: run oxfmt single-threaded to stop nondeterministic format failures
oxfmt 0.60.0's parallel checker is nondeterministic on the 2-core CI runners:
`oxfmt --check src` intermittently flags a correctly-formatted file (observed on
src/__tests__/render.test.ts — `oxfmt --write` produces no change, and the check
passes on higher core counts and re-runs), which is a data race in its worker
pool, not a real format issue. `--threads=1` serializes the run (61 files in
~90ms, so no meaningful cost) and makes the check deterministic.
- setup-auth reads the repo's auth config and walks through each credential:
a ChatGPT/Codex sign-in (runs the bundled `opencode auth login`, reuses an
existing sign-in, extracts the refresh token) and/or API keys (prints where
to create them and the exact restricted permissions), then prints the
`export` lines to add to your shell config
- doctor recommends it when a credential is missing, and offers to run it
right there on an interactive terminal
- add a `code-review-cli` bin alias so bare `npx @expo/code-review-cli <cmd>`
resolves (npx matches the bin named after the package; with only ecr/
expo-code-review it errored) — README's documented invocation now works
- README: first-time setup collapses to init → setup-auth → doctor
* Fail fast on unusable credentials, model ids, and a port/PATH collision
Debugging a local run surfaced four setup problems that all failed in the same
misleading way — silently, late, or as something unrelated:
- A malformed OAuth token makes OpenCode drop the provider from its provider
list entirely, so every configured model reports "model not found" and nothing
mentions credentials. Check the token's shape up front. The bar for refusing a
run is "this cannot be a valid credential" (whitespace, absurdly short, an API
key in oauth mode, or an OAuth token whose leading "sk-" was dropped); an
unrecognized shape only warns, since the format is an observed convention
rather than a guarantee and a false rejection blocks a working setup.
- A model id the server can't resolve failed every pass identically, N times,
after spending the run's budget. Preflight the configured ids against the
server's real provider/model list and throw once, with close matches. When the
provider we supplied a credential for is the missing one, say the credential
was refused rather than blaming the model id.
- The SDK defaults to a FIXED port 4096, so any already-running opencode (a
developer's own session) killed every local run with an opaque ServeError.
Use port 0.
- The SDK spawns a bare `opencode`, i.e. whatever is first on PATH, so a stale
global install silently replaced the version we depend on. Prepend our own
node_modules/.bin, pin the CLI and SDK exactly as the matched pair they are,
and have `ecr doctor` report the version actually in use.
* Name rate limiting as a cause when a credential is refused or a pass goes silent
A throttled subscription credential (HTTP 429) is refused exactly like an invalid
one, and OpenCode surfaces neither: it drops the provider, so every model looks
nonexistent, and it retries a 429 mid-request internally, so a throttled pass
reaches us as pure silence rather than an error (opencode#17583).
Both messages previously blamed the only cause they knew about — the token and a
wedged connection — which sends people to re-issue a perfectly good credential or
hunt for a bug here. Offer rate limiting first, with the one-line curl that tells
429 (wait) from 401 (the credential really is bad).
Config-driven, multi-agent AI code reviewer engine. Extracted from
expo/eas-cli (PR #4022, packages/expo-code-review-cli) into its own repo so
it can be published as @expo/code-review-cli and iterated independently.
The engine is repo-agnostic: consuming repos supply their own agents and
settings under .expo-code-review/. Built on the OpenCode SDK.