{"slug": "creating-a-vs-code-agent-hook-to-respond-to-file-changes", "title": "Creating a VS Code agent hook to respond to file changes", "summary": "Visual Studio Code released agent hooks, a deterministic feature that triggers shell commands at specific points during an agent session. A developer created a hook to automatically regenerate TypeScript types when a wrangler.jsonc file is edited, solving a problem where agents previously failed to perform this action reliably.", "body_md": "Visual Studio Code recently shipped [agent hooks] 1 as a way to customize your agent experience. Unlike other enhancements such as skills, agent hooks are deterministic. Instead of relying on the agent to call a tool or use a skill, an agent hook sits in the middle of the agent loop and listens for specific events. You can use those events to trigger an action. That means no more pleading with the agent to always follow one action with another. Just define a hook that makes it happen 100% of the time.\n\nFor example, I recently set up an agent hook to regenerate TypeScript types whenever a `wrangler.jsonc`\n\nfile is edited. These files specify bindings for Cloudflare Workers, and the `wrangler types`\n\ncommand reads `wrangler.jsonc`\n\nand outputs TypeScript type definitions. I had been struggling to get agents to regenerate types whenever they edited a `wrangler.jsonc`\n\nfile. I begged, pleaded, and strongly cautioned the agent, and nothing worked. Now it happens automatically with an agent hook, and I don’t have to think about it.\n\nCreating an agent hook that triggers only when a specific file changes isn’t straightforward. It takes a fair amount of boilerplate just to determine whether the file in question changed. Before digging into that use case, though, it helps to understand how agent hooks work in general.\n\n## How agent hooks work\n\nAgent hooks are shell commands that execute at specific points during an agent session. Every agent session consists of a series of events, and you can trigger an agent hook for any of them. At the time of writing, the available hook events are:\n\n`SessionStart`\n\n: When the user enters their first prompt in the chat.`UserPromptSubmit`\n\n: When the user submits any prompt. This gives you access to the prompt itself.`PreToolUse`\n\n: When the agent is about to use a tool.`PostToolUse`\n\n: When a tool has completed its job.`PreCompact`\n\n: Before the chat is compacted to free up context.`SubagentStart`\n\n: When a subagent is created.`SubagentStop`\n\n: When a subagent finishes.`Stop`\n\n: When an agent has completed all of its work and is waiting for another prompt.\n\nYou can hook into any of these events by creating a JSON file in the `.github/hooks`\n\ndirectory that specifies which events to listen for and which command to run. Here’s an example:\n\n```\n{\n  \"hooks\": {\n    \"PostToolUse\": [\n      {\n        \"type\": \"command\",\n        \"command\": \"bash ./hello-world.sh\"\n      }\n    ]\n  }\n}\n```\n\nThis hook runs the `hello-world.sh`\n\nscript with `bash`\n\nafter a tool has been used.\n\n## Hook input\n\nWhen a hook command is executed, details about the hook are passed through `stdin`\n\nas a JSON structure. Five common fields are sent with every hook:\n\n`timestamp`\n\n: The time of the event`cwd`\n\n: The current working directory at the time of the event`sessionId`\n\n: A unique identifier for the agent session`hookEventName`\n\n: The name of the hook event that was triggered`transcript_path`\n\n: A JSON file containing the transcript of the agent session\n\nEach hook event adds hook-specific information to the payload. For example, a `PostToolUse`\n\nhook, which is the one this post focuses on, adds information about the tool that was used:\n\n```\n{\n  \"timestamp\": \"2026-02-09T10:30:00.000Z\",\n  \"cwd\": \"/path/to/workspace\",\n  \"sessionId\": \"session-identifier\",\n  \"hookEventName\": \"PostToolUse\",\n  \"transcript_path\": \"/path/to/transcript.json\",\n  \"tool_name\": \"editFiles\",\n  \"tool_input\": { \"files\": [\"src/main.ts\"] },\n  \"tool_use_id\": \"tool-123\",\n  \"tool_response\": \"File edited successfully\"\n}\n```\n\n### Hook output\n\nYour hook command communicates back to VS Code through two mechanisms:\n\n- The exit code\n`stdout`\n\nThe exit code can be one of the following:\n\n`0`\n\nmeans the command executed successfully and the agent session should continue`2`\n\nmeans the command failed and the agent session should stop- Anything else means the command didn’t fully succeed or fail, and the agent session should continue with a warning displayed to the user\n\nThe exit code is used along with a JSON structure written to `stdout`\n\n. Every hook event supports the following fields:\n\n`continue`\n\n: A boolean indicating whether the agent session should continue`stopReason`\n\n: If`continue`\n\nis`false`\n\n, this message is displayed to the user`systemMessage`\n\n: A warning message displayed to the user regardless of`continue`\n\n`hookSpecificOutput`\n\n: An object with information specific to the hook in use`hookEventName`\n\n: The name of the hook event`additionalContext`\n\n: Text to insert into the agent session context\n\nFor `PostToolUse`\n\n, you can also specify these fields:\n\n`decision`\n\n: Set to`\"block\"`\n\nto prevent further processing`reason`\n\n: If`decision`\n\nis`\"block\"`\n\n, this field is passed to the model so it can understand the block\n\nHere’s an example:\n\n```\n{\n  \"continue\": true,\n  \"stopReason\": \"Security policy violation\",\n  \"systemMessage\": \"Unit tests failed\",\n  \"decision\": \"block\",\n  \"reason\": \"Post-processing validation failed\",\n  \"hookSpecificOutput\": {\n    \"hookEventName\": \"PostToolUse\",\n    \"additionalContext\": \"The edited file has lint errors that need to be fixed\"\n  }\n}\n```\n\n## Detecting when a specific file is edited\n\nWhile there is no direct way to determine whether a given file was edited, you can infer it by checking when known editing tools were executed against that file.\n\n### Step 1: Read from `stdin`\n\nThe first step is to read data from `stdin`\n\n. To do that, it helps to create a wrapper function:\n\n``` js\nfunction readStdin() {\n  return new Promise((resolve, reject) => {\n    let data = \"\";\n\n    process.stdin.setEncoding(\"utf8\");\n    process.stdin.on(\"data\", chunk => {\n      data += chunk;\n    });\n    process.stdin.on(\"end\", () => resolve(data));\n    process.stdin.on(\"error\", reject);\n  });\n}\n```\n\nThen you can read the data and parse it into an object:\n\n``` js\nconst raw = await readStdin();\nconst payload = JSON.parse(raw);\n```\n\n### Step 2: Detect changes to the file\n\nNow that you have the hook payload, you need to filter for the tools that can create or edit files. VS Code has several of these tools, so the first step is to define them.\n\n``` js\nconst WRITE_TOOL_NAMES = new Set([\n  \"apply_patch\",\n  \"create_file\",\n  \"replace_string_in_file\",\n  \"editFiles\",\n  \"createFile\",\n  \"edit\",\n  \"create\",\n  \"write\",\n  \"str_replace_editor\",\n  \"str_replace_based_edit_tool\",\n]);\n```\n\nThen you can filter the hook payload by `tool_name`\n\nto ensure you aren’t responding to unrelated tool calls:\n\n```\n// if this isn't a tool we're looking for then just exit\nif (!WRITE_TOOL_NAMES.has(payload.tool_name)) {\n  process.stdout.write(JSON.stringify({ continue: true }));\n  process.exit(0);\n}\n```\n\nAfter that, you can check whether the target filename is present in the `tool_input.files`\n\narray. Keep in mind that the file paths are relative to the current working directory, so if you want to calculate the directory containing the file, you’ll need to resolve it first. Here’s an example that looks for `wrangler.jsonc`\n\nand extracts the parent directory:\n\n``` js\nconst relativePath = payload.tool_input.files.find(\n  filePath => path.basename(filePath) === \"wrangler.jsonc\"\n);\n\n// file wasn't found so just exit\nif (!relativePath) {{\n  process.stdout.write(JSON.stringify({ continue: true }));\n  process.exit(0);  \n}}\n\nconst parentDirectory = path.dirname(path.resolve(payload.cwd, relativePath));\n```\n\nIf this code executes all the way through, that means a file named `wrangler.jsonc`\n\nwas edited, and you can run any additional code related to that change. Just be sure to exit gracefully once you’re done:\n\n```\n// do what you want with relativePath and parentDirectory\n\n// exit gracefully at the end of the script\nprocess.stdout.write(JSON.stringify({ continue: true }));\nprocess.exit(0);\n```\n\n### Step 3: Wire up the hook\n\nThe last step is to wire up the hook. By convention, hook scripts are stored in `.github/hooks/scripts`\n\n, so you can create a JSON file like this in `.github/hooks`\n\n:\n\n```\n{\n  \"hooks\": {\n    \"PostToolUse\": [\n      {\n        \"type\": \"command\",\n        \"command\": \"node ./scripts/detect-file-change.mjs\"\n      }\n    ]\n  }\n}\n```\n\nNow, whenever any file named `wrangler.jsonc`\n\nis edited, the hook command will execute.\n\n## Conclusion\n\nAgent hooks add a new layer of reliability to customizing the agent experience in VS Code. Instead of hoping the model remembers to perform a follow-up task, you can enforce that behavior with a deterministic command. It takes some setup, especially when you need to detect changes to a specific file, but the payoff is worth it. Once the hook is in place, the workflow becomes automatic, consistent, and one less thing to think about.", "url": "https://wpnews.pro/news/creating-a-vs-code-agent-hook-to-respond-to-file-changes", "canonical_source": "https://humanwhocodes.com/blog/2026/05/vscode-agent-hooks/", "published_at": "2026-05-26 00:00:00+00:00", "updated_at": "2026-06-16 14:28:08.853841+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "large-language-models"], "entities": ["Visual Studio Code", "Cloudflare Workers", "wrangler"], "alternates": {"html": "https://wpnews.pro/news/creating-a-vs-code-agent-hook-to-respond-to-file-changes", "markdown": "https://wpnews.pro/news/creating-a-vs-code-agent-hook-to-respond-to-file-changes.md", "text": "https://wpnews.pro/news/creating-a-vs-code-agent-hook-to-respond-to-file-changes.txt", "jsonld": "https://wpnews.pro/news/creating-a-vs-code-agent-hook-to-respond-to-file-changes.jsonld"}}