惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LINUX DO - 最新话题
G
Google Developers Blog
J
Java Code Geeks
The GitHub Blog
The GitHub Blog
F
Full Disclosure
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Vercel News
Vercel News
酷 壳 – CoolShell
酷 壳 – CoolShell
Recent Announcements
Recent Announcements
Help Net Security
Help Net Security
The Hacker News
The Hacker News
IT之家
IT之家
Y
Y Combinator Blog
Martin Fowler
Martin Fowler
L
Lohrmann on Cybersecurity
C
CERT Recently Published Vulnerability Notes
V
Visual Studio Blog
博客园 - 聂微东
Hacker News: Ask HN
Hacker News: Ask HN
H
Hacker News: Front Page
Know Your Adversary
Know Your Adversary
Security Latest
Security Latest
Security Archives - TechRepublic
Security Archives - TechRepublic
Simon Willison's Weblog
Simon Willison's Weblog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
T
Troy Hunt's Blog
Last Week in AI
Last Week in AI
Schneier on Security
Schneier on Security
N
News and Events Feed by Topic
博客园 - 【当耐特】
有赞技术团队
有赞技术团队
AWS News Blog
AWS News Blog
Blog — PlanetScale
Blog — PlanetScale
博客园_首页
Google DeepMind News
Google DeepMind News
Cloudbric
Cloudbric
N
News | PayPal Newsroom
A
About on SuperTechFans
S
Schneier on Security
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Hugging Face - Blog
Hugging Face - Blog
M
MIT News - Artificial intelligence
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
雷峰网
雷峰网
T
The Exploit Database - CXSecurity.com
罗磊的独立博客
K
Kaspersky official blog
The Cloudflare Blog
I
Intezer

Butler's Log

Agentic Version Control Benchmarks Grit: rewriting Git in Rust with agents Git Merge 2026 Agent-safe Git with GitButler We’ve raised $17M to build what comes after Git Announcing the GitButler CLI for Linux The Great CSS Expansion A couple of git nits Simplifying Git by Using GitButler Introducing the GitButler CLI GitButler 0.19 - "Commander Keen" But Head: Crafting a Custom Font MCP vs RAG: Two Very Different Ways to Gain Context Getting Started With GitButler Agents Using the GitButler MCP Server to Build Better AI-Driven Git Workflows Using GitButler With Multiple GitHub Accounts Advent of Code! Upcoming GitButler Events Use GitButler for your Gerrit workflow Integrating GitButler and GitHub Enterprise Butler Flow: shipping code faster (but less like Alfred, more like CI on steroids) - Part 3 Butler Flow: shipping code faster (but less like Alfred, more like CI on steroids) - Part 2 Butler Flow: shipping code faster (but less like Alfred, more like CI on steroids) - Part 1 Grid Happens: Because Flexbox Wasn’t Enough Using Cursor Hooks for automatic version control A Responsive Item Counter with CSS only GitButler 0.16 - "Sweet Sixteen" GitButler's Claude Code tab GitButler's Annual Open Source Pledge Report Git Mini Summit 2025 Videos Automate Your AI Workflows with Claude Code Hooks Managing Multiple Claude Code Sessions Without Worktrees GitButler 0.15 - "Quirky Quinceañera" 20 years of Git. Still weird, still wonderful. GitButler's new patch based Code Review (Beta) Going down the rabbit hole of Git's new bundle-uri How to do patch-based review with git range-diff How Core Git Developers Configure Git Why is Git Autocorrect too fast for Formula One drivers? Stacked Branches with GitButler Git Merge 2024 Talks are Up GitButler 0.13 - "Lucky Baseball" Fearless Rebasing Git Merge 2024 Why GitHub Actually Won GitButler is joining the Open Source Pledge The New Era of Town Hall Chat The Future of Open Source GitButler is now Fair Source Git Merge 2024 GitButler 0.12 - "Stingy Baker" The Birth of THE MERGE GitButler for Windows Fixing up Git with Autosquash The Git Zeitgeist Git Worktrees and GitButler DevWorld Git Slides Git Tips and Tricks Git Tips 1: Oldies but Goodies Git Tips 2: New Stuff in Git Git Tips 3: Really Large Repositories FOSDEM Git Talk Opening Up GitButler Debugging Tauri in VS Code Advent of GitButler Code Signing Commits in Git, Explained Virtual Branches Alpha Our We Are Developers Adventure Building Virtual Branches DevDays in Vilnius The Future of Software and Open Source Introducing GitButler
Deep Dive into the new Cursor Hooks
Scott Chacon · 2025-09-23 · via Butler's Log

Recently we got invited by the Cursor team to help them refine and give early feedback on the new hooks system that they've been working on, which we've used to implement our GitButler/Cursor integration. I figured I would take a minute to write up how it works, what it might be used for, and what it's capable of.

Technically, the Cursor hooks system is currently in beta, so the APIs may change, but here's what it looks like today.

There are currently 6 lifecycle hooks that can run any number of specified commands when any of these points in a running task is hit:

  • When the prompt is first submitted (beforeSubmitPrompt)
  • Before a shell execution runs (beforeShellExecution)
  • Before an MCP execution runs (beforeMCPExecution)
  • Before a file is read (beforeReadFile)
  • After a file is edited (afterFileEdit)
  • When the task is completed (stop)

Before we dive into the details, how are hooks defined?

They are found in a combined set of locations, looking for a hooks.json file:

  • Your local project ([project]/.cursor/hooks.json)
  • Your "enterprise" configuration (/etc/cursor/hooks.json)
  • Your user's global config (~/.cursor/hooks.json)

All hooks specified in all of those places will try to be run - so if you have two executables defined for a stop hook in your project's local config and one in your user's global config, all three will be executed.

The hooks.json file looks like this:

JSON

{
  "version": 1,
  "hooks": {
    "afterFileEdit": [
      {
        "command": "hooks/audit.sh"
      },
      {
        "command": "but cursor after-edit"
      }
    ],
    "stop": [
      {
        "command": "but cursor stop"
      }
    ]
  }
}

This will run two hooks at the afterFileEdit step - in this case audit.sh is found in .cursor/hooks/audit.sh relative to the hooks.json file that defines this hook.

Ok, now let's look at the lifecycle hooks that are currently available.

The first thing to always run will be the beforeSubmitPrompt which essentially tells the hook what the prompt was before starting. This hook should be called when the user submits a prompt string, but before the Cursor agent sends it to the model.

Here is an example of what will be sent to stdin for whatever executable this hook runs:

JSON

{
  "conversation_id": "668320d2-2fd8-4888-b33c-2a466fec86e7",
  "generation_id": "490b90b7-a2ce-4c2c-bb76-cb77b125df2f",
  "prompt": "do something super duper awesome",
  "attachments": [
    {
      "type": "file",
      "file_path": "path/to/open/file.rb"
    }
  ],
  "hook_event_name": "beforeSubmitPrompt",
  "workspace_roots": ["/Users/schacon/projects/cc-hooks-example"]
}

So you can see some cool stuff. Clearly the "prompt" is there, also there are "attachments", which is the context of what the user was looking at or had added as context manually.

It also has a conversation and generation id, which help you figure out if the chat context has changed.

The "conversation" is different for each new "chat" you do, and the "generation" is different for each new prompt you type within that chat. So for example, in the GitButler hooks, we create a new branch for each "conversation" and a commit for each "generation".

Finally it has the "workspace roots", which generally will only be one path, but as VS Code can have multi-root setups, can be an array.

This hook is useful for systems like GitButler who need the prompt context in order to help write good commit messages eventually.

In the beta version, you cannot do much here other than record this information - Cursor doesn't respect any output json here currently as far as we can tell, such as stopping the task or adding context. (This is different than the beta spec, so maybe it will change in the future)

Before executing any shell command, Cursor can run a hook to see if it's OK to run it.

When executing a beforeShellExecution hook, it passes something like this to stdin:

JSON

{
  "conversation_id": "668320d2-2fd8-4888-b33c-2a466fec86e7",
  "generation_id": "490b90b7-a2ce-4c2c-bb76-cb77b125df2f",
  "command": "git status",
  "cwd": "",
  "hook_event_name": "beforeShellExecution",
  "workspace_roots": ["/Users/schacon/projects/cc-hooks-example"]
}

So most of the same information, but also including the command run. In this case however, the hook will respect json sent to stdout, which is of this format:

JSON

{
  "continue": true,
  "permission": "allow|deny|ask",
  "userMessage": "Message shown to the user",
  "agentMessage": "Message shown to the AI agent"
}

So for example if my script returns this:

JSON

{
  "continue": false,
  "userMessage": "absolutely not",
  "permission": "deny"
}

Then you will see this:

absolutely not

absolutely not

This allows you to block certain commands, stop the execution of the task entirely, communicate with the user or if you do allow it to continue, provide extra information to the agent.

Pretty cool.

In addition to hooks running before shell commands, you can run the same thing before any MCP calls it tries to do. It's input is similar:

JSON

{
  "server": "<server name>"
  "tool_name": "<tool name>"
  "tool_input": "<json params>"
  "url": "string"
  // OR
  "command": "<full command+args>"
}

So for example, before calling the GitButler MCP server (if you were using our MCP server instead of our hooks handler for some reason) would provide something like this to the script:

JSON

{
  "conversation_id": "cdefee2d-2727-4b73-bf77-d9d830f31d2a",
  "generation_id": "63feaa30-ae88-4e47-b6c7-70ee4c39980c",
  "tool_name": "gitbutler_update_branches",
  "tool_input": "{\"changesSummary\": \"Added a README to the project\", \"currentWorkingDirectory\": \"/Users/schacon/projects/cc-hooks-example\", \"fullPrompt\": \"add a README to the project\"}",
  "command": "but",
  "hook_event_name": "beforeMCPExecution",
  "workspace_roots": ["/Users/schacon/projects/cc-hooks-example"]
}

So command is the MCP server command, tool_name is the MCP tool call, and tool_input is an escaped json of the paramaters that tool requested. You get the idea.

This can also return the same JSON as the beforeShellExecution hook, so if it denies the MCP call, you'll see this:

Get the MCP outta here!

Get the MCP outta here!

Before reading a file and sending the contents of that file to an LLM endpoint, you can filter the contents with beforeReadFile. For example, you can enable redaction of secrets or make sure anything else you don't want to send to an external server isn't sent.

The input looks like this:

JSON

{
  "conversation_id": "668320d2-2fd8-4888-b33c-2a466fec86e7",
  "generation_id": "490b90b7-a2ce-4c2c-bb76-cb77b125df2f",
  "content": "#!/bin/bash\n\necho 'my_github_access_token'\n",
  "file_path": "leaks/github_tokens.sh",
  "hook_event_name": "beforeReadFile",
  "workspace_roots": ["/Users/schacon/projects/cc-hooks-example"]
}

This hook also will respect output JSON of the same format, so you can tell it to allow or deny the file reading (or at least, having Cursor send the file contents to the LLM).

Check out Michael Feldstein's actual example of how to deny reading files that contain GitHub API keys on GitHub.

After Cursor is done modifying a file, it can tell a hook what it changed in which file.

It doesn't tell you the diff, it provides you with the old string of the file and the new string of the file, so it can pass quite a lot of data to stdin. Here is an example of stdin you get when updating a README:

JSON

{
  "conversation_id": "cdefee2d-2727-4b73-bf77-d9d830f31d2a",
  "generation_id": "23681cf0-a483-49ab-9748-36044efcef52",
  "file_path": "README.md",
  "edits": [
    {
      "old_string": "# OLD README",
      "new_string": "# NEW README"
    }
  ],
  "hook_event_name": "afterFileEdit",
  "workspace_roots": ["/Users/schacon/projects/cc-hooks-example"]
}

Like the beforeSubmitPrompt hook, this is informational only - you cannot communicate to the user, agent or stop the agent with json output here.

Finally, there is a stop hook for when the task is completed. GitButler uses this as a signal for when to commit the generated work.

JSON

{
  "conversation_id": "cdefee2d-2727-4b73-bf77-d9d830f31d2a",
  "generation_id": "26b45fb6-bdea-439c-b2dc-5e97ee00ecea",
  "status": "completed",
  "hook_event_name": "stop",
  "workspace_roots": ["/Users/schacon/projects/cc-hooks-example"]
}

Really the only unique field here is status, which can be "completed", "aborted" or "error".

Ok, great. So what can we do with this?

Well, besides something like GitButler being able to hook into the agent's lifecycle to take over the version control parts, you can also use it for things like:

  • Auto-formatting or linting after a file edit
  • Sensitive command blocking, like blacklisting an rm -rf
  • Access controls to restrict changes to critical files or directories
  • Prompt validation to analyze or sanitize user prompts before processing
  • Auto documentation, for example firing off a background process to re-document affected files
  • Storing session transcripts to automatically keep all the transcripts of everything you've done in Cursor somewhere

For a very quick example, let's implement a script to display a notification whenever Cursor finishes a task:

JSON

{
  "version": 1,
  "hooks": {
    "stop": [
      {
        "command": "osascript -e 'display notification \"Nailed it\" with title \"Cursor\" subtitle \"AI assistant\" sound name \"Glass\"'"
      }
    ]
  }
}

Now after every task is done, you get a little popup:

Nailed it

Nailed it

There is a hooks output channel in Cursor that will give you a ton of debugging information so you can see if your json is malformed, what it tried to run, etc.

You can get there by going to "Output channels" and choosing "Hooks".

Hooks output channel

Hooks output channel

You may ask yourself "why use hooks when I can use rules?". And you can use rules or even MCP servers to try to do a lot of this.

However, hooks are generally deterministic programs that can always be known to do the same thing the same way. Rules and MCP calls and parameters will almost by definition be non-deterministic and could be called or interpreted in any way at any time, as they are run by the LLM.

In addition to that, rules and MCP services are also by definition slower, since they need to be run by the LLM in serial and the paramaters need to be determined by further LLM calls. For hooks, as the GitButler implementation demonstrates, version control stuff can be kicked off into another process and dealt with in the background rather than waiting for your LLM to struggle with a series of obscure Git commands or determine which options to call them with.

So go forth and build some cool stuff with Cursor's hooks!

Scott Chacon

Written by Scott Chacon

Scott Chacon is a co-founder of GitHub and GitButler, where he builds innovative tools for modern version control. He has authored Pro Git and spoken globally on Git and software collaboration.

Table of Contents

  • Cursor Hooks Deep Dive
  • (Beta) Hooks in Cursor 1.7
  • Setting up hooks
  • The Hooks
  • beforeSubmitPrompt
  • beforeShellExecution
  • beforeMCPExecution
  • beforeReadFile
  • afterFileEdit
  • stop
  • Some Use Cases
  • An Example Implementation
  • Debugging hooks
  • Why Hooks?