Repository navigation
MCP/CLI repo read tools serialize the node's error body as a fabricated result (no HTTP status check) #123
Description
Activity
- addedsev:mediumDegraded but workaround existsDegraded but workaround existscrate:glgl — the contributor CLIgl — the contributor CLIkind:bugDefect fix — wrong or unsafe behaviorDefect fix — wrong or unsafe behaviorsubsystem:apiNode REST API request/response surfaceNode REST API request/response surface
on Jun 30, 2026 - added 9 commits that reference this issue
on Jul 11, 2026 - added 3 commits that reference this issue
on Jul 22, 2026 - added a commit that references this issue
on Aug 4, 2026 Scope note, because the sweep this issue defers turns out to reach further than reads.
All six
gl taskcommands parse before checking status, mutations included:cmd_create(crates/gl/src/task.rs:143),cmd_list(:180),cmd_view(:205),cmd_claim(:218),cmd_complete(:235),cmd_fail(:257). Each is the sameclient.post(...).await?.json().await?shape with nostatus()anywhere.send_signedreturnsOkfor a non-iCaptcha 403 (crates/gl/src/http.rs, pinned bysend_signed_returns_non_icaptcha_403_without_retry), so a denial pretty-prints the node's error JSON and the process exits 0. A script keying on the exit code reads a refused claim as a successful one.That matters more now: #275 adds a 403 for claiming a task reserved for another agent, so the claim route gains a denial class this client renders as success.
The affected-sites list here is MCP reads plus a deferred "worth a sweep when fixing", and #187 is the sanitization axis rather than this one (those six commands have no
bail!at all, so they are not among its sites). So the write side currently has no owner, while PR #186 already implements it: itsread_jsonconverts all six task commands and returns an error on non-2xx.Worth knowing if you are picking this up: #261 also rewrites
task.rsthrough a different helper,json_or_denial, and covers four of the six, leavingcmd_listandcmd_view. Both currently report as conflicting with main.- added a commit that references this issue
on Aug 11, 2026
Summary
The MCP repo read tools parse the node response with
client.get(...).await?.json().await?and never inspect the HTTP status. When a visibility-gated endpoint returns a non-2xx JSON body (the opaque404for a private repo the caller cannot read, or a5xx), that error body is serialized straight back to the agent as if it were the requested resource. The tool returnsOk, so the agent has no signal that the read was denied or failed and proceeds on a fabricated object.This is the MCP twin of the
gl repo infobug fixed in PR #113 (cmd_info), and it is distinct from #115. #115 is about the clients sending unsigned requests (owner can't authenticate); this is about not checking the status before parsing. They are orthogonal: #115's proposed.get(->.get_maybe_signed(switch lets the owner reach a200, but it does not stop a genuine non-2xx (non-owner, deleted repo, node error) from being parsed as a result.cmd_infoalready usedget_maybe_signedand still needed the separate status-check fix in #113.Reproduced by execution
A throwaway test driving
call_tool("repo_get", {owner, name}, node, None)against a mock node returning a JSON404:The dispatch returned
Okwith the error body serialized as the repo. The node is correct (get_repocallsauthorize_repo_read, repos.rs:269); the client is wrong.Affected call sites
Confirmed by reading the code (same
get(...).await?.json().await?shape, no status check):crates/gl/src/mcp.rs—repo_get(:690),:701),repo_commits(repo_tree(~:713)The same shape appears on the CLI side (
crates/gl/src/repo.rscmd_commits, thegl prread subcommands) and other MCP read arms; those were not individually reproduced here. Worth a sweep when fixing.Fix direction
Mirror the #113
cmd_infofix: keep theResponse, capturestatus, parse with.json().await.unwrap_or_default(), and return an error on non-2xx (surface the node'smessage) before serializing. Pairs naturally with #115's signing switch — apply both on the same pass so a private repo's owner gets a real200and everyone else gets a surfaced error instead of a fabricated object.Related
gl repo infoequivalent)