Relying on documentation or repeated corrections to enforce project tooling standards is tedious and unreliable. The AI might default to common commands like npm instead of your preferred tool, and you find yourself constantly correcting it. There's a better way to enforce these standards automatically.
This lesson shows you how to build a beforeShellExecution hook that intercepts every shell command the AI attempts to run. You'll inspect the command, decide whether to allow or deny it, and send custom feedback that guides the AI to use the correct tools—all automatically, with zero manual intervention.
The Goal: Automated Tooling Enforcement
The hook you'll build accomplishes three things:
- Detects when the AI attempts to run a command using 
npm 
- Denies permission for that command to execute
 
- Sends a custom message back to the AI explaining that 
bun must be used instead 
The AI receives this feedback instantly and retries the command using the correct tool. Your project standards are enforced automatically, with no manual corrections needed.
Hook Implementation
The process involves creating a TypeScript file for our hook's logic and registering it in Cursor's hooks.json configuration.
First, we register the hook in .cursor/hooks.json:
{
  "version": 1,
  "hooks": {
    "beforeShellExecution": [
      {
        "command": "bun run hooks/before-shell-execution.ts"
      }
    ]
  }
}
 
The hook logic in hooks/before-shell-execution.ts receives a payload with the command to be executed and returns a response with permission ("allow" or "deny") and an optional agentMessage.
A simple startsWith check isn't enough—commands can be chained like cd /path/to/project && npm install. The solution is to check both whether the command starts with npm and whether it contains npm (with spaces) anywhere in the string.
Here's the complete implementation:
import type { BeforeShellExecutionPayload, BeforeShellExecutionResponse } from "cursor-hooks";
const input: BeforeShellExecutionPayload = await Bun.stdin.json();
const startsWithNpm = input.command.startsWith("npm") || input.command.includes(" npm ");
const output: BeforeShellExecutionResponse = {
  permission: startsWithNpm ? "deny" : "allow",
  agentMessage: startsWithNpm ? "npm is not allowed, always use bun instead" : undefined,
};
console.log(JSON.stringify(output, null, 2));
 
Seeing it in Action
When you ask the AI to install a package, it might default to using npm:
The hook intercepts the command, denies execution, and sends back the custom message explaining that bun is required. The AI reads this feedback and immediately retries the command using bun—all automatically.
This demonstrates the power of intelligent guardrails: the AI learns your project's rules on the fly, adapting its behavior without any manual corrections. Your standards are enforced seamlessly, creating a smoother, more reliable development experience.
Transcript
[00:00] Now with everything we've learned so far let's create a before shell execution hook here. So before shell execution and wire it up to our hooks. So drop it here before shell execution and the command will point to our script we're setting up. Then close this out, collapse this, and we'll bring in the proper types. So before shell execution payload and response, then type these.
[00:32] I'm just hitting tab now. So now we have this template we can use that's fully typed with an input and an output so we can customize what to do based on the payload. So the response is going to allow us to set the permission And our goal here is to deny anytime someone uses NPM and force the agent to use bun instead. So I'm going to say permission and we'll check the input.command and we'll say starts with NPM. So if it starts with npm we will deny, otherwise we will allow.
[01:05] And then similarly we'll hop over to the agent message. I'll just tab complete this. So we'll say npm is not allowed, always use bun instead. Then we'll quickly extract starts with npm, hit tab to replace that. And now if I attempt to run this, this would actually be blocked still because I'd always have to say allow.
[01:27] So I'm going to disable this hook real quick. So the only available hook is the shell execution hook. So hop over the agent. I'll just say npm install lodash. Hit enter.
[01:38] And we actually ran into a scenario where the command actually started with cd. So this is a good catch where I'll have to say or input command includes space npm space. Now if I jump over the agent and try it again, npm install lodash, hit enter, you'll see that we switched from lodash to bun because up here we get command execution was blocked by a hook. Behind the scenes it got this message that npm is not allowed, always use bun, so it switched, and then it automatically swapped over to bun. So Whereas in the past you might have included this inside of some sort of cursor rule, now you can inject rules just in time whenever specific text is detected.