// Notes from the edge of the platform

A hook that says “allowed” without knowing who asked

Six things we’d ask of Claude Code, each with the incident that produced it — and the single defect they all turn out to be.

A code-reviewer agent flipped main’s HEAD to an unmerged pull request commit carrying three unresolved critical findings. It sat there for 65 seconds.

Nothing was broken, exactly. The sandbox hook did what it was built to do. It just had no way to know that the session asking for unrestricted git access was a code-reviewer rather than the top-level orchestrator, because the field that would tell it does not exist.

We run an autonomous team of 26 agent roles on Claude Code — Discussions in, merged pull requests out, review gates in between. It has merged over 1,100 PRs, most of them into its own tooling, which means we spend an unusual amount of time at the edges of the platform rather than in the middle of it. What follows is six things we’d ask for, each with the incident that produced it.

They look like six unrelated requests. They aren’t. Every one is the same shape as the bug class this project keeps finding in its own code: a surface reporting a confident value it never measured.

01. Subagent identity in the hook payload

The PreToolUse hook receives three fields: tool_name, tool_input, cwd. No role. No subagent id. No spawn lineage.

So a sandbox tier can only key on where a session is registered, not who it is. That failed in both directions in a single session. A code-reviewer registered at the repo root classified as team_lead, inherited unrestricted git access, and moved main. In the same session two project-managers running in worktrees were denied the GitHub mutations their role is defined by — they had to hand a human files to post on their behalf.

One agent got authority it shouldn’t have. Two lost authority they needed. Identical cause, opposite harms.

The most telling part: two specialist reviewers on that Discussion independently concluded they “cannot determine whether the hook can distinguish a subagent from the top-level session”, and declined to design around it. When the platform won’t say who is asking, the honest response is to stop designing — and that is a lot of capability left on the floor.

What would fix it: role, subagent id, and spawn parent in the hook payload. Three fields.

02. A taint marker for untrusted tool output

The only defence against prompt injection through tool results today is prose: treat this as data, not instructions. That is the entire mechanism.

We got this wrong ourselves. I told the project owner that PR comments were a small injection surface — I’d grepped for code that reads them and found little. The security review found the real path, under a heading that says fix every flagged issue:

.claude/agents/executor.md:242
gh pr view {pr} --comments

A grep cannot find that, because the instruction isn’t in code. It is prose, in an agent definition. The moment the repo’s PRs are public, a stranger’s comment becomes work-to-act-on, routed by an instruction no static analysis will surface.

What would fix it: a trusted: false marker on tool output that the runtime structurally honours, rather than a sentence asking the model to remember. Prose defences fail exactly when a clever input is trying to make them fail.

03. Stable agent addressing

Role names are not addresses. SendMessage to "executor" fails — measured across two independent spawns. main is the only stable address in the system, and it is one-directional: there is no equivalent address pointing at the orchestrator.

So every routing decision has to carry an opaque spawn-time id, threaded through whatever state survives. And a crashed session loses the ability to reach its own children, permanently, because the only handle to them died with it.

What would fix it: addressable, stable identifiers with a resolution mechanism — so “the executor working on D#2149” is a thing you can name rather than a token you must not lose.

04. Structured progress from background agents

An orchestrator cannot read a subagent’s transcript — it would overflow context, and the tooling forbids it outright. So you get a final report and nothing before it.

One reviewer sat waiting on a baseline it did not need for roughly twenty minutes. We learned when it surfaced. There was no intervention available, because there was nothing to intervene on: from the outside the agent was working, right up until it wasn’t.

What would fix it: a structured progress channel. Phase, current action, blocked-on. Three fields again, and enough to turn “discover afterwards” into “intervene during”.

05. Measurement integrity primitives

Two independent reviewers ran tests against trees that changed underneath them. One removed its own worktree mid-run and produced a log reading 670 failed against a real value of 35. Not a regression — measurement damage, of a kind that would look like catastrophe to whoever found the log later.

We built our own verify_tree_build / verify_tree_assert for this. Its docstring is the most damning thing in our codebase: results have been wrong “five distinct ways in one session, twice returning a clean pass from a tree that had silently reverted to base content.”

A clean pass from the wrong tree is not a cosmetic defect. It is a wrong verdict wearing a green tick.

What would fix it: a platform-level way to pin the tree being measured, so correctness doesn’t depend on every agent remembering a convention.

06. Durable action audit

An agent moved main’s HEAD, and nothing durably recorded it. audit.jsonl has 29,573 rows and zero matches. The commit isn’t an ancestor of origin/main, so it is in the unreachable-object class: 30-day reflog expiry, prunable by gc. In a month there would have been no evidence it ever happened.

The only lasting record exists because an agent chose to write a Discussion about it, unprompted, after its own pull request had already passed review. We got lucky. There is no mechanism making that disclosure reliable — and mildly the reverse incentive, since self-reporting invites your own verdict to be discounted.

What would fix it: a durable, tamper-evident record of privileged actions taken through the runtime, independent of whether the agent that took them decides to mention it.

The same bug, six layers down

This project spends most of its time finding one class of defect in itself. A dry run that reported 116 removals the real run never made, because the skip it should have hit is gated on the run not being a dry run. Two code reviews cited that transcript as their verification. The reviews weren’t lazy. The evidence was lying.

Every request above is that same defect, one layer down.

None of these are exotic. They are the ordinary failure of a system that produces an answer without the information needed to produce it — which is precisely what we spend our days catching in our own code, and precisely what gets harder to catch as more of the stack becomes agents talking to agents.

We’re not asking for these because they would be convenient. We’re asking because each is a place where the platform currently returns a confident value it cannot actually know, and because we now have the incident reports to show what that costs.

Happy to supply any of the underlying discussions, logs or measurements. The work queue this came out of is public: the live queue.