DEV Community

Cover image for Wiring a "read this first" hook as "ask" (don't)
 Gábor Mészáros
Gábor Mészáros Subscriber

Posted on

Wiring a "read this first" hook as "ask" (don't)

In retrospect, it was pretty obvious... sharing it anyway.

I'm currently working on a self-steering progressive disclosure system, and I wanted my agent to read a design doc before it touched the code that doc governs. Simple PreToolUse hook: match Edit/Write, check whether the doc was Read this session, and if not, stop the edit and tell the agent to read it first.

I returned permissionDecision: "ask". My reasoning at that time: "ask has an escape hatch. Approve and the call proceeds, so a bad condition can never wedge the agent - you can always wave it through."

That safety was the whole problem. The approval is an escape from the prerequisite, not a path through it. The edits went through without the doc reads.

Here is what ask actually does. It surfaces the tool call for approval. Approve it - or auto-approve it - and the action runs. The doc never gets read. ask is a checkpoint for a human, not a gate on the agent. Nothing about it makes the agent do the prerequisite step.

The fix was one field. Return deny:

// PreToolUse hook, matcher: Edit|Write
if (!wasReadThisSession(governingDoc)) {
  return {
     hookSpecificOutput: {
         hookEventName: "PreToolUse",
         permissionDecision: "deny",
         permissionDecisionReason: `Read ${governingDoc} before editing this, then retry.`
     }
  };
}
Enter fullscreen mode Exit fullscreen mode

deny refuses the tool call and hands the agent the reason. The agent can't proceed, so it does what the reason says - reads the file - and retries. The read clears the check, the retry passes.

This is the behavior Claude Code already ships natively: when it tries to Edit a file that haven't been Read before, the tool refuses the action until the relevant Read happens. The deny hook lets you apply that same read-before-edit rule to any file you designate, not only the file being edited.

The distinction I missed:

  • ask gates on approving the call.

  • deny gates on the agent doing the prerequisite work first.

If the goal is to force the agent's own behavior - read this, run that check, load that context - deny is the lever. ask just adds a human to the loop.

Important: this only works, if the gate releases accurately, when the prerequisite is actually done. A deny on a condition that never clears will wedge the agent. Mine keys off "was this file read this session," which the read itself flips.

Top comments (0)