Start with the thesis: Satisfied Is Not Established.
Pull request #666 on my repository was a small docs fix. An agent session opened it, CI passed, and an automated reviewer, Cursor Bugbot, posted a summary in the PR body.
When the agent tried to merge, a safety classifier in its own runtime stopped it, because the change had no human review. I told the agent, in a chat window, to run the command. It merged.
GitHub records that msaleme opened the PR and msaleme merged it. There is no formal review on it. The block and my instruction happened, but neither is in that record. Both exist only in a session transcript on my laptop.
The record knows which account acted. It does not know who decided.
The study
A paper published this week measures this gap where it matters most, inside an approval gate.
Adam Manji reports twelve weeks of agent-driven development at a fintech startup preparing for a SOC 2 audit (arXiv 2610.28683). From 3 October, every non-bot merge passed a non-author review gate. Yet "71 of those 263 merges (27%) rested only on an approval that a coding agent had submitted through the technical lead's GitHub account, and the platform recorded each one as an ordinary approval by the lead."
Each of those merges satisfied the gate, and the record showed an approval. What it could not show was who made the decision.
The paper's lesson: "Record who decided, not only who acted." And the part that is easy to skip: "A decision record the agent can write itself is not independent evidence."
My repository has the earlier version of the problem
My case is different, and the difference matters. Manji's team had recorded approvals whose decision-maker was uncertain. My repository has no recorded approvals at all, and its attribution is uncertain.
The Agent Security Harness has one maintainer and several agents. I pulled its merge history from the GitHub API on 23 October.
- 477 pull requests merged since March. The account
msalemeopened and merged 474 of them; three came from outside contributors. - 274 of those 474 carry a "Generated with Claude Code" footer or a Claude co-author line. The agent writes that footer itself.
- 200 carry no footer. That establishes neither human nor agent authorship. They are unclassified.
- None of the 474 has an approving review. An automated reviewer, Cursor Bugbot, left 131 comment reviews.
The missing reviews are not a lapse. GitHub does not let an author approve their own pull request, and every agent and I act as the same author. A shared account removes the possibility of a recorded second decision, before any gate is configured.
The assumption I got wrong
Yesterday another of my agents filed an issue on the repo. I identified which one from a single field, performed_via_github_app, which said Cursor. I wrote that down as a rule: every agent posts as me, and the app field is how to tell them apart.
Then I counted. Of 598 issues and pull requests created as msaleme, the field names an app on 18. On the other 580 it provides no app attribution at all.
The rule held for the one case I checked. It does not tell anything apart across the record.
What I changed
Manji's recommendations transfer, and the paper is precise about their limit: on GitHub, the permission that lets an identity open pull requests also lets it submit reviews. Separate identities matter only if the rule that counts approvals relies on them. So I changed both the identity and the rule, the same day.
-
The agent has its own identity. Claude Code sessions now act through a GitHub App,
msaleme-claude-agent, using installation tokens. Activity is attributed tomsaleme-claude-agent[bot], not to me. A user token would have attributed it to both of us, which recreates the problem. The tokens are minted per command and scoped to this one repository. - The agent commits and pushes as itself. Its commits are authored by the bot and left unsigned on purpose, because signing them with my key would have my key vouch for work I did not write. Its pushes go through the App.
-
The decision comes through a channel the agent cannot use. A ruleset on
mainrequires a pull request and one approval from a code owner, with no bypass for anyone, me included. Any new push dismisses an earlier approval, so the approval always covers the exact revision that merges.CODEOWNERSlists only people. The App can comment on its own pull request, or even approve it, but that approval does not count. -
The agent cannot quietly route around it. A hook in the agent's runtime refuses
gh pr create,gh pr merge,git commitandgit pushagainst this repository unless they go through the App. I tested it by asking the agent to merge as me, and the hook refused.
The first pull request under the new rules is #667. The agent committed it, pushed it, opened it and queued it for auto-merge. I approved it from my own account. The App then executed the merge, which the rule allowed only after my approval of that revision. The record reads: author, the bot; approver, me; merger, the bot. Who acted and who decided are now different identities, and the record says which is which.
GitHub's record has a seam of its own. I had also required approval from someone other than the last pusher. The merge box refused: it named me as the last pusher, while the repository's activity log records the push as the bot's. I dropped that rule rather than leave every agent pull request unmergeable. Dismissing approvals on any new push still forces my approval onto the code that merges.
Two limits remain, and both are visible:
- Other agents still act as me. They are listed in #668. Until each has its own App, the ruleset leaves their pull requests unable to merge, which is the intended effect.
- I am still the repository's admin. I can edit the ruleset, and I did, twice, while setting this up. GitHub keeps the ruleset's history.
None of this proves that I read every diff. It makes the record claim no more than it can show: which agent acted, and that a person holding an identity no agent holds approved the exact revision that merged.
Michael K. Saleme maintains the Agent Security Harness. The counts in this post come from the GitHub API on 23 October 2026; the queries, with what each number does and does not establish, are in this gist.
Top comments (0)