Then I Told It Who to Ask Instead
Part one ended with a confession: I had moved the click. This is the fix, on the exact command from Exhibit A. A permission called git.push, a role someone was allowed to grant, a hook that asks the authority model instead of me, and a server that writes down every answer, including the ones that depend on where you are standing.
The practical follow-on to I Told Claude Code to Stop Asking Me. We modeled git push as a permission, gave a delegated owner the authority to grant it, and put a hook in Claude Code that asks IdentityRM before the shell runs. Refused with a reason, fixed by someone whose authority to fix it was recorded, allowed with no prompt, refused again from a different position. Seven real frames, the server records for each, and a plain statement of what a courtesy hook does not enforce.
"The Ask leaves a record every time. An act that IdentityRM executes itself leaves a sealed one. An act that runs anywhere else leaves nothing. That asymmetry is the whole embed thesis."
— Martin Gee, Founder & CEO, IdentityRM
Part one ended on a confession. I told Claude Code to stop asking me, then noticed that what I had done on the client side was an allowlist. I had moved the click. This is the part where the click gets replaced with something better, and it starts with the exact command from Exhibit A.
git push. The one that asked, and asked, and asked.
One thing up front so the rest can be short. This is a demonstration of a pattern, not an access gate for git. IdentityRM does not enforce git and never sees a push. Anyone with a terminal can push whatever they like. What it can do is answer the question the prompt was asking me, and write the answer down. Frame three shows the boundary on camera so nobody has to take my word for it.
What I built
Nobody had encoded who may push. So we encoded it, the way our platform already models application permissions: a resource called Dev Tooling with a permission key git.push, a role called Release Pusher that grants exactly that key, and an owner of that role at the Engineering node. Then a small hook in Claude Code that fires before every shell command, recognizes git push, and asks IdentityRM whether the person operating Claude holds git.push from the position they are operating in. Held, the call runs. Not held, the call is refused before it starts, with the reason. Everything else the hook ignores.
Two people are in this story, both fixtures from our demo seed. Release Engineer is the one pushing, home node New York Office, assigned at Engineering. Jordan Lee is a support analyst who owns the Release Pusher role at Engineering and cannot assign roles in general. That last part turns out to be the point.
Here is the whole model in one picture, so you can carry it into the frames.
One: the prompt, for the record
Hook off. The demo repo, in Claude Code.
Bash command · git push origin main · This command requires approval. Do you want to proceed?The ordinary approval prompt, the way it appeared forty times a day in part one. I cancelled it. No allowlist produced or suppressed that prompt; the user settings had zero allow rules and the project’s were empty.

Two: refused, before it runs, with the reason
Hook on. Same repo, same three words.
Permission denied for git.push on dev_tooling at EngineeringClaude checks the tree, decides to push, the hook asks IdentityRM, and that sentence comes back. It is IdentityRM’s sentence, not the hook’s and not Claude’s. Claude relays it in plain words:

No prompt. No human in the loop. A computed no, and it names the position it was computed at.
Here is what that looks like on the server. An access evaluation event: who was evaluated, on which resource, for which permission, at which node, and the verdict.

Two details in that record matter more than the verdict. The client is the hook itself, recorded as its own caller. Twenty-odd seconds later Claude asked the same question on its own connection to explain the refusal to me, and IdentityRM logged that as a second event with a different client. Same person, same question, two askers, both on the record. And on the engineer’s connection the grant_role tool is not offered at all. The person who lacks git.push cannot even see the tool that would give it to them. That part is not a courtesy. That is the server withholding.
One more thing the record shows that I would not have known otherwise. After the refusal, Claude switched itself to the engineer’s other position, New York Office, asked again, was refused again, and switched back. It told me it had done this. The audit trail confirms it independently: two persona-change events bracketing another denial. The control held because the engineer holds the role at neither node. What is proven is that the other route was closed, not that the assistant declined to take it. I am stating that plainly rather than telling you a story about a well-behaved agent.
Three: the shell pushes anyway
While the engineer was still denied, I opened a plain terminal in the same repo and ran git push.

It worked. The push Claude had refused minutes earlier went through seconds later, and nobody asked anyone. It is still there. That is the boundary from the top of this post, on camera. For git, what we built is the Ask and the record of the Ask. The Act belongs to whoever is holding the shell, because git has no enforcement point that consults an authority model. Our own tools do, which is why the same push could not be made by Claude and could be made by me.
Four: the delegated fix, and who was allowed to make it
Someone has to give Release Engineer the permission, and in an authority model you do that by granting a role, from a position that has the authority to grant it.
That person is Jordan Lee. Not an administrator. A support analyst with four roles, every one of them unable to assign roles, and a guardrail test that locks in the absence of any broad role-administration permission. Jordan can grant Release Pusher for one reason: Jordan owns that role at Engineering. Ownership of a role at a node is authority to delegate it there. It is not the permission itself. Jordan is denied git.push too, and that frame is at the end.
Jordan, in Claude Code, at Engineering.
The subject has authority and this operation is currently eligible.No identifiers typed. Claude resolved the person, the role, and the node through the discovery tools, each of which prompted normally because they are not enrolled in the hook. Then the grant, with the verdict above from the hook’s rule on it. Preview. Confirm. Executed. Whether that one enrolled call also skipped Claude’s prompt I did not verify on this run, so I am not claiming it; the prompt claims in this post are frames two, five, and six. It also read “this week” as seven days and set the assignment to expire on the twenty-fifth, which it said out loud. That is the assistant exercising judgment I did not specify, and it is worth noticing as behavior rather than as a guarantee.

Claude’s explanation of why, which it got right:
And not only as prose. The stored event records two authority sources as separately established facts, each with its own factor: a permission assignment that let Jordan see the role, and role ownership that let Jordan grant it. Neither alone was enough, and IdentityRM says so in fields rather than adjectives. It is also why we cast Jordan and not a manager: the manager role in this seed lacks the visibility factor, and ownership would not have bypassed it.
The grant carries a server-side authority seal:
sha256:2e6aa89691f3a03b5b9c5a196bcc595954fb92d7a07c5a49134a7962ba2d9bce
Only git.push changed hands. The resource also defines git.force_push and git.tag, and Release Pusher grants neither, so being allowed to push does not quietly become being allowed to rewrite history.
Five: the same command, no prompt
Release Engineer, same repo, same three words as frames one and two. Manual mode, the mode that asks about everything.
Permission granted for git.push on dev_tooling at Engineering
Pushed. Local and remote on the same commit. Nothing changed in Claude between frame two and frame five. No allowlist, no pre-approved tool, nothing in Claude’s configuration. The hook caches nothing and asks again on every call. The only thing that changed in the whole system was one role assignment, made by a person whose authority to make it was recorded, and the prompt went away because a computed answer replaced it. The server’s record of the allow sits next to its record of the deny, identical in every field but the verdict.


Six: same person, same credential, different position
This is the one I wanted most.
Permission denied for git.push on dev_tooling at New York Office
Permission granted for git.push on dev_tooling at EngineeringPushed. Same human, same credential, same command, minutes apart, opposite answers, and IdentityRM’s own sentence names which position it evaluated each time. Claude explained the mechanism itself, unprompted, and got it right:
Authority is a property of where you are standing in the organization, not of who you are. A token cannot say that. A role list cannot say that. This can, and it writes it down.
Seven: the person who can grant it does not hold it
Jordan, whose ownership is what made the grant possible, asks Claude to push. This frame is from an earlier run, and it is still true today: Jordan has never held the role.
Permission denied for git.push on dev_tooling at Engineering
Ownership is authority to delegate. It is not the thing delegated. Most systems cannot express that distinction at all, and it is the one that keeps an owner from being an administrator.
The hook, so you can copy it
{
"hooks": {
"PreToolUse": [
{ "matcher": "mcp__identityrm__grant_role",
"hooks": [{ "type": "command", "command": "IDRM_AUTHORITY_CONFIG=... node .../authority-hook.mjs", "timeout": 15 }] },
{ "matcher": "Bash",
"hooks": [{ "type": "command", "command": "IDRM_AUTHORITY_CONFIG=... node .../authority-hook.mjs", "timeout": 15 }] }
]
}
}
Claude’s matcher cannot inspect a command string, so the shell rule is matched broadly and the hook decides enrollment itself, returning nothing for anything that is not a git push. It never caches an allow, never switches persona, never mutates anything, and forwards only IdentityRM’s sentence. Its credentials live in an operator-owned file that the hook reads and never passes to the model. It recognizes the spellings of push we taught it; the ones it does not recognize fall back to the ordinary prompt, never to silent execution. That is not a hypothetical. During this shoot Claude emitted a push in a form the hook did not yet recognize, the push went through to the ordinary prompt, and the frame was re-shot after the matcher was fixed. If you try sudo git push or wrap it in a subshell, you will find those unenrolled by design. And it is a courtesy: a killed or timed-out hook returns to Claude’s normal permission flow, and the server is the control wherever there is one.
The credentials in every frame are harness-issued from our demo seed, not an identity-provider login. That is a separate proof, later.
Back to the question
When your agent writes its careful little receipt and you type approved, where does it go?
Here, for one command, it went into a computed answer instead of a click, and every answer, allowed or denied, went into a record that names who asked, on whose behalf, at which position, and the verdict. The grant that changed the answer went into a sealed one, with the why and the human’s confirmation inside it as facts. Everything else still asks you, and that is correct for a system with no authority model to consult.
So the question for anyone shipping a coding agent moves one step: which tool surface would you model first?
IdentityRM governs delegated authority at the point of operation, for human operators and AI agents alike, and exposes it through governed MCP tools any MCP-capable client can call. Part one, the contract, or the platform.