Repository navigation
feat(providers): add zcode as a Strict harness backend - #238
Conversation
STRICT_BACKEND=zcode drives the ZCode CLI (Z.AI / GLM subscription) headless: --prompt= --output-format stream-json --mode plan, with --disallowed-tools stripping every non-read tool (measured 28 -> 11 on zcode 0.16.9) and a post-session audit against the read-only allowlist. The session cwd is a scratch dir we own, never the PR worktree: zcode loads .env from its cwd and connects stdio MCP servers declared in a project .zcode/config.json without a trust prompt, so a PR checkout as cwd would let the PR configure its reviewer. The MCP tool bridge rides that scratch dir's config (verified live: bridge read_file called and traced). zcode has no per-run model flag, so STRICT_BACKEND_MODEL is asserted against the served modelId under MODEL_MISMATCH_POLICY instead of selected; the host picks the model with /model in zcode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Both pages already describe the zcode entry; bump verified-against so check_spec_freshness --strict passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 25433ec7f1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| # -- transport contract -------------------------------------------------- | ||
| def run_session(self, req: AgentSessionRequest) -> AgentOutcome: | ||
| session = Path(tempfile.mkdtemp(prefix="imc-zcode-session-")) |
There was a problem hiding this comment.
Keep scratch directories outside the reviewed checkout
tempfile.mkdtemp() honors the process's TMPDIR, so when a runner or developer points TMPDIR inside the checkout, this session is created beneath the untrusted repository. ZCode then walks upward and discovers the repository's .env and zcode.json/.zcode/config.json, allowing a reviewed PR to launch its configured MCP commands—the exact attack the scratch directory is intended to prevent. The one-shot path has the same issue; create these directories under a trusted fixed root or reject locations within the checkout.
Useful? React with 👍 / 👎.
| _READ_TOOLS = frozenset({ | ||
| "Read", "Grep", "Glob", "TodoRead", "TodoWrite", "TaskOutput", | ||
| "ReadSessionContext", "ListWorkflowRuns", "GetWorkflowRun", | ||
| "ListSavedWorkflows", "ListModels", | ||
| }) |
There was a problem hiding this comment.
Deny host-state readers in untrusted sessions
When review text or another untrusted prompt induces a native call, this allowlist accepts ReadSessionContext, TaskOutput, and the saved/workflow-run tools in both run_session() and complete() without any root-containment check. These tools can inspect ZCode state outside the checkout, and tool_less=True only additionally disables Read, Grep, and Glob, so the one-shot path advertised as tool-less remains exposed as well. Deny these host-state tools and retain only filesystem reads whose paths can be containment-audited (with Read enabled solely for the oversized attachment case).
Useful? React with 👍 / 👎.
The zcode provider (#238) landed after the spend-threshold change and kept the old complete() signature. ModelGateway already refuses a threshold for transports that cannot stop at one before dispatch, so nothing broke, but every transport now takes max_budget_usd; zcode accepts and ignores it (stops_at_spend stays False). A test now checks every transport class, so the next provider cannot miss it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The zcode provider (#238) landed after the spend-threshold change and kept the old complete() signature. ModelGateway already refuses a threshold for transports that cannot stop at one before dispatch, so nothing broke, but every transport now takes max_budget_usd; zcode accepts and ignores it (stops_at_spend stays False). A test now checks every transport class, so the next provider cannot miss it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Summary
Adds
zcode(ZCode CLI, Z.AI / GLM subscription) as a Strict harness backend:STRICT_BACKEND=zcode. This is PR 1 of the reviewbot routing-arms plan: the later reviewbot arm table will be able to route Strict reviews tozcode/glm-5.3-flash.How it runs: headless
zcode --prompt=... --output-format stream-json --mode plan --cwd <scratch> --disallowed-tools=<every non-read tool>. The transport parsesresult.response,result.usage,session.updated.modelIdandtool.updated(kind: scheduled).Governance (measured on zcode 0.16.9, 2026-09-30)
--mode planalone still exposes a node REPL MCP, subagents (Agent, which can reach a shell), workflows, and web fetch/search.--disallowed-toolsstrips everything except reads: 28 tools → 11, all read-only.Read/Grep/Globcalls are containment-audited against checkout + run dir + session dir. This reusesaudit.contained_in, and relative glob traversal (../../etc/*) is flagged.complete()removes the native reads entirely. It keeps onlyReadwhen an oversized prompt rides an attachment, and fails the call on any read outside its scratch dir..envfrom its cwd.zcode.json/.zcode/config.jsonfrom the cwd up to the git root, and connects the stdio MCP servers declared there without a trust prompt..zcode/config.json. In a live runmcp__infermatrix-tools__read_filewas called, andbridge_trace.jsonlrecorded it in scope.resultevent, raisesRuntimeErrorwith zcode's stderr instead of reading as an empty review.Model selection: an assertion, not a selector
zcode has no per-run model flag: there is no
--model, and settingZCODE_PERSONAL_PROVIDER_CONFIG_FILEwas measured not to change the served model. It serves the host's configured default, which the host sets with/modelin the zcode TUI.STRICT_BACKEND_MODELis therefore checked case-insensitively against the servedmodelIdunderMODEL_MISMATCH_POLICY. The default policy isfail, which raisesModelMismatchError.Operational consequence for the planned 50/50 arm: each reviewbot host must set zcode's default model to
GLM-5.3-Flash. The arm'smodel: GLM-5.3-Flashthen acts as a guard that fails loudly if a host drifts.Registry spec:
capabilities = {mcp_tools, builtin_tools_off, usage_reporting}. Nodefault_modelis set, because zcode picks its own.Test plan
test/test_provider_zcode.py, 16 tests, offline fake CLI; event shapes were recorded from a live 0.16.9 run. Covers:ZCODE_DATA_BASE_DIRpassthrough--attachcomplete()containmentPYTHONPATH=$PWD/src pytest→ 1926 passed, 17 skipped.transport_for(...).run_session(...)with the real zcode CLI:read_filereturned the probe marker; audit clean; servedGLM-5.3; model check ok.Readinside the session dir, the bridge read the probe, and the audit was clean.🤖 Generated with Claude Code