Automation Hooks: Making AI Coding Assistants Actually Follow the Rules

    Instruction files such as `AGENTS.md` tell an AI coding assistant what you would like it to do, but they are advisory rather than enforced — a probabilistic model cannot guarantee that a rule was applied. This article explains why writing "run the formatter after every edit" into a context file does not work, and how automation hooks — built from an event, a matcher, and an action — turn "please do this" into "this will definitely happen" for formatting, linting, tests, and guardrails. It also covers common use cases, best practices for fast, idempotent, observable hooks, and the security considerations of executing commands without human intervention.

    As teams rely more and more on AI coding assistants, one problem keeps coming up: we want the assistant to handle the cleanup work right after it finishes an action. Format every file right after it is edited, run unit tests after every code change, check style conventions before every commit.

    The first instinct is usually: how hard can that be? Just write a line like “run the formatter after every edit” in the rules file at the project root — the context file the industry commonly calls AGENTS.md, or a conventions section in README — and you are done.

    It does not work that way. This article explains why.

    1. Why “just write it in the instruction file” is unreliable

    Before looking at solutions, it helps to understand the nature of the problem. An instruction file plays the role of an employee handbook for the AI assistant: you write your project conventions, coding habits, and caveats into it, and the assistant reads it as context when it starts working.

    The catch is that these instructions are advisory, not enforced.

    Think of a sign on the office wall saying “please turn off the air conditioning when you leave.” You can follow it or ignore it. It is not that employees do not want to comply — they simply forget when they are busy. AI assistants behave the same way: while working through a complex refactoring task with a long context, the handful of rules read at the beginning are easily drowned out.

    There are a few deeper technical reasons behind this:

    1. A large language model is probabilistic, not a rule engine. It predicts what is most likely to come next based on its training data and the current context, rather than executing if-else branches one by one like a compiler. Write “use TypeScript” and it will most likely comply, but “most likely” is not “every time.”
    2. Context priority drifts. As the conversation grows longer and more files come into play, the relative weight of early instructions drops — especially for background rules that do not directly conflict with the current task.
    3. There is no closed loop. An instruction file can only express intent; it cannot verify that the outcome actually happened. If the assistant forgets to run the formatter, nothing forces it to catch up.

    The conclusion is clear: anything that must happen and cannot be missed should not be left to the model’s memory. It belongs to a deterministic system mechanism.

    2. Automation hooks: from “please do it” to “you must do it”

    If instruction files are the rulebook posted on the wall, automation hooks are the access control system at the office entrance.

    The difference:

    • The rulebook: you can read it, or ignore it.
    • The access control system: you cannot get in without a badge, no exceptions.

    An automation hook is essentially a deterministic automated rule: when a predefined event occurs, the system automatically runs a command or script you configured in advance. It does not depend on the AI remembering, and it does not depend on the AI’s judgment — it is enforced at the system level.

    The difference in one sentence:

    An instruction file says “please do it this way.” An automation hook says “this is how it will definitely be done.”

    For requirements that cannot be left to the model’s discretion — “format after editing,” “run tests before committing” — hooks are the last line of defense that provides deterministic control.

    3. The three core elements of a hook

    Every hook rule can be broken down into a combination of three elements. Sticking with the access control analogy:

    Element Role Access control analogy
    Event When it triggers The moment “someone wants to enter”
    Matcher Under what condition it triggers “Only for employees who need to badge in”
    Action What happens after it triggers “Open the door on success, raise an alarm on failure”

    1. Event (when it triggers)
    Defines the point in time at which the rule takes effect — for example, before a given tool is invoked, after a tool finishes executing, when a task completes or terminates, or when a session starts. The choice of timing directly determines the semantics of the action: placed before, it is interception and validation; placed after, it is cleanup and post-processing.

    2. Matcher (under what condition it triggers)
    Narrows the trigger scope so the hook does not apply indiscriminately. You can restrict it to file-editing and file-writing tools only, to specific extensions such as .ts or .py, or to changes under a particular subdirectory. The more precise the matcher, the less likely the hook is to interfere with unrelated operations.

    3. Action (what happens after it triggers)
    Usually a shell command or a script invocation. It can run a formatter, execute static checks, run tests, write a log entry, send a notification, or even block an operation through its return value.

    Combined, these three produce a complete, readable, and maintainable automation rule.

    4. A reusable configuration pattern

    The exact configuration format differs from tool to tool, but the mental model of “event + matcher + action” is universal. The following illustrative snippets show the structure (not the syntax of any specific product):

    1. [
    2. {
    3. "event": "post_tool_use", // when: after a tool finishes executing
    4. "matcher": { // condition: only file writes/edits
    5. "tools": ["Write", "Edit"],
    6. "files": "**/*.{ts,tsx,js,py}"
    7. },
    8. "action": "npx prettier --write $EDITED_FILE && npm run lint"
    9. },
    10. {
    11. "event": "pre_tool_use", // when: before a tool is invoked
    12. "matcher": {
    13. "tools": ["Bash"],
    14. "patterns": ["git push*"] // block dangerous commands
    15. },
    16. "action": "exit 2" // non-zero exit blocks the operation
    17. }
    18. ]

    The first entry implements post-processing: format and lint as soon as a file is edited. The second stops a dangerous operation before it happens. Together they show the principle directly: pre-events are for interception, post-events are for cleanup.

    5. Typical use cases

    Applied to real workflows, these three elements cover a wide range of needs:

    • Consistent code style: format immediately after an edit or write, so formatting problems are never discovered only at commit time.
    • Static quality gate: run lint automatically after a change and feed the findings back to the AI so it fixes them on the spot.
    • Protecting sensitive files: block writes to or deletions of .env files, key files, and production configuration to prevent accidental changes.
    • A closed verification loop: run unit tests or type checks automatically when a task ends, using the test results as objective evidence of whether the work is done.
    • Pre-commit validation: run convention checks before version control operations, and do not let anything through if they fail.
    • Audit and traceability: record the time and content of every tool call and file change for later review.

    Of these, “letting the AI correct itself based on test results” is particularly valuable. Enforcing tests through a post-event hook puts a layer of objective fact around the model’s judgment — whether they pass is not open to subjective interpretation.

    6. Best practices for designing hooks

    Hooks are powerful, but used badly they become a drag on team productivity. These principles are worth following:

    1. Be fast, and give feedback fast. Hooks run synchronously on the critical path, so the longer they take, the more they slow down the interaction. Prefer operations in the millisecond-to-second range. Heavy checks such as a full test suite are better placed at task completion or commit time than on every edit.
    2. Stay idempotent. A hook should be safe to run repeatedly, producing the same result no matter how many times it fires, so retries or a series of edits do not cause side effects such as repeated formatting or repeated file writes.
    3. Make failures clear and readable. A blocking hook should return an explicit error message and a suggested fix, not a bare error code. Both the AI and the human should be able to tell why the operation was stopped.
    4. Minimize permissions. Hooks run with system privileges, so their scope should be strictly limited and unrelated commands avoided, to reduce the risk of accidental damage.
    5. Make them observable and easy to disable. Log what hook actions do so problems can be diagnosed, and keep a convenient switch so hooks do not get in the way during debugging or ad hoc work.
    6. Keep matchers narrow rather than broad. Precisely define the trigger scope so hooks do not fire on unrelated operations — this saves resources and reduces noise.

    7. Security and risk control

    Because hooks execute commands automatically with no human in the loop, security has to be considered up front:

    • Validate inputs: check the paths, file names, and other arguments passed to a hook to avoid command injection or path traversal.
    • Intercept dangerous operations early: for high-risk actions such as deletion, force push, and overwriting production configuration, prefer a pre-event as the gate.
    • Sandbox and least privilege: run hooks in a restricted environment where possible, and avoid granting them more system access than they need.
    • Review changes: keep hook configuration under version control and have it reviewed by the team, because it is effectively code that runs automatically and can block development.

    Conclusion

    Instruction files answer “does the AI know the rule?” Automation hooks answer “is the rule actually enforced?” The former relies on the model’s discretion and is a probabilistic guarantee; the latter is backed by the system and is a deterministic one.

    In engineering work that demands rigor, the right approach is to combine the two: use instruction files to convey intent and background, and use automation hooks to lock down the actions that must not be missed and must happen. Turning “hoping the AI remembers” into “the system always does it” is what takes an AI coding assistant from a smart colleague to a reliable engineering partner.