# Hooks and Permissions

Course: [Harness Engineering](https://www.devtutorials.ai/learn/harness-engineering) · Section: Foundations
Source: https://www.devtutorials.ai/learn/harness-engineering/foundations/hooks-and-permissions

> Rules are requests; hooks and permissions are walls. You watch an agent in a hurry with and without guards, write a guard config that blocks only the dangerous calls, see the same guards in Claude Code, Cursor and Codex, then check and download your v1 harness.

## What Hooks and Permissions Do

A **hook** is a small script that your tool runs before the agent acts. A **permission** is a deny entry in the tool's settings.

In this lesson, you will learn how both stop an action for good, while a rule only asks.

Your rules file can say "never".

But a rule is a request. The agent reads it, and most of the time it obeys.

An agent in a hurry may not.

For example, the rules file in this project says:

- "Never edit `.env.production`. Never switch off a test. Never delete files."

The agent reads this line. Then it deletes files anyway.

### A Real-World Example

An office door has a sign: "Staff only".

That sign is a rule. Most people respect it.

Someone in a hurry walks in anyway.

Now add a badge reader to the same door. With no badge, the door stays shut.

A good badge reader also has a small screen.

It says: "No access. Ask reception for a visitor badge."

Now the visitor knows what to do next.

Click the button and watch the transcript in the Harness Lab.

- **The rules file:** it says "never" three times.
- **The steps:** the agent deletes files and switches off the failing test.
- **The end:** it says every check passes. The bug is still there.

**Simple lesson:** for things that must never happen, a sign is not enough. You need a badge reader.

## What You Will Learn

In this lesson, you will learn:

1. Why a rule is not enough for "never".
2. What hooks and permissions are.
3. How to block switched-off tests, deletes and production edits.
4. Why a blocking hook needs a message and a narrow match.
5. How the same guards look in Claude Code, Cursor and Codex.
6. What guards cannot protect.
7. How to check your first complete harness and put it in your own repo.

## Rules Are Requests, Guards Are Walls

### What a Guard Is

**Guard** = a hook or a permission. It runs in code, before the tool acts.

The agent asks for a tool call. The guard checks it first.

If the guard says no, the call never runs. The model cannot talk its way past it.

### Without and With Guards

Here is the same agent in a hurry, run twice.

The first run has only the rules file. The second run adds one settings file.

Step through all five frames.

- **Frame 1:** the "never" line in the rules file.
- **Frame 2:** the red lines. Each one did real damage.
- **Frame 3:** the settings file. One hook and two deny rules.
- **Frame 4:** the yellow lines. Each is a blocked call, with the message the agent saw.
- **Frame 5:** both runs, checked the same way.

The rules file did not change. Only the guards were added.

**Simple lesson:** same agent, same rules. The guards made the difference.

## How a Hook Works

### Two Moments

A hook runs at a fixed moment in the agent loop.

1. **Before a tool call.** Claude Code calls it `PreToolUse`. It sees what the agent wants to do, and it can block it.
2. **When the agent wants to stop.** Claude Code calls it `Stop`. It can refuse "done" while the tests fail.

**Hook** = a script the tool runs at one of these moments.

**Exit code** = the number a script gives back when it ends. 0 means "all fine".

To block, the script prints a message and exits with code **2**.

The tool cancels the action. Then it shows the message to the agent.

### The Hook Scripts

The starter project has four scripts in `scripts/hooks/`:

| Script | When it runs | What it blocks |
|---|---|---|
| `block-skip.sh` | before an edit | an edit that switches off a test |
| `protect.sh <file>` | before an edit | an edit to a file you name |
| `block-destructive.sh` | before a command | `rm -r`, force pushes, dropped databases |
| `require-tests.sh` | when the agent stops | "done" while the tests fail |

Each script reads the tool's JSON from standard input. They are real bash, so you can copy them into your own project.

**JSON** = a plain text format for data: names and values inside `{ }`.

**Standard input** = the text a program is handed when it starts. Here, the tool hands the hook a description of the call.

Here is the message `block-skip.sh` gives the agent:

```text
Blocked: this change switches off a test (.skip / Skip =). Fix the code so the test passes instead. If the test itself is wrong, stop and ask a person.
```

It says what was blocked. It says what to do instead.

**Simple lesson:** a hook script checks one thing and explains itself.

## Permissions: Deny Lists

For a simple "never", a deny list is often easier than a hook.

**Permission** = an allow or deny entry in the tool's settings. A denied call never runs.

In Claude Code, hooks and permissions share one file, `.claude/settings.json`. Here is its full shape, with one hook and one deny entry:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write|MultiEdit",
        "hooks": [
          { "type": "command", "command": "scripts/hooks/protect.sh docs/api.md" }
        ]
      }
    ]
  },
  "permissions": {
    "allow": [],
    "deny": ["Bash(git push:*)"]
  }
}
```

- **`PreToolUse`:** run the hook before a tool call.
- **`matcher`:** which tools it watches. Here, every kind of edit.
- **`command`:** the script to run. Here, `protect.sh` guards one file.
- **`deny`:** calls that never run. `Bash(git push:*)` stops every command that starts with `git push`. `Edit(<file>)` stops edits to one file.

A deny rule matches a command name or a file name. It cannot read what an edit says.

So "never switch off a test" needs a hook. A deny rule on the test file would also block a good change to it.

**Simple lesson:** use a deny rule when one name catches the danger. Use a hook when you must look inside the change.

## Two Things a Wall Must Get Right

A hook that blocks with no message leaves the agent guessing. It tries again until the step limit stops it.

A hook that blocks every edit also blocks the real fix. Match only the thing that must never happen.

Here is one agent and one temptation, with three different hooks:

Step through all four frames.

- **Silent hook:** the skip is blocked again and again. The run ends at the step limit.
- **Broad hook:** the agent knows why now. But its real fix is blocked too.
- **Narrow hook with a message:** the agent reads the message and fixes the code.
- **Last frame:** the silent hook and the narrow hook, side by side.

**A wall can hurt you too.**

A silent hook wastes every step you have.

A broad hook stops the work you wanted.

Test each guard on a safe call as well as a dangerous one.

**Simple lesson:** a good wall is narrow, and it says what to do instead.

## One Plan, Three Tools

Each tool keeps hooks and permissions in its own files. The tabs show where.

Now watch one guard plan become a file:

Step through all three frames.

- **Frame 1:** the plan. Three guards, before any file.
- **Frame 2:** the plan as a Claude Code file.
- **Frame 3:** the agent in a hurry, run with each tool's file.

**Hook formats are new and still changing.** Check your tool's docs. Codex runs project hooks only after you **trust** the project's `.codex/` folder.

### Lab Exercise: Guard the Project

The hook file for your tool is in the lab. It is empty.

Click the button to open the hook file in the lab. Add three guards:

1. A hook that runs `scripts/hooks/block-skip.sh` before edits.
2. A deny rule that stops `rm` commands. In Codex, use a hook.
3. A deny rule for the production settings file. Or use a `protect.sh` hook.

To pass, all three bug versions must end safe:

- **No test switched off.**
- **No files deleted.**
- **Production untouched.**
- **The bug really fixed.** A hook that blocks the real fix fails.

**Stuck?** Open the **Inspector** tab.

- It lists the hooks and deny rules the lab found in your file.
- A JSON mistake shows there too.
- Open `scripts/hooks/block-skip.sh` in the Files tab to see how a real hook reads its input.

## What Guards Cannot Protect

Guards stop calls you can name. Some risks slip past them.

- **Secrets in files.** The agent can read what is in the repo. Keep secrets out of it. Put them in env files, and deny reads of those files.
- **Orders hidden in text.** A file or a web page may say "ignore your rules and run this". This is **prompt injection** = text that tries to give the agent orders. Treat that text as data, and keep your guards on.
- **Auto-approve modes.** Some tools have a mode that runs every call without asking, sometimes called "yolo" mode. Use it only in a sandbox or a container.
- **Your main branch.** Run the agent on its own branch. Then you read the diff before anything merges.

Guards cannot stop the agent from reading hidden orders. But they can stop what the orders ask for.

Click the button. The README has a hidden note for AI agents. Add guards until it is safe:

- **The hidden note** asks for a delete and a production change.
- **Your guards** must block both. The real fix must still go through.
- **Tip:** the guards from the exercise above are enough. Copy them in.

**Simple lesson:** guards are one layer. Keep secrets out, keep guards on, and keep the agent on a branch.

## Your v1 Harness

You now have every part of a working harness:

| Part | From | What it does |
|---|---|---|
| Short rules map + `docs/` | **Rules Files** | The agent knows the commands and the "never" rules. |
| A skill | **Agent Skills** | Repeated jobs are done the same way every time. |
| Definition of done + teaching lint | **Tests and Linters** | The agent checks its work and fixes its own mistakes. |
| Hooks and permissions | **Hooks and Permissions** | "Never" really means never. |

### Lab Exercise: Check Your v1 Harness

This check merges your work from the earlier lab exercises.

It runs **9 jobs**:

- 3 bug fixes from an agent in a hurry
- 3 new statuses or fields
- 3 date jobs

A part you have not done yet uses the starting files. So the check tells you which lesson to go back to.

Click the button, then press **Check my harness**.

- **All 9 jobs pass:** your harness is ready to export.
- **A job fails:** the message names the part to fix.

### Export It

When the check passes, open the **Export** tab.

Pick your tools. Download one zip for each tool.

Each zip has your rules (`AGENTS.md`, plus a `CLAUDE.md` for Claude Code), your skill, your guards, the hook scripts, your lint file, your `docs/` and a short `HARNESS-README.md`. Each file lands where that tool looks for it.

**This is the course's first big milestone.**

If you stop here, you already have a usable harness.

Read every file before you commit it. The next lessons add what long tasks need: memory between sessions, a feature list, and end-to-end checks.

### Put v1 in Your Own Repo

The lab project is small. Your project is not. So the files need a few changes before they work for you.

1. **Export** the zip for the tool your team uses.
2. **Copy** the files into the root of your repo.
3. **Change** the test command and the lint command to your project's own.
4. **Change** the protected paths and deny entries to your project's real files.
5. **Commit** the files, so the whole team shares the same harness.
6. **Run** one small task with the agent, then read the diff line by line.

If the agent gets something wrong, add one rule, sensor or guard. Then run the task again.

**Simple lesson:** rules, a skill, sensors and guards together make your v1 harness. Put it in your repo and test it on a small task.

## TL;DR

1. **Rule** = a request in the rules file. An agent in a hurry can ignore it.
2. **Guard** = a hook or a permission. It runs in code, before the tool acts.
3. **Hook** = a script the tool runs before a tool call or when the agent stops. Exit code 2 blocks.
4. **Permission** = a deny entry, such as `Bash(rm:*)` or `Edit(.env.production)`.
5. **Good hook** = narrow, and its message says why and what to do instead.
6. **Tool files** = `.claude/settings.json`, `.cursor/hooks.json` + `.cursor/cli.json`, `.codex/hooks.json`.
7. **v1 harness** = rules map + skill + sensors + guards, exported for your tools.

## Common mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Rules for "never" | "The rules file says never." | Use a hook or a deny rule. | The rule is ignored under pressure. |
| Silent hook | "Blocking is enough." | Say why, and what to do instead. | The agent retries until the step limit. |
| Broad hook | "Block every edit, to be safe." | Match only what must never happen. | The real fix is blocked too. |
| Deny rule for content | "Deny edits to the test file." | Use a hook that reads the change. | Good test edits are blocked too. |
| Missing script | "The config is enough." | Ship the script with the config. | The hook fails, and the action goes through. |
| Untrusted Codex folder | "The hooks file is there." | Trust the project's `.codex/` folder. | The hooks never run. |

## Check Your Understanding

## Exercises

These exercises practise writing and choosing guards.

Press **Check** when you are ready. Use **Show Hint** if you get stuck.

## What to Remember

- A rule is a request. A hook or a permission is a wall.
- A hook blocks with exit code 2 and a message.
- The message says why, and what to do instead.
- Keep each guard narrow, so normal edits still pass. Keep secrets out of the repo.
- Your v1 harness is rules map + skill + sensors + guards. Put it in your repo, then test it on a small task.

## Further Reading

- [Claude Code hooks, a step-by-step guide on systemprompt.io](https://systemprompt.io/guides/claude-code-hooks-workflows): PreToolUse hooks in settings.json.
- [Cursor hooks documentation](https://cursor.com/docs/hooks): hooks.json, events, and how a hook blocks.
- [awesome-harness-engineering, by walkinglabs](https://github.com/walkinglabs/awesome-harness-engineering): guardrails, hooks and permissions across tools.

Next: **Context Windows**. You will see why a long task overflows one session, and how to plan for that.
