<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eliran Kotzero</title>
    <description>The latest articles on DEV Community by Eliran Kotzero (@eliran_kotzero_e1781dc487).</description>
    <link>https://dev.to/eliran_kotzero_e1781dc487</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4191090%2Fa3adef20-1c62-4911-982e-fcb9636d36a3.jpg</url>
      <title>DEV Community: Eliran Kotzero</title>
      <link>https://dev.to/eliran_kotzero_e1781dc487</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eliran_kotzero_e1781dc487"/>
    <language>en</language>
    <item>
      <title>Hooks in Claude Code, Cursor and Codex: enforce what rules can't</title>
      <dc:creator>Eliran Kotzero</dc:creator>
      <pubDate>Wed, 11 Nov 2026 07:16:06 +0000</pubDate>
      <link>https://dev.to/eliran_kotzero_e1781dc487/hooks-in-claude-code-cursor-and-codex-enforce-what-rules-cant-4ni2</link>
      <guid>https://dev.to/eliran_kotzero_e1781dc487/hooks-in-claude-code-cursor-and-codex-enforce-what-rules-cant-4ni2</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://kitquarry.com/articles/claude-code-cursor-codex-hooks" rel="noopener noreferrer"&gt;KitQuarry&lt;/a&gt;. Written by Eliran Kotzero for KitQuarry.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; a rule asks the agent to do something; a hook makes the tool do it. Claude Code, Cursor and Codex all run your scripts at fixed points: before a shell command, after a file edit, and when the agent wants to stop. That's where "never force-push", "format everything" and "run the tests before you say done" belong. Below are three small Python scripts that work in all three tools, the config for each, and the gotchas that make a hook quietly do nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why rules aren't enough
&lt;/h2&gt;

&lt;p&gt;Instruction files are context. Claude Code's permissions docs put it plainly: "Permission rules are enforced by Claude Code, not by the model. Instructions in your prompt or CLAUDE.md shape what Claude tries to do, but they don't change what Claude Code allows." A long or crowded rules file makes it worse (see &lt;a href="https://kitquarry.com/articles/why-long-claude-md-gets-ignored" rel="noopener noreferrer"&gt;why a long CLAUDE.md gets ignored&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Hooks are the other half. The Claude Code hooks guide describes them as "deterministic control: certain actions always happen rather than relying on the LLM to choose to run them." Keep the rule as the explanation and add a hook for the handful of things that must happen every time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Block&lt;/strong&gt; a command before it runs (force-push, &lt;code&gt;reset --hard&lt;/code&gt;, &lt;code&gt;DROP TABLE&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix up&lt;/strong&gt; after an edit (run the formatter, so style never comes up in review).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refuse "done"&lt;/strong&gt; while the checks fail, and send the agent back to work.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The same three hooks in each tool
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Claude Code&lt;/th&gt;
&lt;th&gt;Cursor&lt;/th&gt;
&lt;th&gt;Codex&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Before a shell command&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PreToolUse&lt;/code&gt;, matcher &lt;code&gt;Bash&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;beforeShellExecution&lt;/code&gt; (or &lt;code&gt;preToolUse&lt;/code&gt;, matcher &lt;code&gt;Shell&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PreToolUse&lt;/code&gt;, matcher &lt;code&gt;Bash&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After the agent edits a file&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PostToolUse&lt;/code&gt;, matcher &lt;code&gt;Edit&lt;/code&gt; or &lt;code&gt;Write&lt;/code&gt; (one regex, see config below)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;afterFileEdit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;PostToolUse&lt;/code&gt;, matcher &lt;code&gt;apply_patch&lt;/code&gt; (or &lt;code&gt;Edit&lt;/code&gt; / &lt;code&gt;Write&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When the agent wants to stop&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Stop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;stop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Stop&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How to block&lt;/td&gt;
&lt;td&gt;Exit 2 + reason on stderr, or JSON &lt;code&gt;permissionDecision&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;JSON &lt;code&gt;"permission": "deny"&lt;/code&gt;, or exit 2&lt;/td&gt;
&lt;td&gt;Exit 2 + reason on stderr, or JSON &lt;code&gt;permissionDecision&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How to keep it working&lt;/td&gt;
&lt;td&gt;Stop hook exits 2&lt;/td&gt;
&lt;td&gt;Return a &lt;code&gt;followup_message&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Stop hook exits 2 (becomes a new prompt)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Codex's hook format is close to Claude Code's: the same core event names, the same &lt;code&gt;matcher&lt;/code&gt; + &lt;code&gt;hooks&lt;/code&gt; nesting and the same &lt;code&gt;hookSpecificOutput&lt;/code&gt; JSON. Cursor has its own flatter format, and it also reads Claude Code's hooks (more on that below).&lt;/p&gt;

&lt;h2&gt;
  
  
  Where hooks live and when they run
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Claude Code&lt;/th&gt;
&lt;th&gt;Cursor&lt;/th&gt;
&lt;th&gt;Codex&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Project file&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.claude/settings.json&lt;/code&gt; (&lt;code&gt;.claude/settings.local.json&lt;/code&gt; for just you)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/hooks.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.codex/hooks.json&lt;/code&gt; or &lt;code&gt;[hooks]&lt;/code&gt; in &lt;code&gt;.codex/config.toml&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Personal file&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.claude/settings.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.cursor/hooks.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;~/.codex/hooks.json&lt;/code&gt; or &lt;code&gt;~/.codex/config.toml&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Before it runs&lt;/td&gt;
&lt;td&gt;Interactive sessions hold back hooks until you accept the workspace trust dialog. &lt;code&gt;-p&lt;/code&gt; and SDK runs treat the folder as trusted&lt;/td&gt;
&lt;td&gt;Project hooks run only in a trusted workspace&lt;/td&gt;
&lt;td&gt;Project hooks load only in a trusted project, and you review and trust each hook in &lt;code&gt;/hooks&lt;/code&gt;. A changed hook is skipped until you trust it again&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;See what's loaded&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/hooks&lt;/code&gt; (read-only)&lt;/td&gt;
&lt;td&gt;Hooks tab in Customize, Hooks output channel&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/hooks&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turn hooks off&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"disableAllHooks": true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Remove the entries (the docs list no global switch)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;hooks = false&lt;/code&gt; under &lt;code&gt;[features]&lt;/code&gt;, or disable one hook in &lt;code&gt;/hooks&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three merge hooks from every level instead of letting one file replace another, so a personal hook and a project hook for the same event both run. Hooks run with your user's permissions, so read the hooks in a repo you didn't write before you trust it. Claude Code's docs warn that &lt;code&gt;claude -p&lt;/code&gt; runs a repository's committed hooks without asking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup: one folder of scripts, three small configs
&lt;/h2&gt;

&lt;p&gt;Put the scripts in the repo, outside any one tool's folder, so all three tools and all your teammates use the same code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scripts/agent-hooks/guard_commands.py   # before shell commands
scripts/agent-hooks/format_edited.py    # after file edits
scripts/agent-hooks/require_checks.py   # when the agent wants to stop
.claude/settings.json                   # Claude Code
.cursor/hooks.json                      # Cursor
.codex/hooks.json                       # Codex
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each script reads the event JSON on stdin and works out which tool called it. Cursor sends a &lt;code&gt;cursor_version&lt;/code&gt; field with every hook and expects JSON back; Claude Code and Codex accept exit code 2 with the reason on stderr. They need only Python 3 and git.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hook 1: block risky commands
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;scripts/agent-hooks/guard_commands.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;span class="s2"&gt;"""Block risky shell commands. Claude Code / Codex PreToolUse hook, Cursor beforeShellExecution hook."""&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shlex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="no"&gt;PATTERNS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;  &lt;span class="c1"&gt;# (regex, why), checked against each part of a command line&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;"\bgit\b.*\bpush\b.*\s(--force(?![\w-])|-f\b)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"force push (use --force-with-lease, and ask first)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;"\bgit\b.*\breset\b.*\s--hard\b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"git reset --hard discards uncommitted work"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;"\bgit\b.*\bclean\b.*\s-[a-zA-Z]*f"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"git clean -f deletes untracked files"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;"\bgit\b.*\s--no-verify\b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--no-verify skips the repo's git hooks"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;"(?i)\b(drop\s+(table|database|schema)|truncate\s+table)\b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"destructive SQL"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;risky_rm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shlex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rsplit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"rm"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"--recursive"&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"--"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s2"&gt;"r"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"-"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"~"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"$"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;".."&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"recursive rm of {t} (too broad or outside the project)"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tool_input"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;" "&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;"&amp;amp;&amp;amp;|\|\||[;|\n]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;"^((sudo|command)\s+|\w+=\S*\s+)+"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;why&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="no"&gt;PATTERNS&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;why&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;why&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;risky_rm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;why&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;"\b(curl|wget)\b.*\|\s*(sudo\s+)?(ba|z)?sh\b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;why&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"piping a download into a shell"&lt;/span&gt;

&lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cursor_version"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;  &lt;span class="c1"&gt;# Cursor sends this field to every hook&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;why&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"permission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Blocked by a project hook: {why}. Don't work around it. Tell the user the exact command and why you need it."&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"permission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"user_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Blocked: {why}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"agent_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It splits the command line at &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, &lt;code&gt;||&lt;/code&gt;, &lt;code&gt;;&lt;/code&gt; and pipes, so &lt;code&gt;cd src &amp;amp;&amp;amp; rm -rf ../..&lt;/code&gt; is caught too. It blocks force-pushes (but not &lt;code&gt;--force-with-lease&lt;/code&gt;), &lt;code&gt;reset --hard&lt;/code&gt;, &lt;code&gt;clean -f&lt;/code&gt;, &lt;code&gt;--no-verify&lt;/code&gt;, &lt;code&gt;DROP&lt;/code&gt;/&lt;code&gt;TRUNCATE&lt;/code&gt; in SQL, piping a download into a shell, and recursive &lt;code&gt;rm&lt;/code&gt; of &lt;code&gt;/&lt;/code&gt;, &lt;code&gt;~&lt;/code&gt;, &lt;code&gt;$VAR&lt;/code&gt;, &lt;code&gt;..&lt;/code&gt;, &lt;code&gt;.&lt;/code&gt; or &lt;code&gt;*&lt;/code&gt;. &lt;code&gt;rm -rf node_modules dist&lt;/code&gt; still runs. The message tells the agent to stop and ask you, rather than just saying "blocked" and leaving it to try another way. Edit &lt;code&gt;PATTERNS&lt;/code&gt; to match your own never-list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code&lt;/strong&gt;, in &lt;code&gt;.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"PreToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"python3 \"$CLAUDE_PROJECT_DIR\"/scripts/agent-hooks/guard_commands.py"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"PostToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Edit|Write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"python3 \"$CLAUDE_PROJECT_DIR\"/scripts/agent-hooks/format_edited.py"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"Stop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"python3 \"$CLAUDE_PROJECT_DIR\"/scripts/agent-hooks/require_checks.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A hook that exits 2 blocks the call before permission rules are checked, so the guard holds even if you allow &lt;code&gt;Bash&lt;/code&gt; wholesale. The reverse isn't true: a hook can't approve a call that a deny or ask rule catches. Claude Code also offers an &lt;code&gt;if&lt;/code&gt; field (&lt;code&gt;"Bash(git *)"&lt;/code&gt;) to skip spawning the script for unrelated commands, but its docs call that filter best-effort, so the script above checks every command itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor&lt;/strong&gt;, in &lt;code&gt;.cursor/hooks.json&lt;/code&gt; (project hooks run from the project root, so the path is relative to it):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"beforeShellExecution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"python3 scripts/agent-hooks/guard_commands.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"failClosed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"afterFileEdit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"python3 scripts/agent-hooks/format_edited.py"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"stop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"python3 scripts/agent-hooks/require_checks.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"loop_limit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;failClosed: true&lt;/code&gt; blocks the command if the script crashes or times out. Without it, Cursor lets the command through. For commands it doesn't block, the script returns &lt;code&gt;allow&lt;/code&gt;, as Cursor's own &lt;code&gt;block-git.sh&lt;/code&gt; example does. That doesn't auto-approve anything: a Cursor team member &lt;a href="https://forum.cursor.com/t/beforeshellexecution-returns-permission-ask-but-sandboxed-agent-shell-still-runs-the-command-sandbox-true/155438" rel="noopener noreferrer"&gt;confirmed on the forum&lt;/a&gt; in March 2026 that only &lt;code&gt;deny&lt;/code&gt; is enforced from these hooks today, and &lt;code&gt;allow&lt;/code&gt; and &lt;code&gt;ask&lt;/code&gt; are ignored, so your normal approval settings still apply. Rely on &lt;code&gt;deny&lt;/code&gt; and nothing else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Codex&lt;/strong&gt;, in &lt;code&gt;.codex/hooks.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"PreToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"^Bash$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"python3 \"$(git rev-parse --show-toplevel)/scripts/agent-hooks/guard_commands.py\""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"statusMessage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"Checking command"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"PostToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"^apply_patch$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"python3 \"$(git rev-parse --show-toplevel)/scripts/agent-hooks/format_edited.py\""&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nl"&gt;"Stop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"python3 \"$(git rev-parse --show-toplevel)/scripts/agent-hooks/require_checks.py\""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;"timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Codex docs suggest resolving repo-local hook paths from the git root, because Codex may start in a subfolder. &lt;code&gt;^Bash$&lt;/code&gt; covers both shell commands and unified exec sessions. After you add the file, open &lt;code&gt;/hooks&lt;/code&gt; and trust the hooks, or they won't run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hook 2: format what the agent edits
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;scripts/agent-hooks/format_edited.py&lt;/code&gt; runs Prettier (from the repo's &lt;code&gt;node_modules&lt;/code&gt;), &lt;code&gt;ruff format&lt;/code&gt; or &lt;code&gt;gofmt&lt;/code&gt; on edited files, and skips any formatter that isn't installed. Change &lt;code&gt;FORMATTERS&lt;/code&gt; to your stack.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;span class="s2"&gt;"""Format files after the agent edits them. Claude Code / Codex PostToolUse hook, Cursor afterFileEdit hook."""&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"workspace_roots"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getcwd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"rev-parse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--show-toplevel"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                   &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;prettier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s2"&gt;"node_modules"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s2"&gt;".bin"&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s2"&gt;"prettier"&lt;/span&gt;
&lt;span class="no"&gt;FORMATTERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;  &lt;span class="c1"&gt;# extension -&amp;gt; command; a formatter that isn't installed is skipped&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;".js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".jsx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".tsx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".css"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;".md"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prettier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--write"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;".py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ruff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;".go"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"gofmt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-w"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"tool_input"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"file_path"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Claude Code Edit/Write and Cursor afterFileEdit name the file&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Codex apply_patch doesn't: format every modified or new file instead&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ls-files"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-m"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"-o"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--exclude-standard"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;
    &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"\0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;exts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="no"&gt;FORMATTERS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;exts&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;which&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s2"&gt;"cursor_version"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; sends the absolute path in &lt;code&gt;tool_input.file_path&lt;/code&gt; for &lt;code&gt;Edit&lt;/code&gt; and &lt;code&gt;Write&lt;/code&gt;, and &lt;strong&gt;Cursor&lt;/strong&gt;'s &lt;code&gt;afterFileEdit&lt;/code&gt; sends &lt;code&gt;file_path&lt;/code&gt;, so only that file is formatted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex&lt;/strong&gt; edits files with &lt;code&gt;apply_patch&lt;/code&gt; and sends the patch text, not a path, so the script formats every modified or new file git reports. That includes files you changed by hand.&lt;/li&gt;
&lt;li&gt;It runs after the edit has happened and always exits 0, so it never blocks the agent.&lt;/li&gt;
&lt;li&gt;Claude Code doesn't run an &lt;code&gt;Edit|Write&lt;/code&gt; hook when a Bash command rewrites a file (a &lt;code&gt;sed -i&lt;/code&gt;, a codegen script). Its docs point to the &lt;code&gt;FileChanged&lt;/code&gt; event for that.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hook 3: don't let the agent stop while checks fail
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;scripts/agent-hooks/require_checks.py&lt;/code&gt; runs one command when the agent tries to finish. If it fails, the agent gets the last 40 lines of output and is sent back to work. Set &lt;code&gt;CHECK&lt;/code&gt; to your fastest meaningful check, because it runs every time the agent stops, including after a plain question.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3&lt;/span&gt;
&lt;span class="s2"&gt;"""Stop hook: keep the agent working while the checks fail. Claude Code, Codex and Cursor."""&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="no"&gt;CHECK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"npm test"&lt;/span&gt;  &lt;span class="c1"&gt;# your fastest meaningful check, e.g. "npm run typecheck &amp;amp;&amp;amp; npm test"&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cursor_version"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"workspace_roots"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getcwd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"stop_hook_active"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# already pushed back once this turn, or the run was aborted or errored&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"git"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"--porcelain"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# nothing changed, e.g. the agent only answered a question&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CHECK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;returncode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;done&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"\n"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"`{CHECK}` fails. Fix the code, not the tests, then finish. Output:\n{tail}"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"followup_message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each tool keeps the agent going in its own way: Claude Code and Codex on exit code 2 (Codex turns the reason into a new prompt), Cursor when the hook returns a &lt;code&gt;followup_message&lt;/code&gt;. Each also has a loop guard. Claude Code and Codex set &lt;code&gt;stop_hook_active&lt;/code&gt; once a Stop hook has already sent the agent back, and the script then lets it stop, so you get one retry instead of an endless loop. Cursor caps follow-ups with &lt;code&gt;loop_limit&lt;/code&gt; (5 by default; the config above sets 3). The script also skips the check when &lt;code&gt;git status&lt;/code&gt; is clean. For the Claude Code details (the eight-block cap, testing a Stop hook by hand) see the "make done mean the tests passed" section of &lt;a href="https://kitquarry.com/articles/why-long-claude-md-gets-ignored" rel="noopener noreferrer"&gt;why a long CLAUDE.md gets ignored&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using your Claude Code hooks in Cursor
&lt;/h2&gt;

&lt;p&gt;Cursor loads hooks from &lt;code&gt;.claude/settings.json&lt;/code&gt;, &lt;code&gt;.claude/settings.local.json&lt;/code&gt; and &lt;code&gt;~/.claude/settings.json&lt;/code&gt; when &lt;strong&gt;Include Third-Party Plugins, Skills, and Other Configs&lt;/strong&gt; is on (Cursor Settings → Agents → Third-Party Imports; on by default). It maps &lt;code&gt;PreToolUse&lt;/code&gt;, &lt;code&gt;PostToolUse&lt;/code&gt;, &lt;code&gt;Stop&lt;/code&gt;, &lt;code&gt;SubagentStop&lt;/code&gt;, &lt;code&gt;SessionStart&lt;/code&gt;, &lt;code&gt;SessionEnd&lt;/code&gt;, &lt;code&gt;PreCompact&lt;/code&gt; and &lt;code&gt;UserPromptSubmit&lt;/code&gt; to its own events, maps the tool names &lt;code&gt;Bash&lt;/code&gt; to &lt;code&gt;Shell&lt;/code&gt; and &lt;code&gt;Edit&lt;/code&gt; to &lt;code&gt;Write&lt;/code&gt;, and accepts Claude Code's JSON responses. &lt;code&gt;Notification&lt;/code&gt; and &lt;code&gt;PermissionRequest&lt;/code&gt; aren't supported.&lt;/p&gt;

&lt;p&gt;Two consequences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A repo with only &lt;code&gt;.claude/settings.json&lt;/code&gt; already gets these hooks in Cursor. The scripts should work on that path too, because every Cursor hook input includes &lt;code&gt;cursor_version&lt;/code&gt;, but we tested only the native &lt;code&gt;.cursor/hooks.json&lt;/code&gt; input format.&lt;/li&gt;
&lt;li&gt;If you also add &lt;code&gt;.cursor/hooks.json&lt;/code&gt;, Cursor runs the hooks from both files. For the guard that's harmless (any &lt;code&gt;deny&lt;/code&gt; wins); for the formatter and the stop check it's double work. Pick one source per hook. Cursor-only options such as &lt;code&gt;failClosed&lt;/code&gt;, &lt;code&gt;loop_limit&lt;/code&gt; and &lt;code&gt;beforeShellExecution&lt;/code&gt; go in the native file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why a hook silently does nothing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exit 1 isn't a block.&lt;/strong&gt; In Claude Code, any exit code other than 0 or 2 is a non-blocking error and the action goes ahead; the docs say "If your hook is meant to enforce a policy, use exit 2." Cursor fails open on crashes, timeouts and exit codes other than 2 too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failures fail open by default.&lt;/strong&gt; A wrong path, a missing &lt;code&gt;python3&lt;/code&gt; or a timeout lets the command run. Set &lt;code&gt;"failClosed": true&lt;/code&gt; in Cursor, or &lt;code&gt;"onFailure": "block"&lt;/code&gt; on the handler in Claude Code (v2.1.295 or later). The Codex hooks page doesn't document an equivalent switch for command hooks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The hook isn't trusted or isn't loaded.&lt;/strong&gt; Check &lt;code&gt;/hooks&lt;/code&gt; in Claude Code and Codex, or the Hooks tab and output channel in Cursor. In Codex, editing a hook definition marks it for review again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong path for the tool.&lt;/strong&gt; Cursor runs project hooks from the project root and user hooks from &lt;code&gt;~/.cursor/&lt;/code&gt;. Claude Code gives you &lt;code&gt;$CLAUDE_PROJECT_DIR&lt;/code&gt;. Codex may start in a subfolder, hence &lt;code&gt;git rev-parse --show-toplevel&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stray output.&lt;/strong&gt; Claude Code reads stdout as JSON only if it's a single JSON object with nothing around it, and Codex treats plain text from a &lt;code&gt;Stop&lt;/code&gt; hook that exits 0 as invalid. Print nothing, or exactly one object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The work happened somewhere the hook doesn't watch.&lt;/strong&gt; Codex doesn't hook hosted tools such as web search. Cursor cloud agents run project hooks but not &lt;code&gt;~/.cursor/hooks.json&lt;/code&gt;. Claude Code cloud sessions don't read your local &lt;code&gt;~/.claude/settings.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows.&lt;/strong&gt; Cursor runs hook commands through PowerShell on Windows, and &lt;code&gt;python3&lt;/code&gt; may not exist there. Use &lt;code&gt;py -3&lt;/code&gt; or &lt;code&gt;python&lt;/code&gt;; Codex also takes a &lt;code&gt;commandWindows&lt;/code&gt; override.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Hooks are guardrails, not a sandbox
&lt;/h2&gt;

&lt;p&gt;The guard matches text. A Python one-liner that calls &lt;code&gt;shutil.rmtree()&lt;/code&gt; on the same folder doesn't contain &lt;code&gt;rm -rf&lt;/code&gt;, and a commit message that mentions &lt;code&gt;push -f&lt;/code&gt; gets blocked by mistake. Codex's docs say it directly: "Treat tool hooks as a useful guardrail, not a complete enforcement boundary." For limits that must hold, use each tool's permission rules and sandbox. We cover those settings for secrets in &lt;a href="https://kitquarry.com/articles/stop-ai-agents-reading-env-files" rel="noopener noreferrer"&gt;how to stop Claude Code, Cursor and Codex reading your .env file&lt;/a&gt;. Use hooks to make the team's never-list and definition of done automatic, and keep a &lt;a href="https://kitquarry.com/articles/ai-coding-agent-security-rules" rel="noopener noreferrer"&gt;short security rule&lt;/a&gt; so the agent knows why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test a hook before you trust it
&lt;/h2&gt;

&lt;p&gt;Pipe the JSON each tool sends into the exact command from your config and check the exit code and output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Claude Code / Codex shape: expect exit 2 and the reason on stderr&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"tool_name":"Bash","tool_input":{"command":"git push --force origin main"}}'&lt;/span&gt; \
  &lt;span class="o"&gt;|&lt;/span&gt; python3 scripts/agent-hooks/guard_commands.py&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit $?"&lt;/span&gt;

&lt;span class="c"&gt;# Cursor shape: expect {"permission": "deny", ...} and exit 0&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"cursor_version":"x","command":"git reset --hard HEAD~3"}'&lt;/span&gt; \
  &lt;span class="o"&gt;|&lt;/span&gt; python3 scripts/agent-hooks/guard_commands.py&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit $?"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We ran the three scripts this way on a sample repo, using each tool's documented input format and the exact command strings from the three configs: 27 commands through the guard for each tool, the formatter for a Claude Code edit, a Cursor edit and a Codex patch started from a subfolder, and the stop check with a clean tree, a passing change, a failing change and a repeat stop. All passed. We didn't run them inside Claude Code, Cursor or Codex, so watch &lt;code&gt;/hooks&lt;/code&gt; or Cursor's Hooks output channel the first time each one fires.&lt;/p&gt;

&lt;h2&gt;
  
  
  A ten-minute setup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Copy the three scripts into &lt;code&gt;scripts/agent-hooks/&lt;/code&gt; and set &lt;code&gt;PATTERNS&lt;/code&gt;, &lt;code&gt;FORMATTERS&lt;/code&gt; and &lt;code&gt;CHECK&lt;/code&gt; for your repo.&lt;/li&gt;
&lt;li&gt;Add the config for each tool you use and commit it, so teammates and worktrees get the same hooks.&lt;/li&gt;
&lt;li&gt;Trust the hooks: accept the workspace trust dialog in Claude Code, trust the workspace in Cursor, and trust each hook in Codex's &lt;code&gt;/hooks&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run the two test commands above, then ask the agent to force-push and confirm it's blocked.&lt;/li&gt;
&lt;li&gt;Leave one line per hook in your rules file ("force-pushes are blocked by a hook; ask me instead") so the agent understands the block instead of fighting it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What's the difference between a rule and a hook?
&lt;/h3&gt;

&lt;p&gt;A rule (CLAUDE.md, AGENTS.md, a Cursor .mdc file) is text the model reads and usually follows. A hook is a script the tool itself runs at a fixed point, such as before a shell command or when the agent wants to stop, whatever the model decided. Use rules to explain what you want and hooks for the few things that must happen every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do Claude Code hooks work in Cursor?
&lt;/h3&gt;

&lt;p&gt;Yes, by default. Cursor loads hooks from .claude/settings.json, .claude/settings.local.json and ~/.claude/settings.json when Include Third-Party Plugins, Skills, and Other Configs is on, and maps PreToolUse, PostToolUse, Stop and the other main events to its own. Bash maps to Shell and Edit to Write. If you also add .cursor/hooks.json, hooks from both files run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Codex support hooks?
&lt;/h3&gt;

&lt;p&gt;Yes. Codex reads hooks from hooks.json or inline &lt;code&gt;[hooks]&lt;/code&gt; tables in config.toml, in ~/.codex or a project's .codex folder. Project hooks load only in a trusted project, and every non-managed hook must be reviewed and trusted in /hooks before it runs. The events and JSON shapes follow Claude Code's closely: PreToolUse, PostToolUse, Stop, SessionStart and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I block a command in a Claude Code hook?
&lt;/h3&gt;

&lt;p&gt;Register a PreToolUse hook with the matcher Bash. The script reads the command from tool_input.command on stdin, and to block it either exits with code 2 and writes the reason to stderr, or exits 0 and prints a JSON permissionDecision of "deny". Exit code 1 doesn't block: Claude Code treats it as a non-blocking error and runs the command.&lt;/p&gt;

&lt;h3&gt;
  
  
  Are hooks a security boundary?
&lt;/h3&gt;

&lt;p&gt;No. A command hook matches text, so a script that does the same thing under another name gets through, and by default a hook that crashes or times out lets the action proceed. Codex's docs call tool hooks a useful guardrail, not a complete enforcement boundary. Use permission rules and the sandbox for hard limits, and hooks for team policy and checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;Official docs, checked 2026-11-01.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/hooks" rel="noopener noreferrer"&gt;Claude Code docs: Hooks reference (events, exit codes, onFailure, workspace trust)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/hooks-guide" rel="noopener noreferrer"&gt;Claude Code docs: Automate actions with hooks (guide)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/permissions" rel="noopener noreferrer"&gt;Claude Code docs: Configure permissions (hooks vs permission rules)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cursor.com/docs/hooks" rel="noopener noreferrer"&gt;Cursor docs: Hooks (hooks.json, failClosed, beforeShellExecution, afterFileEdit, stop)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cursor.com/docs/reference/third-party-hooks" rel="noopener noreferrer"&gt;Cursor docs: Third Party Hooks (Claude Code hooks in Cursor)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.chatgpt.com/docs/hooks" rel="noopener noreferrer"&gt;Codex docs (ChatGPT Learn): Hooks (hooks.json, trust review, PreToolUse, Stop)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you want a starting point, KitQuarry's free sampler is on GitHub: &lt;a href="https://github.com/elirankotzero/ai-coding-rules-sampler" rel="noopener noreferrer"&gt;ai-coding-rules-sampler&lt;/a&gt;. It has a short CLAUDE.md/AGENTS.md, security rules and three skills (plan-feature, review-changes, record-decisions) for Claude Code, Cursor, Codex and Windsurf. MIT, free. (I made it.)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>cursor</category>
      <category>codex</category>
    </item>
    <item>
      <title>Too many agent skills? What they cost in context and how to prune</title>
      <dc:creator>Eliran Kotzero</dc:creator>
      <pubDate>Sun, 08 Nov 2026 07:11:55 +0000</pubDate>
      <link>https://dev.to/eliran_kotzero_e1781dc487/too-many-agent-skills-what-they-cost-in-context-and-how-to-prune-3kph</link>
      <guid>https://dev.to/eliran_kotzero_e1781dc487/too-many-agent-skills-what-they-cost-in-context-and-how-to-prune-3kph</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://kitquarry.com/articles/too-many-agent-skills-context-bloat" rel="noopener noreferrer"&gt;KitQuarry&lt;/a&gt;. Written by Eliran Kotzero for KitQuarry.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; an installed skill costs context in two ways. Its name and description sit in a listing the model sees on every turn. Its full &lt;code&gt;SKILL.md&lt;/code&gt; enters the conversation once it's used and stays there. The listing has a budget (1% of the context window in Claude Code, 2% in Codex by default), and when it overflows, descriptions get cut and skills stop triggering. Audit what's installed, turn off what you don't use, make side-effect skills manual-only, move repo-specific skills into the repo, and keep every description short with the trigger words first.&lt;/p&gt;

&lt;h2&gt;
  
  
  How skills load
&lt;/h2&gt;

&lt;p&gt;All three tools follow the Agent Skills standard and use progressive disclosure: the model starts with a short entry per skill and reads the rest only when it picks one.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;At session start&lt;/th&gt;
&lt;th&gt;When a skill is used&lt;/th&gt;
&lt;th&gt;Listing budget&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;Name and description of every skill Claude can invoke&lt;/td&gt;
&lt;td&gt;Rendered &lt;code&gt;SKILL.md&lt;/code&gt; enters the conversation and stays across turns&lt;/td&gt;
&lt;td&gt;Scales at 1% of the context window; each entry capped at 1,536 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex&lt;/td&gt;
&lt;td&gt;Name, description and file path&lt;/td&gt;
&lt;td&gt;Reads the full &lt;code&gt;SKILL.md&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;At most 2% of the context window, or 8,000 characters if unknown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;Discovers skills and presents them to Agent, which decides when they're relevant&lt;/td&gt;
&lt;td&gt;Loads the skill; resources load on demand&lt;/td&gt;
&lt;td&gt;Not documented&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So the skill you never use isn't free. Claude Code's docs put it plainly: every skill in the listing adds to your context on every turn, whether or not Claude ever uses it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually costs context
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part of a skill&lt;/th&gt;
&lt;th&gt;When it costs context&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;name&lt;/code&gt; + &lt;code&gt;description&lt;/code&gt; (+ &lt;code&gt;when_to_use&lt;/code&gt; in Claude Code)&lt;/td&gt;
&lt;td&gt;Every turn, for every skill the model can invoke on its own&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Body of &lt;code&gt;SKILL.md&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;From the moment it's invoked. In Claude Code it stays in the conversation on later turns, so every line is a recurring cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supporting files (&lt;code&gt;references/&lt;/code&gt;, &lt;code&gt;examples.md&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Only when the agent reads them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scripts (&lt;code&gt;scripts/&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Executed, not loaded. Only their output reaches the context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual-only skills (&lt;code&gt;disable-model-invocation: true&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Claude Code leaves the description out of the listing entirely, so nothing until you type &lt;code&gt;/name&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skills preloaded into a Claude Code subagent&lt;/td&gt;
&lt;td&gt;The full content is injected when that subagent starts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The listing is where too many skills hurt. In Claude Code every skill name always stays in it, but when the descriptions don't fit the budget, Claude Code drops descriptions, starting with the skills you invoke least. A skill listed by name only loses the keywords Claude matches against, so it stops triggering on its own. Codex works the same way in its own terms: it shortens descriptions first, and with a large skill set it may leave some skills out of the initial list and show a warning.&lt;/p&gt;

&lt;p&gt;After auto-compaction, Claude Code re-attaches each invoked skill but keeps only its first 5,000 tokens, within a combined 25,000-token budget filled from the most recently used skill. Invoke many skills in one long session and the older ones can drop out entirely.&lt;/p&gt;

&lt;h2&gt;
  
  
  How many skills is too many?
&lt;/h2&gt;

&lt;p&gt;None of the three tools publishes a maximum count, and the number that matters isn't the count. It's whether the descriptions you rely on still fit. Signs you're past it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code:&lt;/strong&gt; run &lt;code&gt;/doctor&lt;/code&gt; for an estimate of the listing's context cost and its biggest contributors. The Skills row in &lt;code&gt;/context&lt;/code&gt; shows the listing's size after the budget is applied. When the listing exceeds its budget, a warning goes to the debug log (&lt;code&gt;claude --debug&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex:&lt;/strong&gt; a warning that some skills were left out of the initial list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Any tool:&lt;/strong&gt; a skill that used to trigger now only runs when you call it by name.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can raise the budget (&lt;code&gt;skillListingBudgetFraction&lt;/code&gt;, for example &lt;code&gt;0.02&lt;/code&gt; for 2%, or &lt;code&gt;SLASH_COMMAND_TOOL_CHAR_BUDGET&lt;/code&gt; in Claude Code; &lt;code&gt;skills.max_context_tokens&lt;/code&gt;, up to 10,000 tokens, in Codex). That trades context for discoverability. Pruning usually gets you more.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: audit what's installed
&lt;/h2&gt;

&lt;p&gt;Skills come from more places than most people check. Claude Code loads them from enterprise, personal (&lt;code&gt;~/.claude/skills&lt;/code&gt;), project (&lt;code&gt;.claude/skills&lt;/code&gt;, from the launch folder up to the repo root), nested subfolders, &lt;code&gt;--add-dir&lt;/code&gt; directories, plugins (as &lt;code&gt;/plugin-name:skill-name&lt;/code&gt;), and skills synced from your claude.ai account (&lt;code&gt;~/.claude/skills/synced/&lt;/code&gt;). Codex reads &lt;code&gt;.agents/skills&lt;/code&gt; from the launch folder up to the repo root, &lt;code&gt;~/.agents/skills&lt;/code&gt;, &lt;code&gt;/etc/codex/skills&lt;/code&gt; and its bundled system skills. Cursor reads &lt;code&gt;.agents/skills&lt;/code&gt;, &lt;code&gt;.cursor/skills&lt;/code&gt; and their &lt;code&gt;~/&lt;/code&gt; versions, and for compatibility also &lt;code&gt;.claude/skills&lt;/code&gt;, &lt;code&gt;.codex/skills&lt;/code&gt;, &lt;code&gt;~/.claude/skills&lt;/code&gt; and &lt;code&gt;~/.codex/skills&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code:&lt;/strong&gt; &lt;code&gt;/skills&lt;/code&gt; lists every skill and groups synced ones under &lt;code&gt;claude.ai sync&lt;/code&gt;. &lt;code&gt;/skill-doctor&lt;/code&gt; (v2.1.252 or later) shows what each skill costs and how often it's used, flags skills that have never been invoked, and says where to turn them off. Start with the ones that cost the most.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex:&lt;/strong&gt; run &lt;code&gt;/skills&lt;/code&gt; to see what's available.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor:&lt;/strong&gt; open Customize in the sidebar, then Skills.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a quick look across all three tools' folders, this script lists every &lt;code&gt;SKILL.md&lt;/code&gt; visible from the current folder (personal folders, then project folders up to the repo root), sorted by description length, and marks manual-only skills:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;List agent skills visible from this folder, with description size and body length.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;

&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;home&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;git&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rev-parse&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--show-toplevel&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                        &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;
&lt;span class="n"&gt;dirs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.claude/skills&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.agents/skills&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.cursor/skills&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.codex/skills&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cwd&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# project folders from here up to the repo root
&lt;/span&gt;    &lt;span class="n"&gt;dirs&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.claude/skills&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.agents/skills&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.cursor/skills&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.codex/skills&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;

&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;found&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*/SKILL.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SKILL.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_dir&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replace&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;---\n(.*?)\n---\n?(.*)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="nf"&gt;else &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^description:\s*(.*)$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;manual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^disable-model-invocation:\s*(true|yes|on|1)\s*$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;desc&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;manual&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;manual&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;skill&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;desc&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;lines&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;dl&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;auto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; skills, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;auto&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; description characters in auto-invocable skills&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with &lt;code&gt;python3 list_skills.py&lt;/code&gt; from your project. It reads single-line &lt;code&gt;description:&lt;/code&gt; values only, follows symlinked skill folders and counts each target once. It can't see plugin, bundled or enterprise skills, or nested skills below the current folder, so use &lt;code&gt;/skill-doctor&lt;/code&gt; for the full Claude Code picture. We tested it on a sample repo; it doesn't call any of the tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: turn off or remove what you don't use
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Where the skill comes from&lt;/th&gt;
&lt;th&gt;To remove it&lt;/th&gt;
&lt;th&gt;To keep it but hide it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code personal or project&lt;/td&gt;
&lt;td&gt;Delete &lt;code&gt;~/.claude/skills/&amp;lt;name&amp;gt;/&lt;/code&gt; or &lt;code&gt;.claude/skills/&amp;lt;name&amp;gt;/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;skillOverrides&lt;/code&gt;, or &lt;code&gt;disable-model-invocation: true&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code plugin&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;/plugin uninstall&lt;/code&gt; or disable it in &lt;code&gt;/plugin&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Not covered by &lt;code&gt;skillOverrides&lt;/code&gt;; manage it in &lt;code&gt;/plugin&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Synced from claude.ai&lt;/td&gt;
&lt;td&gt;Turn it off for your claude.ai account (deleting the folder makes the next sync download it again)&lt;/td&gt;
&lt;td&gt;Same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code bundled&lt;/td&gt;
&lt;td&gt;&lt;code&gt;disableBundledSkills: true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"off"&lt;/code&gt; in &lt;code&gt;skillOverrides&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex local&lt;/td&gt;
&lt;td&gt;Delete the folder&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;[[skills.config]]&lt;/code&gt; with &lt;code&gt;enabled = false&lt;/code&gt;, then restart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;Delete the folder&lt;/td&gt;
&lt;td&gt;&lt;code&gt;disable-model-invocation: true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Claude Code's &lt;code&gt;skillOverrides&lt;/code&gt; has four states. In the &lt;code&gt;/skills&lt;/code&gt; menu, highlight a skill and press Space to cycle them, then Esc to save to &lt;code&gt;.claude/settings.local.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Listed to Claude&lt;/th&gt;
&lt;th&gt;In the &lt;code&gt;/&lt;/code&gt; menu&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"on"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Name and description&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"name-only"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Name only&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"user-invocable-only"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hidden&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;"off"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Hidden&lt;/td&gt;
&lt;td&gt;Hidden&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;.claude/settings.local.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skillOverrides"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"old-helper"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"off"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"release-notes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user-invocable-only"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"legacy-context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"name-only"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.codex/config.toml&lt;/span&gt;
&lt;span class="k"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;skills&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="k"&gt;]]&lt;/span&gt;
&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/home/you/.agents/skills/old-helper/SKILL.md"&lt;/span&gt;
&lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3: make side-effect skills manual-only
&lt;/h2&gt;

&lt;p&gt;Deploy, commit, release and "send a message" skills shouldn't fire on their own anyway. Marking them manual-only also takes them out of the budget in Claude Code, where the description is no longer in context at all.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploys the current branch to staging or production. Only when the user asks to deploy.&lt;/span&gt;
&lt;span class="na"&gt;disable-model-invocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor reads the same field: the skill is included only when you type &lt;code&gt;/skill-name&lt;/code&gt;. In Codex, set &lt;code&gt;allow_implicit_invocation: false&lt;/code&gt; under &lt;code&gt;policy&lt;/code&gt; in the skill's &lt;code&gt;agents/openai.yaml&lt;/code&gt;; explicit &lt;code&gt;$skill&lt;/code&gt; invocation still works. The Codex docs don't say whether that also removes it from the initial list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: consolidate and slim the rest
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Merge overlapping skills.&lt;/strong&gt; Three skills for "write tests", "add coverage" and "fix failing tests" compete for the same requests. One skill with a clear description and three sections in its body costs one listing entry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep each skill to one job.&lt;/strong&gt; That's the Codex docs' first best practice, and it's what lets a short description be accurate. Merging overlaps and keeping one job per skill aren't in conflict: merge skills that do the same job, split skills that do several.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move reference material out of &lt;code&gt;SKILL.md&lt;/code&gt;.&lt;/strong&gt; Keep the body under 500 lines and put API details or long examples in files it links to. They load only when the agent reads them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write the body as instructions, not explanation.&lt;/strong&gt; In Claude Code an invoked body stays in context across turns, so the docs suggest stating what to do rather than narrating how or why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turn always-loaded procedures into skills.&lt;/strong&gt; The reverse problem also exists: a multi-step procedure in &lt;code&gt;CLAUDE.md&lt;/code&gt; or an always-on rule loads every session, while a skill's body loads only when it's used.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 5: short, trigger-first descriptions
&lt;/h2&gt;

&lt;p&gt;The description does two jobs: it's what the model matches your request against, and it's the part every skill pays for on every turn. Claude Code caps each entry's combined &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;when_to_use&lt;/code&gt; at 1,536 characters, and Codex shortens descriptions when the list is crowded. Both docs say the same thing: put the key use case and trigger words first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;This skill is a comprehensive helper that can assist with many aspects of testing, including writing new tests, reviewing existing tests, improving coverage, and general quality work across the codebase.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Writes and runs unit tests for changed code. Use when the user asks to add tests, raise coverage, or reproduce a bug with a failing test. Not for end-to-end tests.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second is shorter and names the requests people actually type. For the full pattern and a test loop, see &lt;a href="https://kitquarry.com/articles/claude-code-skill-md-that-triggers" rel="noopener noreferrer"&gt;how to write a SKILL.md that actually triggers&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: scope skills to where they're used
&lt;/h2&gt;

&lt;p&gt;A personal skill loads in every project on your machine. If you use it in one repo, move it there.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Personal folders&lt;/strong&gt; (&lt;code&gt;~/.claude/skills&lt;/code&gt;, &lt;code&gt;~/.agents/skills&lt;/code&gt;, &lt;code&gt;~/.cursor/skills&lt;/code&gt;): only skills you want in every repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project folders&lt;/strong&gt; (&lt;code&gt;.claude/skills&lt;/code&gt;, &lt;code&gt;.agents/skills&lt;/code&gt;): skills for this repo, committed so the team gets them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nested folders&lt;/strong&gt; for one package in a monorepo. Claude Code doesn't load skills in a &lt;code&gt;.claude/skills&lt;/code&gt; below the launch folder at startup; they load the first time Claude reads or edits a file in that subdirectory. Cursor scopes a nested &lt;code&gt;.cursor/skills&lt;/code&gt; or &lt;code&gt;.agents/skills&lt;/code&gt; to files in its directory. Codex scans from the launch folder up to the repo root, so a skill in &lt;code&gt;services/payments/.agents/skills&lt;/code&gt; is picked up when you start Codex in that service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;paths&lt;/code&gt; frontmatter&lt;/strong&gt; (Claude Code and Cursor): glob patterns that limit when a skill is used. Claude Code loads it automatically only when working with matching files; Cursor surfaces it only then.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;migration-rules&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Rules for writing database migrations. Use when creating or editing a migration.&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;db/migrations/**"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A ten-minute cleanup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;/skill-doctor&lt;/code&gt; in Claude Code (or the script above) and list skills you haven't used in a month.&lt;/li&gt;
&lt;li&gt;Delete or turn off each one. For plugin skills, disable the plugin.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;disable-model-invocation: true&lt;/code&gt; to every skill with side effects.&lt;/li&gt;
&lt;li&gt;Merge skills that answer the same requests.&lt;/li&gt;
&lt;li&gt;Move repo-specific personal skills into their repos, and package-specific ones into nested folders.&lt;/li&gt;
&lt;li&gt;Rewrite each remaining description: what it does, then "Use when..." with real phrasings, under a few hundred characters.&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;/context&lt;/code&gt; or &lt;code&gt;/doctor&lt;/code&gt; again, then test the skills you kept in a fresh session.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The same thinking applies to instruction files: see &lt;a href="https://kitquarry.com/articles/why-long-claude-md-gets-ignored" rel="noopener noreferrer"&gt;why a long CLAUDE.md gets ignored&lt;/a&gt;, and for keeping one set of rules across tools, &lt;a href="https://kitquarry.com/articles/agents-md-vs-claude-md-single-source-of-truth" rel="noopener noreferrer"&gt;AGENTS.md vs CLAUDE.md&lt;/a&gt;. MCP servers have their own per-tool files, covered in &lt;a href="https://kitquarry.com/articles/mcp-config-claude-code-cursor-codex" rel="noopener noreferrer"&gt;one MCP server, three configs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How many Claude Code skills is too many?
&lt;/h3&gt;

&lt;p&gt;None of the three tools publishes a maximum. The limit that matters is the skill listing budget. In Claude Code it scales at 1% of the context window, and when the listing overflows, Claude Code drops descriptions, starting with the skills you invoke least. Once a skill's description is gone, Claude can't match your request to it. Run /doctor for the listing's cost and /skill-doctor to find skills you never use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do installed skills use context even if I never use them?
&lt;/h3&gt;

&lt;p&gt;Their listing entry does. Claude Code's docs say every skill in the listing adds to your context on every turn, whether or not Claude uses it. The SKILL.md body loads only when the skill is invoked. A skill with disable-model-invocation: true is left out of the listing, so it costs nothing until you call it.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I turn off a skill without deleting it?
&lt;/h3&gt;

&lt;p&gt;In Claude Code, set it to "off" or "name-only" in skillOverrides, or use the /skills menu, which saves to .claude/settings.local.json. Plugin skills ignore skillOverrides, so disable the plugin with /plugin. In Codex, add a &lt;code&gt;[[skills.config]]&lt;/code&gt; entry with the SKILL.md path and enabled = false to ~/.codex/config.toml, then restart Codex.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much of the context window can Codex's skill list use?
&lt;/h3&gt;

&lt;p&gt;By default the initial list uses at most 2% of the model's context window, or 8,000 characters when the window size is unknown. With many skills, Codex shortens descriptions first and may leave some skills out with a warning. &lt;code&gt;skills.max_context_tokens&lt;/code&gt; sets a different budget, up to 10,000 tokens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should skills be personal or per project?
&lt;/h3&gt;

&lt;p&gt;Keep only skills you use in every repo in ~/.claude/skills or ~/.agents/skills. Put repo-specific skills in that repo's .claude/skills or .agents/skills, and module-specific ones in a nested folder. Claude Code loads nested skills only once it works on files there, and Cursor scopes nested skills to their directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;Official docs, checked 2026-10-30.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/skills" rel="noopener noreferrer"&gt;Claude Code docs: Extend Claude with skills (listing budget, skillOverrides, /skill-doctor)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;Claude Code docs: How Claude remembers your project (rules vs skills)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.chatgpt.com/docs/build-skills" rel="noopener noreferrer"&gt;Codex docs (ChatGPT Learn): Build skills (initial list budget, skills.config)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cursor.com/docs/context/skills" rel="noopener noreferrer"&gt;Cursor docs: Agent Skills (directories, paths, nested skills)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you want a starting point, KitQuarry's free sampler is on GitHub: &lt;a href="https://github.com/elirankotzero/ai-coding-rules-sampler" rel="noopener noreferrer"&gt;ai-coding-rules-sampler&lt;/a&gt;. It has a short CLAUDE.md/AGENTS.md, security rules and three skills (plan-feature, review-changes, record-decisions) for Claude Code, Cursor, Codex and Windsurf. MIT, free. (I made it.)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>codex</category>
      <category>cursor</category>
    </item>
    <item>
      <title>One MCP server, three configs: Claude Code, Cursor and Codex</title>
      <dc:creator>Eliran Kotzero</dc:creator>
      <pubDate>Wed, 04 Nov 2026 07:06:40 +0000</pubDate>
      <link>https://dev.to/eliran_kotzero_e1781dc487/one-mcp-server-three-configs-claude-code-cursor-and-codex-1em0</link>
      <guid>https://dev.to/eliran_kotzero_e1781dc487/one-mcp-server-three-configs-claude-code-cursor-and-codex-1em0</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://kitquarry.com/articles/mcp-config-claude-code-cursor-codex" rel="noopener noreferrer"&gt;KitQuarry&lt;/a&gt;. Written by Eliran Kotzero for KitQuarry.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; all three tools speak MCP, but each keeps its servers in a different file. Claude Code uses &lt;code&gt;.mcp.json&lt;/code&gt; at the repo root, Cursor uses &lt;code&gt;.cursor/mcp.json&lt;/code&gt;, and Codex uses &lt;code&gt;[mcp_servers.&amp;lt;name&amp;gt;]&lt;/code&gt; tables in &lt;code&gt;.codex/config.toml&lt;/code&gt;. The JSON files look alike, but two details break copy-paste: Claude Code needs &lt;code&gt;"type": "http"&lt;/code&gt; on remote servers, and every tool writes environment variables differently. Below is the same two-server setup written for each tool, the differences that matter, and how to check that each one loaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where each tool looks
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Claude Code&lt;/th&gt;
&lt;th&gt;Cursor&lt;/th&gt;
&lt;th&gt;Codex&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Project file (commit it)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.mcp.json&lt;/code&gt; at the project root&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.cursor/mcp.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.codex/config.toml&lt;/code&gt;, trusted projects only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Personal file&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;~/.claude.json&lt;/code&gt; (user scope, and local scope per project)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.cursor/mcp.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.codex/config.toml&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Format&lt;/td&gt;
&lt;td&gt;JSON, &lt;code&gt;mcpServers&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;JSON, &lt;code&gt;mcpServers&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;TOML, &lt;code&gt;[mcp_servers.&amp;lt;name&amp;gt;]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Env variable in a value&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;${VAR}&lt;/code&gt; or &lt;code&gt;${VAR:-default}&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;${env:VAR}&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Name the variable in a field such as &lt;code&gt;bearer_token_env_var&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;See what loaded&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;claude mcp list&lt;/code&gt;, &lt;code&gt;/mcp&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Customize, MCP Logs in the Output panel&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;codex mcp list&lt;/code&gt;, &lt;code&gt;/mcp&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sources: the &lt;a href="https://code.claude.com/docs/en/mcp#mcp-installation-scopes" rel="noopener noreferrer"&gt;Claude Code MCP docs&lt;/a&gt;, the &lt;a href="https://cursor.com/docs/mcp" rel="noopener noreferrer"&gt;Cursor MCP docs&lt;/a&gt; and the &lt;a href="https://learn.chatgpt.com/docs/extend/mcp" rel="noopener noreferrer"&gt;Codex MCP docs&lt;/a&gt;, all checked on 2026-10-29. Some third-party guides still say Codex has no project-level MCP config. The current Codex docs say it does: put the tables in &lt;code&gt;.codex/config.toml&lt;/code&gt; in the repo, and Codex loads them once you trust the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same setup, written three ways
&lt;/h2&gt;

&lt;p&gt;The example has two servers: Context7, a local stdio server that the Codex docs use as their example (&lt;code&gt;npx -y @upstash/context7-mcp&lt;/code&gt;), and &lt;code&gt;team-docs&lt;/code&gt;, a stand-in for a remote HTTP server that needs a bearer token. &lt;code&gt;mcp.example.com&lt;/code&gt; and &lt;code&gt;TEAM_DOCS_TOKEN&lt;/code&gt; are placeholders; swap in your server's URL and auth. The token value never appears in a file. Each developer sets &lt;code&gt;TEAM_DOCS_TOKEN&lt;/code&gt; in their own shell.&lt;/p&gt;

&lt;p&gt;We checked that all three files parse (JSON and TOML). We didn't run them inside the three tools, so test them in yours before you rely on them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Code: &lt;code&gt;.mcp.json&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"context7"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@upstash/context7-mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"team-docs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.example.com/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer ${TEAM_DOCS_TOKEN}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;An entry with a &lt;code&gt;url&lt;/code&gt; needs a &lt;code&gt;type&lt;/code&gt; (&lt;code&gt;"http"&lt;/code&gt;, &lt;code&gt;"sse"&lt;/code&gt; or &lt;code&gt;"ws"&lt;/code&gt;). Claude Code reads an entry without one as a stdio server, so it skips a &lt;code&gt;url&lt;/code&gt;-only entry and reports &lt;code&gt;has a "url" but no "type"&lt;/code&gt;. The docs accept &lt;code&gt;"streamable-http"&lt;/code&gt; as another name for &lt;code&gt;"http"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;${VAR}&lt;/code&gt; and &lt;code&gt;${VAR:-default}&lt;/code&gt; expand in &lt;code&gt;command&lt;/code&gt;, &lt;code&gt;args&lt;/code&gt;, &lt;code&gt;env&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt; and &lt;code&gt;headers&lt;/code&gt;. If a variable is unset and has no default, the config still loads, &lt;code&gt;claude mcp list&lt;/code&gt; shows a missing-variable warning, and the literal &lt;code&gt;${VAR}&lt;/code&gt; text is used.&lt;/li&gt;
&lt;li&gt;In a remote server's &lt;code&gt;url&lt;/code&gt; and &lt;code&gt;headers&lt;/code&gt;, some credential variables always read as empty, including &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt;, &lt;code&gt;ANTHROPIC_AUTH_TOKEN&lt;/code&gt; and &lt;code&gt;NPM_TOKEN&lt;/code&gt;. That stops a repo's &lt;code&gt;.mcp.json&lt;/code&gt; from sending your own credentials to a server it names. Use a variable name of your own, like &lt;code&gt;TEAM_DOCS_TOKEN&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Claude Code sets &lt;code&gt;CLAUDE_PROJECT_DIR&lt;/code&gt; inside a stdio server's environment. To use it in &lt;code&gt;command&lt;/code&gt; or &lt;code&gt;args&lt;/code&gt; of &lt;code&gt;.mcp.json&lt;/code&gt;, write &lt;code&gt;${CLAUDE_PROJECT_DIR:-.}&lt;/code&gt;, because the variable isn't set in Claude Code's own environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cursor: &lt;code&gt;.cursor/mcp.json&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"context7"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"stdio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@upstash/context7-mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"team-docs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://mcp.example.com/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer ${env:TEAM_DOCS_TOKEN}"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Variables use &lt;code&gt;${env:NAME}&lt;/code&gt;. Cursor also offers &lt;code&gt;${userHome}&lt;/code&gt;, &lt;code&gt;${workspaceFolder}&lt;/code&gt; (the folder that contains &lt;code&gt;.cursor/mcp.json&lt;/code&gt;), &lt;code&gt;${workspaceFolderBasename}&lt;/code&gt; and &lt;code&gt;${/}&lt;/code&gt;. They resolve in &lt;code&gt;command&lt;/code&gt;, &lt;code&gt;args&lt;/code&gt;, &lt;code&gt;env&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt; and &lt;code&gt;headers&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Remote servers are written as &lt;code&gt;url&lt;/code&gt; plus optional &lt;code&gt;headers&lt;/code&gt;, as in Cursor's own examples. Cursor's stdio field table lists &lt;code&gt;"type": "stdio"&lt;/code&gt; as required, so the example includes it.&lt;/li&gt;
&lt;li&gt;Stdio servers can load more variables from a file with &lt;code&gt;envFile&lt;/code&gt; (for example &lt;code&gt;"${workspaceFolder}/.env"&lt;/code&gt;). Remote servers can't use &lt;code&gt;envFile&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Codex: &lt;code&gt;.codex/config.toml&lt;/code&gt;
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# .codex/config.toml  (loaded only in a trusted project)&lt;/span&gt;
&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mcp_servers&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context7&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"npx"&lt;/span&gt;
&lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"@upstash/context7-mcp"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mcp_servers&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;team-docs&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"https://mcp.example.com/mcp"&lt;/span&gt;
&lt;span class="n"&gt;bearer_token_env_var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"TEAM_DOCS_TOKEN"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Codex doesn't use &lt;code&gt;${...}&lt;/code&gt; placeholders in these fields. It takes the variable's name instead: &lt;code&gt;bearer_token_env_var&lt;/code&gt; for an &lt;code&gt;Authorization&lt;/code&gt; bearer token, &lt;code&gt;env_http_headers&lt;/code&gt; for other headers read from the environment, and &lt;code&gt;env_vars&lt;/code&gt; to forward named variables to a stdio server. &lt;code&gt;env&lt;/code&gt; sets fixed values.&lt;/li&gt;
&lt;li&gt;Codex loads project &lt;code&gt;.codex/&lt;/code&gt; layers only when you trust the project. In an untrusted project it skips them, including project config, hooks and rules. With several &lt;code&gt;.codex/config.toml&lt;/code&gt; files, the one closest to your working directory wins (&lt;a href="https://learn.chatgpt.com/docs/config-file/config-basic" rel="noopener noreferrer"&gt;config basics&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;The ChatGPT desktop app, Codex CLI and IDE extension share this configuration, so setting it up once covers all three.&lt;/li&gt;
&lt;li&gt;Per-server controls that the other two don't put in this file: &lt;code&gt;enabled_tools&lt;/code&gt; and &lt;code&gt;disabled_tools&lt;/code&gt;, &lt;code&gt;default_tools_approval_mode&lt;/code&gt; (&lt;code&gt;auto&lt;/code&gt;, &lt;code&gt;prompt&lt;/code&gt;, &lt;code&gt;writes&lt;/code&gt; or &lt;code&gt;approve&lt;/code&gt;), &lt;code&gt;startup_timeout_sec&lt;/code&gt; (default 10) and &lt;code&gt;tool_timeout_sec&lt;/code&gt; (default 60).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Adding servers from the command line
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Claude Code: write the server into .mcp.json for the team&lt;/span&gt;
claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http &lt;span class="nt"&gt;--scope&lt;/span&gt; project team-docs https://mcp.example.com/mcp
claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; stdio &lt;span class="nt"&gt;--scope&lt;/span&gt; project context7 &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; @upstash/context7-mcp

&lt;span class="c"&gt;# Codex: add a stdio server to your Codex config&lt;/span&gt;
codex mcp add context7 &lt;span class="nt"&gt;--&lt;/span&gt; npx &lt;span class="nt"&gt;-y&lt;/span&gt; @upstash/context7-mcp

&lt;span class="c"&gt;# Check what loaded&lt;/span&gt;
claude mcp list
codex mcp list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;claude mcp add&lt;/code&gt;, everything after &lt;code&gt;--&lt;/code&gt; is the server's own command, so its flags aren't read as Claude's. Without &lt;code&gt;--scope&lt;/code&gt;, Claude Code uses local scope: the server goes into your entry for this project inside &lt;code&gt;~/.claude.json&lt;/code&gt;, not into &lt;code&gt;.mcp.json&lt;/code&gt;, and your teammates won't see it. For a remote server in Codex, the docs show &lt;code&gt;codex mcp add &amp;lt;name&amp;gt; --url &amp;lt;url&amp;gt;&lt;/code&gt;, and &lt;code&gt;codex mcp login &amp;lt;name&amp;gt;&lt;/code&gt; starts an OAuth sign-in. Cursor adds servers through Customize or by editing &lt;code&gt;mcp.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which definition wins
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code:&lt;/strong&gt; local scope beats project scope, which beats user scope. Duplicates are matched by name, and the whole entry from the winning scope is used, with no field-by-field merge. A personal local copy of &lt;code&gt;team-docs&lt;/code&gt; therefore hides the team's version completely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex:&lt;/strong&gt; CLI flags first, then project &lt;code&gt;.codex/config.toml&lt;/code&gt; files (closest first), then a selected profile, then &lt;code&gt;~/.codex/config.toml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor:&lt;/strong&gt; the docs list the project and global files but don't say which wins when both define the same server name. Give team servers names you won't reuse in your global file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Approvals and trust
&lt;/h2&gt;

&lt;p&gt;A committed MCP config runs commands on every machine that opens the repo, so each tool puts a gate in front of it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; asks you to approve servers from &lt;code&gt;.mcp.json&lt;/code&gt; in an interactive session. Until then, &lt;code&gt;claude mcp list&lt;/code&gt; shows them as pending approval. A repo can't approve its own servers: approval settings committed to &lt;code&gt;.claude/settings.json&lt;/code&gt; are ignored until you trust the folder. &lt;code&gt;claude mcp reset-project-choices&lt;/code&gt; resets your choices. In &lt;code&gt;claude -p&lt;/code&gt; runs and Agent SDK sessions there's no prompt, and Claude Code loads project servers without asking, so add a server to &lt;code&gt;disabledMcpjsonServers&lt;/code&gt; or use &lt;code&gt;--strict-mcp-config&lt;/code&gt; if you don't want it there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex&lt;/strong&gt; ignores &lt;code&gt;.codex/config.toml&lt;/code&gt; until you trust the project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cursor&lt;/strong&gt; asks for approval before it runs an MCP tool by default, and MCP follows the same run modes as terminal commands.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Read a server's source before you approve it, and give it a narrowly scoped token. If you're also keeping agents away from secrets files, our &lt;a href="https://kitquarry.com/articles/stop-ai-agents-reading-env-files" rel="noopener noreferrer"&gt;.env guide&lt;/a&gt; covers the file-access side for all three tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping three files in sync
&lt;/h2&gt;

&lt;p&gt;There's no shared MCP file that all three tools read, so you maintain three. A few habits keep that manageable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the same server names in every file, so a rule like "use &lt;code&gt;team-docs&lt;/code&gt; for internal API questions" in &lt;code&gt;AGENTS.md&lt;/code&gt; or &lt;code&gt;CLAUDE.md&lt;/code&gt; means the same thing in each tool.&lt;/li&gt;
&lt;li&gt;Commit references, never values. Keep a &lt;code&gt;.env.example&lt;/code&gt; or a short README table that lists the variables each developer needs.&lt;/li&gt;
&lt;li&gt;When you change a server, change all three files in the same commit. A short note in your instruction file helps, the same way we suggest pointing &lt;code&gt;CLAUDE.md&lt;/code&gt; at &lt;code&gt;AGENTS.md&lt;/code&gt; in &lt;a href="https://kitquarry.com/articles/agents-md-vs-claude-md-single-source-of-truth" rel="noopener noreferrer"&gt;one source of truth&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Third-party sync tools exist. If you use one, check its output against the formats above, especially &lt;code&gt;"type"&lt;/code&gt; on remote servers and the variable syntax.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Write &lt;code&gt;.mcp.json&lt;/code&gt;, &lt;code&gt;.cursor/mcp.json&lt;/code&gt; and &lt;code&gt;.codex/config.toml&lt;/code&gt; with the same server names.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;"type": "http"&lt;/code&gt; to every remote server in &lt;code&gt;.mcp.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Replace every secret with a variable: &lt;code&gt;${VAR}&lt;/code&gt;, &lt;code&gt;${env:VAR}&lt;/code&gt;, or a Codex &lt;code&gt;*_env_var&lt;/code&gt; field.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;claude&lt;/code&gt; in the repo and approve the project servers, then check &lt;code&gt;claude mcp list&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Trust the project in Codex, then check &lt;code&gt;codex mcp list&lt;/code&gt; or &lt;code&gt;/mcp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In Cursor, check the servers are on in Customize and look at MCP Logs in the Output panel if one fails.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Setting up instruction files too?&lt;/strong&gt; The &lt;a href="https://kitquarry.com/free" rel="noopener noreferrer"&gt;free sampler&lt;/a&gt; ships rules and skills in the formats Claude Code, Codex, Cursor, Windsurf and Copilot each expect. The &lt;a href="https://kitquarry.com/products/rules-pack" rel="noopener noreferrer"&gt;AI Coding Rules Pack&lt;/a&gt; adds the full rule set and workflow skills.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Where does each tool keep project MCP servers?
&lt;/h3&gt;

&lt;p&gt;Claude Code reads .mcp.json at the project root. Cursor reads .cursor/mcp.json in the project. Codex reads &lt;code&gt;[mcp_servers.&amp;lt;name&amp;gt;]&lt;/code&gt; tables in .codex/config.toml, but only in a project you've trusted. The personal versions are ~/.claude.json, ~/.cursor/mcp.json and ~/.codex/config.toml.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I copy Cursor's mcp.json into Claude Code's .mcp.json?
&lt;/h3&gt;

&lt;p&gt;Not as is. Both use an mcpServers object, but Claude Code treats an entry with no "type" as a stdio server, so a remote server with only a "url" is skipped with an error until you add "type": "http". Environment variables also differ: Cursor writes ${env:NAME}, Claude Code writes ${NAME} or ${NAME:-default}.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Codex support project-level MCP servers?
&lt;/h3&gt;

&lt;p&gt;Yes. Current Codex docs let you add &lt;code&gt;[mcp_servers.&amp;lt;name&amp;gt;]&lt;/code&gt; tables to .codex/config.toml in the repo. Codex loads project .codex/ layers only for trusted projects, and the config closest to your working directory wins. The ChatGPT desktop app, Codex CLI and IDE extension share this configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I keep API keys out of a committed MCP config?
&lt;/h3&gt;

&lt;p&gt;Commit only variable references. In Claude Code use ${TOKEN} in .mcp.json, in Cursor use ${env:TOKEN}, and in Codex name the variable with bearer_token_env_var, env_http_headers or env_vars instead of writing the value. Each developer sets the variable in their own shell. Codex stores an OAuth client secret in config.toml, so keep that kind of setup in your personal ~/.codex/config.toml.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does my project MCP server not show up in Claude Code?
&lt;/h3&gt;

&lt;p&gt;Claude Code asks you to approve project servers from .mcp.json in an interactive session. Until you do, claude mcp list shows the server as pending approval. Run claude in the folder and accept the trust dialog and the server prompt. To ask again later, run claude mcp reset-project-choices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;Official docs, checked 2026-10-29.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/mcp" rel="noopener noreferrer"&gt;Claude Code docs: Connect Claude Code to tools via MCP (scopes, .mcp.json, variable expansion)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cursor.com/docs/mcp" rel="noopener noreferrer"&gt;Cursor docs: Model Context Protocol (mcp.json, config interpolation)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.chatgpt.com/docs/extend/mcp" rel="noopener noreferrer"&gt;Codex docs (ChatGPT Learn): Model Context Protocol (config.toml, mcp_servers)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.chatgpt.com/docs/config-file/config-basic" rel="noopener noreferrer"&gt;Codex docs (ChatGPT Learn): Config basics (project config, trusted projects)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you want a starting point, KitQuarry's free sampler is on GitHub: &lt;a href="https://github.com/elirankotzero/ai-coding-rules-sampler" rel="noopener noreferrer"&gt;ai-coding-rules-sampler&lt;/a&gt;. It has a short CLAUDE.md/AGENTS.md, security rules and three skills (plan-feature, review-changes, record-decisions) for Claude Code, Cursor, Codex and Windsurf. MIT, free. (I made it.)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>claude</category>
      <category>cursor</category>
    </item>
    <item>
      <title>How to stop Claude Code, Cursor and Codex reading your .env file</title>
      <dc:creator>Eliran Kotzero</dc:creator>
      <pubDate>Sun, 01 Nov 2026 07:05:32 +0000</pubDate>
      <link>https://dev.to/eliran_kotzero_e1781dc487/how-to-stop-claude-code-cursor-and-codex-reading-your-env-file-3bi9</link>
      <guid>https://dev.to/eliran_kotzero_e1781dc487/how-to-stop-claude-code-cursor-and-codex-reading-your-env-file-3bi9</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://kitquarry.com/articles/stop-ai-agents-reading-env-files" rel="noopener noreferrer"&gt;KitQuarry&lt;/a&gt;. Written by Eliran Kotzero for KitQuarry.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; &lt;code&gt;.gitignore&lt;/code&gt; doesn't keep secrets away from a coding agent, and neither does a line in &lt;code&gt;CLAUDE.md&lt;/code&gt; or &lt;code&gt;AGENTS.md&lt;/code&gt;. Each tool has its own control. Claude Code uses &lt;code&gt;Read&lt;/code&gt; deny rules plus the sandbox, Cursor uses &lt;code&gt;.cursorignore&lt;/code&gt; plus a hook for terminal commands, and Codex uses a permission profile with deny globs. Each one leaves a gap, listed below. The real fix is not having production keys in the working tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "don't read .env" in your rules isn't enough
&lt;/h2&gt;

&lt;p&gt;A rule in &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt; or a Cursor rule is guidance. The model usually follows it, but nothing enforces it. The Claude Code &lt;a href="https://code.claude.com/docs/en/permissions" rel="noopener noreferrer"&gt;permissions docs&lt;/a&gt; say it directly: permission rules are enforced by Claude Code, not by the model, and instructions in your prompt or CLAUDE.md shape what Claude tries to do without changing what Claude Code allows. The same split exists in Cursor and Codex. Keep the rule (our &lt;a href="https://kitquarry.com/articles/ai-coding-agent-security-rules" rel="noopener noreferrer"&gt;security rules checklist&lt;/a&gt; covers what to write), and add the tool-level block below so a slip doesn't put a key into the conversation.&lt;/p&gt;

&lt;p&gt;Three things people try that don't work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.gitignore&lt;/code&gt;.&lt;/strong&gt; It keeps the file out of commits. The agent works on your disk, so it can still open the file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;.claudeignore&lt;/code&gt;.&lt;/strong&gt; Claude Code doesn't read it. Per the &lt;a href="https://code.claude.com/docs/en/permissions#read-and-edit" rel="noopener noreferrer"&gt;docs&lt;/a&gt;, the file has no effect, and its entries belong in &lt;code&gt;Read&lt;/code&gt; deny rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A made-up settings shape.&lt;/strong&gt; A &lt;code&gt;"read": {"deny": [...]}&lt;/code&gt; block under &lt;code&gt;permissions&lt;/code&gt; isn't the documented format. Deny rules are strings in &lt;code&gt;permissions.deny&lt;/code&gt;, in the form &lt;code&gt;Tool(specifier)&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Claude Code: Read deny rules, then the sandbox
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Deny the files
&lt;/h3&gt;

&lt;p&gt;Put this in the project's &lt;code&gt;.claude/settings.json&lt;/code&gt; and commit it, so everyone on the repo gets it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(.env)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(.env.*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(!.env.example)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(*.pem)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(*.key)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How these match, per the &lt;a href="https://code.claude.com/docs/en/permissions#how-path-patterns-match" rel="noopener noreferrer"&gt;path pattern docs&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Patterns use gitignore syntax. A bare filename matches at any depth, so &lt;code&gt;Read(.env)&lt;/code&gt; is the same as &lt;code&gt;Read(**/.env)&lt;/code&gt;: any &lt;code&gt;.env&lt;/code&gt; at or under the folder you started Claude in. It doesn't cover a &lt;code&gt;.env&lt;/code&gt; in a parent folder or another project.&lt;/li&gt;
&lt;li&gt;A rule starting with &lt;code&gt;!&lt;/code&gt; carves an exception out of the rules listed before it in the same file. Here, &lt;code&gt;.env.example&lt;/code&gt; stays readable so Claude can still see which variables exist.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;Read&lt;/code&gt; deny rule also blocks the Edit and Write tools on that path, including creating the file.&lt;/li&gt;
&lt;li&gt;Deny rules are checked first. An allow rule can't reopen a path a deny rule blocks, and a deny in any settings file wins.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To cover every project on your machine, put rules in &lt;code&gt;~/.claude/settings.json&lt;/code&gt; with a &lt;code&gt;//&lt;/code&gt; (filesystem root) or &lt;code&gt;~/&lt;/code&gt; (home) anchor. A single leading &lt;code&gt;/&lt;/code&gt; in user settings means &lt;code&gt;~/.claude/&lt;/code&gt;, not your project, so &lt;code&gt;Read(/secrets/**)&lt;/code&gt; there blocks &lt;code&gt;~/.claude/secrets&lt;/code&gt;. The docs give &lt;code&gt;Read(//**/.env)&lt;/code&gt; as the pattern for any &lt;code&gt;.env&lt;/code&gt; on the filesystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Know what deny rules don't cover
&lt;/h3&gt;

&lt;p&gt;The docs are explicit. &lt;code&gt;Read&lt;/code&gt; deny rules apply to Claude's built-in file tools (Read, Grep, Glob, best effort), &lt;code&gt;@file&lt;/code&gt; mentions, IDE selection context, the Bash file commands Claude Code recognizes (&lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;head&lt;/code&gt;, &lt;code&gt;tail&lt;/code&gt;, &lt;code&gt;sed&lt;/code&gt;, &lt;code&gt;tee&lt;/code&gt;) and redirect targets like &lt;code&gt;&amp;lt; .env&lt;/code&gt;. They don't apply to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a command that reads files without naming them, such as &lt;code&gt;grep -r API_KEY .&lt;/code&gt; run from the folder that holds &lt;code&gt;.env&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;a script that opens the file itself, like &lt;code&gt;python3 -c "print(open('.env').read())"&lt;/code&gt; or your app's dev server printing its config.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Turn on the sandbox for shell commands
&lt;/h3&gt;

&lt;p&gt;The sandbox is the OS-level layer for Bash commands and every process they start (Seatbelt on macOS, bubblewrap on Linux and WSL2; native Windows runs commands unsandboxed). It's off by default. Run &lt;code&gt;/sandbox&lt;/code&gt; in a session, or set &lt;code&gt;sandbox.enabled&lt;/code&gt; in settings. By default sandboxed commands can still read most of the machine, but the &lt;a href="https://code.claude.com/docs/en/sandboxing#permission-rules" rel="noopener noreferrer"&gt;sandbox docs&lt;/a&gt; say paths from your permission rules, including &lt;code&gt;Read&lt;/code&gt; and &lt;code&gt;Edit&lt;/code&gt; deny rules, are merged into the sandbox configuration. So with the sandbox on, the &lt;code&gt;.env&lt;/code&gt; rules above also apply to that Python one-liner.&lt;/p&gt;

&lt;p&gt;A user-level setup in &lt;code&gt;~/.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deny"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(//**/.env)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(//**/.env.*)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(~/.ssh/**)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="s2"&gt;"Read(~/.aws/**)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sandbox"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"allowUnsandboxedCommands"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"credentials"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"envVars"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OPENAI_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deny"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STRIPE_SECRET_KEY"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deny"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;allowUnsandboxedCommands: false&lt;/code&gt; turns off the escape hatch where Claude retries a failed command outside the sandbox. Without it, that retry prompts you in Manual mode, runs with no prompt in &lt;code&gt;bypassPermissions&lt;/code&gt; mode, and goes to the classifier in auto mode.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;credentials.envVars&lt;/code&gt; with &lt;code&gt;"mode": "deny"&lt;/code&gt; unsets those variables before each sandboxed command runs. There's no built-in credential list, so name the ones you have. To strip credentials from every subprocess (Bash, hooks, stdio MCP servers), set &lt;code&gt;CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1&lt;/code&gt; (&lt;a href="https://code.claude.com/docs/en/env-vars" rel="noopener noreferrer"&gt;env vars docs&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Hooks, MCP servers and status-line commands run outside the sandbox with your full access, so review what you install.&lt;/li&gt;
&lt;li&gt;On Linux and WSL2, the sandbox docs warn that a wildcard placed right after the filesystem root (their example: &lt;code&gt;/**/*.pem&lt;/code&gt;) blocks nothing at the sandbox level. The user-level &lt;code&gt;//**/.env&lt;/code&gt; rules still guard Claude's file tools, but rely on the project-level rules, which are anchored in the project, for the sandbox, and test them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check it: run &lt;code&gt;/permissions&lt;/code&gt; to see your deny rules and which file each came from. Then ask Claude to show you the contents of &lt;code&gt;.env&lt;/code&gt;, and to run a one-line script that prints it. Both should fail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cursor: .cursorignore, plus a hook for the terminal
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Ignore the files
&lt;/h3&gt;

&lt;p&gt;Per the &lt;a href="https://cursor.com/docs/reference/ignore-file" rel="noopener noreferrer"&gt;ignore file docs&lt;/a&gt;, files in &lt;code&gt;.cursorignore&lt;/code&gt; are blocked from Agent, Tab and @-mention references. It uses gitignore syntax, including &lt;code&gt;!&lt;/code&gt; to re-include a file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .cursorignore  (gitignore syntax)
.env
.env.*
!.env.example
*.pem
*.key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To cover every project, add the same patterns (&lt;code&gt;**/.env&lt;/code&gt;, &lt;code&gt;**/.env.*&lt;/code&gt;, &lt;code&gt;**/*.pem&lt;/code&gt;, &lt;code&gt;**/*.key&lt;/code&gt;) to the global ignore list in your user settings. The docs note it's empty by default.&lt;/p&gt;

&lt;p&gt;Don't skip this because the docs also list &lt;code&gt;.env*&lt;/code&gt; in Cursor's default ignore list. In a &lt;a href="https://forum.cursor.com/t/cursor-does-not-ignore-files-in-gitignore/154430" rel="noopener noreferrer"&gt;forum thread&lt;/a&gt; (March 2026), a Cursor team member explained that &lt;code&gt;.gitignore&lt;/code&gt; and the default ignore list applied to indexing, not to the agent's Read tool or Tab, and pointed to &lt;code&gt;.cursorignore&lt;/code&gt; as the control for agent access. An explicit entry works either way.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Cover the terminal with a hook
&lt;/h3&gt;

&lt;p&gt;The same docs say the terminal and MCP tools used by Agent can't honor &lt;code&gt;.cursorignore&lt;/code&gt;, so &lt;code&gt;cat .env&lt;/code&gt; in a shell command still works. The &lt;a href="https://cursor.com/docs/agent/security" rel="noopener noreferrer"&gt;agent security docs&lt;/a&gt; add that terminal commands need your approval by default. If you've allowlisted commands or use a run mode that approves them for you, add a &lt;a href="https://cursor.com/docs/hooks" rel="noopener noreferrer"&gt;&lt;code&gt;beforeShellExecution&lt;/code&gt; hook&lt;/a&gt;. Cursor runs it before each shell command and accepts &lt;code&gt;allow&lt;/code&gt;, &lt;code&gt;deny&lt;/code&gt; or &lt;code&gt;ask&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.cursor/hooks.json&lt;/code&gt; in the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"beforeShellExecution"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".cursor/hooks/block-secrets.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.env|&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.pem|&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;.key|id_rsa|id_ed25519"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"failClosed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;matcher&lt;/code&gt; is a regex tested against the full command string, so the hook only runs for commands that mention a secret-looking name. Every other command never reaches it and keeps its normal approval flow. &lt;code&gt;failClosed: true&lt;/code&gt; blocks the command if the script crashes or times out. Without it, Cursor lets the command through on those failures.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.cursor/hooks/block-secrets.py&lt;/code&gt; (run &lt;code&gt;chmod +x&lt;/code&gt; on it; project hooks run from the project root):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Cursor beforeShellExecution hook: deny shell commands that name a secrets file.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# .env, .env.local, config/.env.production, *.pem, *.key, id_rsa, id_ed25519
&lt;/span&gt;&lt;span class="n"&gt;SECRET&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(?&amp;lt;![\w.-])(\.env(\.[\w-]+)*|[\w.-]+\.(pem|key)|id_(rsa|ed25519))(?![\w.-])&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;SAFE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.env.example&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.env.sample&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.env.template&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SECRET&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finditer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SAFE&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;permission&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deny&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Blocked: command touches &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;That command reads or writes a secrets file, which is blocked. Use .env.example &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                         &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;for variable names and ask the user for anything else you need.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;permission&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ask&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;  &lt;span class="c1"&gt;# no secret named: leave the decision to Cursor's normal approval
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It denies the command when it names &lt;code&gt;.env&lt;/code&gt;, &lt;code&gt;.env.local&lt;/code&gt;, &lt;code&gt;config/.env.production&lt;/code&gt;, a &lt;code&gt;.pem&lt;/code&gt; or &lt;code&gt;.key&lt;/code&gt; file, or an SSH key. &lt;code&gt;.env.example&lt;/code&gt; and lookalikes such as &lt;code&gt;.envrc&lt;/code&gt; fall through to &lt;code&gt;ask&lt;/code&gt;, which hands the decision back to Cursor's normal approval. Writing to &lt;code&gt;.env&lt;/code&gt; (&lt;code&gt;cp .env.example .env&lt;/code&gt;) is denied too, so do that step yourself. We tested the script on 13 sample commands with Python 3, using the input format from the hooks reference, but not inside Cursor itself. Watch the Hooks output channel the first time it fires. On Windows, Cursor runs hook commands through PowerShell, so the shebang line won't work there. Change the command to call &lt;code&gt;python&lt;/code&gt; explicitly.&lt;/p&gt;

&lt;p&gt;It's a text match, so it has the same blind spot as Claude Code's deny rules: a script that opens &lt;code&gt;.env&lt;/code&gt; without naming it in the command isn't caught. Treat it as a guard against the common case, not a boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Codex: a permission profile with deny globs
&lt;/h2&gt;

&lt;p&gt;Codex enforces file access with its sandbox, and the &lt;a href="https://learn.chatgpt.com/docs/permissions" rel="noopener noreferrer"&gt;permission profile docs&lt;/a&gt; describe how to deny reads inside a writable workspace. Profiles are marked beta. Put this in &lt;code&gt;~/.codex/config.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/.codex/config.toml&lt;/span&gt;
&lt;span class="n"&gt;default_permissions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"no-secrets"&lt;/span&gt;

&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;shell_environment_policy&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;ignore_default_excludes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;   &lt;span class="c"&gt;# drop env vars whose names contain KEY, SECRET or TOKEN&lt;/span&gt;

&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;no-secrets&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Workspace writes, no .env or key files"&lt;/span&gt;
&lt;span class="n"&gt;extends&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;":workspace"&lt;/span&gt;

&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;no-secrets&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filesystem&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;glob_scan_max_depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="s"&gt;"~/.ssh"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"deny"&lt;/span&gt;

&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;no-secrets&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filesystem&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="s"&gt;":workspace_roots"&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="s"&gt;"**/.env"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"deny"&lt;/span&gt;
&lt;span class="s"&gt;"**/.env.*"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"deny"&lt;/span&gt;
&lt;span class="s"&gt;"**/*.pem"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"deny"&lt;/span&gt;
&lt;span class="s"&gt;"**/*.key"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"deny"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;extends = ":workspace"&lt;/code&gt; keeps the built-in workspace profile (writes in the workspace, with &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;.agents&lt;/code&gt;, &lt;code&gt;.codex&lt;/code&gt; and &lt;code&gt;.aws&lt;/code&gt; kept read-only) and adds your rules. &lt;code&gt;deny&lt;/code&gt; blocks reads and writes and wins over broader &lt;code&gt;read&lt;/code&gt; or &lt;code&gt;write&lt;/code&gt; entries.&lt;/li&gt;
&lt;li&gt;On Linux, WSL and native Windows, an unbounded &lt;code&gt;**&lt;/code&gt; deny glob needs &lt;code&gt;glob_scan_max_depth&lt;/code&gt;. Matches are listed when each sandboxed process starts, so a long-running process (a dev server, a watcher) can read a matching file created after it started.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.env.*&lt;/code&gt; also matches &lt;code&gt;.env.example&lt;/code&gt;. The docs say &lt;code&gt;read&lt;/code&gt; and &lt;code&gt;write&lt;/code&gt; globs are less portable than deny globs, so rather than try to reopen it, keep the example file under another name such as &lt;code&gt;env.example&lt;/code&gt; if Codex needs to see it.&lt;/li&gt;
&lt;li&gt;Profiles don't combine with the older settings. If &lt;code&gt;sandbox_mode&lt;/code&gt; appears in any loaded config, or you pass &lt;code&gt;--sandbox&lt;/code&gt;, Codex uses the old sandbox settings and ignores &lt;code&gt;default_permissions&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Profiles govern the local commands Codex runs in its sandbox. MCP servers, connectors, the browser and commands you approve to run outside the sandbox use their own controls, so don't approve an escalation that reads &lt;code&gt;.env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shell_environment_policy&lt;/code&gt; handles the other leak. Per the &lt;a href="https://learn.chatgpt.com/docs/config-file/config-advanced" rel="noopener noreferrer"&gt;advanced config docs&lt;/a&gt;, &lt;code&gt;ignore_default_excludes&lt;/code&gt; defaults to &lt;code&gt;true&lt;/code&gt;, which means Codex does &lt;em&gt;not&lt;/em&gt; drop variables named like &lt;code&gt;*KEY*&lt;/code&gt;, &lt;code&gt;*SECRET*&lt;/code&gt; or &lt;code&gt;*TOKEN*&lt;/code&gt; from the commands' environment unless you set it to &lt;code&gt;false&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check it: start a new session, ask Codex to print &lt;code&gt;.env&lt;/code&gt;, and confirm it can't show you the contents.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix that doesn't depend on any tool
&lt;/h2&gt;

&lt;p&gt;Every control above has a gap, and they all assume you remembered to set them up on this machine. Make the file less worth stealing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Keep production secrets off your laptop's working tree.&lt;/strong&gt; Use development-only keys locally, with low limits and no access to real customer data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load real secrets at run time&lt;/strong&gt; from your platform's secrets store or a secrets manager, not from a file in the repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commit a &lt;code&gt;.env.example&lt;/code&gt;&lt;/strong&gt; with every variable name and no values. The agent gets what it needs to write code without seeing a key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotate any key an agent has printed.&lt;/strong&gt; Once a value is in a transcript, treat it as exposed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch for copies.&lt;/strong&gt; Worktree setups often copy &lt;code&gt;.env&lt;/code&gt; into each new checkout. Commit &lt;code&gt;.claude/settings.json&lt;/code&gt;, &lt;code&gt;.cursorignore&lt;/code&gt; and &lt;code&gt;.cursor/hooks.json&lt;/code&gt; so every worktree gets them, and keep the Codex profile in &lt;code&gt;~/.codex/config.toml&lt;/code&gt;, where it applies to whatever workspace you open. See &lt;a href="https://kitquarry.com/articles/parallel-ai-agents-git-worktrees" rel="noopener noreferrer"&gt;parallel agents with git worktrees&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Five-minute checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Claude Code: &lt;code&gt;Read(.env)&lt;/code&gt;, &lt;code&gt;Read(.env.*)&lt;/code&gt; and &lt;code&gt;Read(!.env.example)&lt;/code&gt; in &lt;code&gt;.claude/settings.json&lt;/code&gt;; &lt;code&gt;/sandbox&lt;/code&gt; on; strict sandbox mode in user settings.&lt;/li&gt;
&lt;li&gt;Cursor: &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;.env.*&lt;/code&gt; in &lt;code&gt;.cursorignore&lt;/code&gt; and in the global ignore list; the &lt;code&gt;beforeShellExecution&lt;/code&gt; hook if any commands run without your approval.&lt;/li&gt;
&lt;li&gt;Codex: a &lt;code&gt;:workspace&lt;/code&gt;-based profile with &lt;code&gt;.env&lt;/code&gt; deny globs, selected with &lt;code&gt;default_permissions&lt;/code&gt;; &lt;code&gt;ignore_default_excludes = false&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Keep the "never read or print secrets" line in your always-on rules as the explanation, not the enforcement.&lt;/li&gt;
&lt;li&gt;Test each tool by asking it to show you &lt;code&gt;.env&lt;/code&gt;. Then rotate anything that ever leaked.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rules files tell the agent what you want, and settings make it stick. The same split applies to tests and formatting; see &lt;a href="https://kitquarry.com/articles/why-long-claude-md-gets-ignored" rel="noopener noreferrer"&gt;why a long CLAUDE.md gets ignored&lt;/a&gt; for a Stop hook that blocks "done" while tests fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want the rule side done?&lt;/strong&gt; The &lt;a href="https://kitquarry.com/free" rel="noopener noreferrer"&gt;free sampler&lt;/a&gt; (no email) includes our always-on security rule (no secrets in code, tests or logs; read them from env and validate at startup) in formats for Claude Code, Codex, Cursor, Windsurf and Copilot. The &lt;a href="https://kitquarry.com/products/rules-pack" rel="noopener noreferrer"&gt;AI Coding Rules Pack&lt;/a&gt; adds a &lt;code&gt;/security-audit&lt;/code&gt; skill that checks a diff against that checklist.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do I stop Claude Code from reading my .env file?
&lt;/h3&gt;

&lt;p&gt;Add Read deny rules to .claude/settings.json, for example Read(.env) and Read(.env.*), which match at any depth under the folder you start Claude in. A .claudeignore file has no effect. Deny rules cover Claude's file tools and the Bash commands Claude Code recognizes, such as cat and head, but not a script that opens the file itself, so also turn on the sandbox with /sandbox; Claude Code merges Read deny rules into the sandbox's filesystem rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Cursor read .env files by default?
&lt;/h3&gt;

&lt;p&gt;Don't rely on the default. The docs list .env* in Cursor's default ignore list, but a Cursor team member explained on the forum that the default list and .gitignore applied to indexing, not to the agent's file reads. Put .env and .env.* in .cursorignore or in the global ignore list. Neither stops the agent's terminal or MCP tools, so add a beforeShellExecution hook for commands that name the file.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I block .env files in Codex?
&lt;/h3&gt;

&lt;p&gt;Use a permission profile in ~/.codex/config.toml that extends :workspace and sets a deny glob such as "**/.env" = "deny" under :workspace_roots, then select it with default_permissions. Profiles are beta, don't combine with sandbox_mode, and only govern the local commands Codex runs in its sandbox.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is .gitignore enough to keep secrets away from an AI coding agent?
&lt;/h3&gt;

&lt;p&gt;No. .gitignore keeps a file out of git, not out of the agent's reach: the agent works on your disk and can open the file anyway. In Cursor, staff described .gitignore as an indexing control. Use each tool's own deny mechanism: Read deny rules in Claude Code, .cursorignore in Cursor, and deny globs in a Codex permission profile.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's the safest way to keep API keys away from coding agents?
&lt;/h3&gt;

&lt;p&gt;Don't have production keys in the working tree at all. Use development-only keys locally, load real secrets from a secrets manager at run time, commit a .env.example with names but no values, and rotate any key an agent has printed. The tool settings in this article reduce accidents; they aren't a vault.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;Official docs, checked 2026-10-27.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/permissions" rel="noopener noreferrer"&gt;Claude Code docs: Configure permissions (Read deny rules, path patterns)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/sandboxing" rel="noopener noreferrer"&gt;Claude Code docs: Configure the sandboxed Bash tool (filesystem rules, credentials)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/env-vars" rel="noopener noreferrer"&gt;Claude Code docs: Environment variables (CLAUDE_CODE_SUBPROCESS_ENV_SCRUB)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cursor.com/docs/reference/ignore-file" rel="noopener noreferrer"&gt;Cursor docs: Ignore file (.cursorignore, global ignore list)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cursor.com/docs/agent/security" rel="noopener noreferrer"&gt;Cursor docs: Agent security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cursor.com/docs/hooks" rel="noopener noreferrer"&gt;Cursor docs: Hooks (beforeShellExecution, failClosed)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.chatgpt.com/docs/permissions" rel="noopener noreferrer"&gt;Codex docs (ChatGPT Learn): Permissions (permission profiles, deny-read globs)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.chatgpt.com/docs/config-file/config-advanced" rel="noopener noreferrer"&gt;Codex docs (ChatGPT Learn): Advanced configuration (shell_environment_policy)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you want a starting point, KitQuarry's free sampler is on GitHub: &lt;a href="https://github.com/elirankotzero/ai-coding-rules-sampler" rel="noopener noreferrer"&gt;ai-coding-rules-sampler&lt;/a&gt;. It has a short CLAUDE.md/AGENTS.md, security rules and three skills (plan-feature, review-changes, record-decisions) for Claude Code, Cursor, Codex and Windsurf. MIT, free. (I made it.)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>claude</category>
      <category>cursor</category>
    </item>
    <item>
      <title>Stale AGENTS.md and CLAUDE.md: why agents follow them, how to catch it</title>
      <dc:creator>Eliran Kotzero</dc:creator>
      <pubDate>Wed, 28 Oct 2026 07:12:22 +0000</pubDate>
      <link>https://dev.to/eliran_kotzero_e1781dc487/stale-agentsmd-and-claudemd-why-agents-follow-them-how-to-catch-it-1mp</link>
      <guid>https://dev.to/eliran_kotzero_e1781dc487/stale-agentsmd-and-claudemd-why-agents-follow-them-how-to-catch-it-1mp</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://kitquarry.com/articles/stale-agents-md-claude-md" rel="noopener noreferrer"&gt;KitQuarry&lt;/a&gt;. Written by Eliran Kotzero for KitQuarry.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; when &lt;code&gt;AGENTS.md&lt;/code&gt; or &lt;code&gt;CLAUDE.md&lt;/code&gt; disagrees with the code, coding agents tend to believe the file. Add one precedence line to your user-level instructions ("when docs and code disagree, follow the existing code and tell me"), put a short check in CI that fails when a path, script or import in those files no longer exists, keep one source of truth with &lt;code&gt;@AGENTS.md&lt;/code&gt;, and delete lines the agent doesn't need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a stale instruction file is worse than none
&lt;/h2&gt;

&lt;p&gt;An instruction file is read at the start of every session, so every line in it carries weight. That's the point when the file is right. When it's wrong, the agent starts every task with a confident, outdated description of your project. The usual ways it goes stale are boring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Renamed paths.&lt;/strong&gt; "Handlers live in &lt;code&gt;src/handlers/&lt;/code&gt;" after you moved them to &lt;code&gt;src/api/&lt;/code&gt;. The agent creates new files in the old place, or recreates the folder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dead commands.&lt;/strong&gt; "Run &lt;code&gt;npm run typecheck&lt;/code&gt; before finishing" after the script was folded into &lt;code&gt;lint&lt;/code&gt;. The agent either fails the step or invents a replacement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Removed dependencies and old conventions.&lt;/strong&gt; "Use &lt;code&gt;moment&lt;/code&gt; for dates" or "use &lt;code&gt;print()&lt;/code&gt; for progress messages" after the codebase moved on. The agent adds the old library back, or rewrites working code to match the file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contradictions between files.&lt;/strong&gt; A root file says one thing and a nested one, or a &lt;code&gt;.claude/rules/&lt;/code&gt; file, says another. The Claude Code docs warn that Claude may then pick one arbitrarily.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the new research says
&lt;/h2&gt;

&lt;p&gt;This topic took off on X this week because of a paper posted to arXiv on 22 October 2026: &lt;a href="https://arxiv.org/abs/2610.30571" rel="noopener noreferrer"&gt;"Stale Instruction Files Override the Code in Coding Agents: A One-Sentence Fix"&lt;/a&gt; by Xiaonan Xu and Wenjing Wu. The authors built 40 small Python projects where the instruction file asked for one convention and the code used another (for example &lt;code&gt;print&lt;/code&gt; versus &lt;code&gt;logging&lt;/code&gt;), then had Claude Code and Codex CLI add a feature, 320 runs in total. Their reported results:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Without any fix, 90.1% of the agents' choices followed the stale file.&lt;/li&gt;
&lt;li&gt;Claude Code pointed out the conflict in 79 of 80 runs and still followed the file.&lt;/li&gt;
&lt;li&gt;Codex CLI rewrote existing code to match the file in 20 of 80 runs, and the project's tests then failed in 17 of them.&lt;/li&gt;
&lt;li&gt;One sentence in the user-level instruction file moved code-following from 4.2% to 91.4%, stopped the rewrites, and raised the test pass rate from 89.4% to 100%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Their explanation: the instruction file reaches the model as an instruction, while the code arrives as tool output, which models are trained to trust less. Keep the scope in mind. These are synthetic Python projects, two agents, and the authors' own runs. Still, the fix costs one line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 1: add a precedence line
&lt;/h2&gt;

&lt;p&gt;This is the sentence the paper used, word for word:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;When a repository's documentation and its existing code disagree, follow the conventions used in the existing code, and in your final reply point out each inconsistency you found between the documentation and the code.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The paper put it in the &lt;em&gt;user-level&lt;/em&gt; file, not the project one: &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt; for Claude Code and &lt;code&gt;~/.codex/AGENTS.md&lt;/code&gt; for Codex. That's a good default, because it works in every repo without anyone first finding the stale line. Two details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For Codex, the &lt;a href="https://learn.chatgpt.com/docs/agent-configuration/agents-md" rel="noopener noreferrer"&gt;docs&lt;/a&gt; say &lt;code&gt;~/.codex/AGENTS.override.md&lt;/code&gt; is used instead of &lt;code&gt;~/.codex/AGENTS.md&lt;/code&gt; when it exists, so put the line in whichever one you actually use.&lt;/li&gt;
&lt;li&gt;For a team, you can add a project version near the top of the shared &lt;code&gt;AGENTS.md&lt;/code&gt;, e.g. "The code and &lt;code&gt;package.json&lt;/code&gt; scripts are the source of truth; if this file disagrees with them, follow them and tell me". The paper only tested the user-level placement, so treat the project copy as a sensible extra, not a measured result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One caveat: "code wins" is wrong in the middle of a migration, when the file describes where you're going and the old code is what you're leaving. Say so explicitly: "We're moving from &lt;code&gt;moment&lt;/code&gt; to &lt;code&gt;date-fns&lt;/code&gt;. New code uses date-fns; don't rewrite old call sites unless asked."&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 2: fail CI when the file points at something that's gone
&lt;/h2&gt;

&lt;p&gt;The precedence line covers conventions. Paths and commands can be checked mechanically. This script scans every &lt;code&gt;AGENTS.md&lt;/code&gt; and &lt;code&gt;CLAUDE.md&lt;/code&gt; in the repo (or the files you pass it) and reports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;code-formatted paths like &lt;code&gt;`src/api/`&lt;/code&gt; or &lt;code&gt;`scripts/seed.ts`&lt;/code&gt; that don't exist, relative to the file or the repo root;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;@path&lt;/code&gt; imports outside code spans (Claude Code's import syntax) whose target is missing;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm run&lt;/code&gt; / &lt;code&gt;pnpm&lt;/code&gt; / &lt;code&gt;yarn&lt;/code&gt; / &lt;code&gt;bun run&lt;/code&gt; scripts missing from &lt;code&gt;package.json&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;make&lt;/code&gt; targets missing from the &lt;code&gt;Makefile&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Save it as &lt;code&gt;scripts/check_agent_docs.py&lt;/code&gt; and run it from the repo root. It needs only Python 3, no packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Fail when AGENTS.md / CLAUDE.md point at a path, @import, package.json script or make target that is gone.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;relative_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ROOT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AGENTS.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CLAUDE.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;node_modules&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.git&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ROOT&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;package.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ROOT&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Makefile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;scripts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scripts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="n"&gt;targets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^([\w.-]+):&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;M&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;ALLOW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.env.local&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;# fine to be missing: gitignored or created on demand (add PLAN.md etc.)
&lt;/span&gt;&lt;span class="n"&gt;BUILTIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;install&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;i&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ci&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;add&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;remove&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dlx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exec&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;create&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;init&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;update&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upgrade&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;audit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;EXT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\.(md|mdc|json|jsonc|ya?ml|toml|ini|cfg|env|lock|txt|sh|py|js|jsx|ts|tsx|mjs|cjs|go|rs|rb|java|kt|swift|gd|tscn|sql|css|html)$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;^(\.{1,2}/)?[\w.-]+(/[\w.-]+)*/?$&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;CMD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\b(?:npm run|npm|pnpm run|pnpm|yarn run|yarn|bun run)\s+([\w:.-]+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;problems&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ROOT&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;blocks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;```[^\n]*\n(.*?)```&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;prose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;```.*?```&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;S&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;spans&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;`([^`\n]+)`&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prose&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;                                    &lt;span class="c1"&gt;# `src/api/`, `scripts/seed.ts`, `.env.example`
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;PATH&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ALLOW&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: path `&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;` does not exist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;imp&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;(?&amp;lt;![\w`])@([\w./-]*\w)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;`[^`\n]+`&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prose&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
        &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;imp&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;imp&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;imp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isupper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;imp&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: import @&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;imp&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; does not exist&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;spans&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;blocks&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;splitlines&lt;/span&gt;&lt;span class="p"&gt;()]:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;CMD&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BUILTIN&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scripts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: script `&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;` is not in package.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bmake\s+([\w.-]+)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: make target `&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;` is not in the Makefile&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;problems&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OK: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; instruction file(s) checked&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;problems&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given this &lt;code&gt;AGENTS.md&lt;/code&gt;, plus a &lt;code&gt;CLAUDE.md&lt;/code&gt; that imports a file that was deleted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# AGENTS.md&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; API handlers live in &lt;span class="sb"&gt;`src/api/`&lt;/span&gt;. Old handlers were in &lt;span class="sb"&gt;`src/handlers/`&lt;/span&gt;.
&lt;span class="p"&gt;-&lt;/span&gt; Setup steps are in &lt;span class="sb"&gt;`docs/setup.md`&lt;/span&gt;.
&lt;span class="p"&gt;-&lt;/span&gt; Before you finish, run &lt;span class="sb"&gt;`npm run lint`&lt;/span&gt;, &lt;span class="sb"&gt;`npm test`&lt;/span&gt; and &lt;span class="sb"&gt;`npm run typecheck`&lt;/span&gt;.

&lt;span class="p"&gt;```&lt;/span&gt;&lt;span class="nl"&gt;bash
&lt;/span&gt;pnpm dev
make deploy
&lt;span class="p"&gt;```&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;it prints the following and exits with status 1. We tested it on exactly this sample repo with Python 3.13, then fixed each line and got &lt;code&gt;OK: 2 instruction file(s) checked&lt;/code&gt; and exit 0:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3 scripts/check_agent_docs.py
AGENTS.md: make target `deploy` is not in the Makefile
AGENTS.md: path `docs/setup.md` does not exist
AGENTS.md: path `src/handlers/` does not exist
AGENTS.md: script `typecheck` is not in package.json
CLAUDE.md: import @docs/claude-extra.md does not exist
$ echo $?
1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the &lt;code&gt;src/handlers/&lt;/code&gt; result: "Old handlers were in &lt;code&gt;src/handlers/&lt;/code&gt;" gets flagged too. That's deliberate. A history note about a path that's gone is just more stale text for the agent to act on; delete it. Paths that are allowed to be missing, like a gitignored &lt;code&gt;.env&lt;/code&gt;, go in the &lt;code&gt;ALLOW&lt;/code&gt; set at the top. If a line really must mention a missing path, write it without backticks. The script also flags a package name such as &lt;code&gt;@scope/pkg&lt;/code&gt; written outside backticks; that's correct, because Claude Code treats any &lt;code&gt;@path&lt;/code&gt; outside a code span as an import, in AGENTS.md files it reads too.&lt;/p&gt;

&lt;p&gt;Run it as a pre-commit hook:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'#!/bin/sh\nexec python3 scripts/check_agent_docs.py\n'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; .git/hooks/pre-commit
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x .git/hooks/pre-commit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or as a GitHub Actions job, so it also catches the commit that renames a folder without touching the docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;agent-docs&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v7&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python3 scripts/check_agent_docs.py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's deliberately simple. It won't catch a wrong convention ("use &lt;code&gt;print()&lt;/code&gt;"), a wrong flag, or a script that exists but now does something else. For those, use the precedence line and the review below.&lt;/p&gt;

&lt;p&gt;Claude Code also has a built-in review: &lt;code&gt;/doctor prompt-audit&lt;/code&gt; (v2.1.283 or later). Per the &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;memory docs&lt;/a&gt;, it looks for instructions written for older models, references to files or commands that don't exist, and files that contradict each other, across CLAUDE.md, CLAUDE.local.md, AGENTS.md and the rules, skills and subagents under &lt;code&gt;.claude/&lt;/code&gt;. It proposes edits and changes nothing until you say so. Use it for the judgment calls; keep the script for CI, where nothing should depend on a model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 3: one source of truth, so the copies can't drift
&lt;/h2&gt;

&lt;p&gt;Two copies of the same rules guarantee that one goes stale. Claude Code reads &lt;code&gt;CLAUDE.md&lt;/code&gt;, and Codex, Cursor, Windsurf and Copilot read &lt;code&gt;AGENTS.md&lt;/code&gt;. Recent Claude Code versions (v2.1.277+) can read &lt;code&gt;AGENTS.md&lt;/code&gt; directly, but by default only when there's no &lt;code&gt;CLAUDE.md&lt;/code&gt;. The docs' table is clear:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your repo has&lt;/th&gt;
&lt;th&gt;Claude Code reads&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;AGENTS.md&lt;/code&gt;, and no &lt;code&gt;CLAUDE.md&lt;/code&gt; or &lt;code&gt;CLAUDE.local.md&lt;/code&gt; in the working directory or above it&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;AGENTS.md&lt;/code&gt; and a &lt;code&gt;CLAUDE.md&lt;/code&gt; or &lt;code&gt;CLAUDE.local.md&lt;/code&gt; in the working directory or above it&lt;/td&gt;
&lt;td&gt;The &lt;code&gt;CLAUDE.md&lt;/code&gt; files only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A &lt;code&gt;CLAUDE.md&lt;/code&gt; that imports &lt;code&gt;AGENTS.md&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CLAUDE.md&lt;/code&gt;, with &lt;code&gt;AGENTS.md&lt;/code&gt; included through the import&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note "or above it". A &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;.claude/CLAUDE.md&lt;/code&gt; or &lt;code&gt;CLAUDE.local.md&lt;/code&gt; in any parent folder, say a forgotten one in &lt;code&gt;~/code/&lt;/code&gt;, counts, and Claude then stops reading your &lt;code&gt;AGENTS.md&lt;/code&gt; by default. Only &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;, a managed CLAUDE.md and &lt;code&gt;.claude/rules/&lt;/code&gt; files don't count. CLAUDE.md files above the working directory are also loaded at launch, so an old parent-folder file feeds its own stale rules into every session. If AGENTS.md seems ignored, look up the tree first, then check &lt;code&gt;/memory&lt;/code&gt; or &lt;code&gt;/context&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The simple fix works in every case: keep the rules in &lt;code&gt;AGENTS.md&lt;/code&gt; and make &lt;code&gt;CLAUDE.md&lt;/code&gt; a thin file that starts with the import, with Claude-only notes after it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;@AGENTS.md

&lt;span class="gu"&gt;## Claude Code&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Use plan mode for changes under &lt;span class="sb"&gt;`src/billing/`&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The docs confirm the import is never read twice, whatever the Project instructions setting. The full pattern is in &lt;a href="https://kitquarry.com/articles/agents-md-vs-claude-md-single-source-of-truth" rel="noopener noreferrer"&gt;AGENTS.md vs CLAUDE.md: one source of truth&lt;/a&gt;, and the Codex side (discovery order, nested files, the 32 KiB cap) is in &lt;a href="https://kitquarry.com/articles/agents-md-for-codex-template" rel="noopener noreferrer"&gt;AGENTS.md for Codex: a practical template&lt;/a&gt;. The checker above also flags the import if someone renames &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 4: prune, don't just add
&lt;/h2&gt;

&lt;p&gt;Instruction files only grow unless someone deletes lines. A short habit that keeps them honest:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Edit the file in the same commit as the change.&lt;/strong&gt; Renamed a folder, dropped a script, swapped a library? Update or delete the line now. The CI check makes forgetting fail loudly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delete rules the agent already follows.&lt;/strong&gt; Remove a line, run a typical task in a fresh session, and see if anything changes. If the agent does it anyway (it reads &lt;code&gt;package.json&lt;/code&gt;, it follows the existing style), the line was only costing attention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't describe what the code already says.&lt;/strong&gt; Directory trees, dependency lists and architecture overviews go stale fastest. The Claude Code docs note that &lt;code&gt;/doctor&lt;/code&gt; proposes cutting exactly that kind of content and keeping pitfalls, rationale and conventions that differ from tool defaults.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep history out.&lt;/strong&gt; "We used toâ€¦" lines belong in a decisions log or the git history, not the always-loaded file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-read it every few weeks&lt;/strong&gt;, or run &lt;code&gt;/doctor prompt-audit&lt;/code&gt;, and resolve contradictions between root, nested and rules files.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Shorter files also get followed better; the reasons, and how to see what's actually loaded, are in &lt;a href="https://kitquarry.com/articles/why-long-claude-md-gets-ignored" rel="noopener noreferrer"&gt;why a long CLAUDE.md gets ignored&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A ten-minute cleanup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Add the precedence sentence to &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt; and &lt;code&gt;~/.codex/AGENTS.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Look for stray &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;.claude/CLAUDE.md&lt;/code&gt; and &lt;code&gt;CLAUDE.local.md&lt;/code&gt; files above your repos.&lt;/li&gt;
&lt;li&gt;Make &lt;code&gt;AGENTS.md&lt;/code&gt; the single source and start &lt;code&gt;CLAUDE.md&lt;/code&gt; with &lt;code&gt;@AGENTS.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;scripts/check_agent_docs.py&lt;/code&gt;, run it, and fix or delete every line it reports.&lt;/li&gt;
&lt;li&gt;Wire it into pre-commit and CI.&lt;/li&gt;
&lt;li&gt;Delete three lines the agent doesn't need.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Want a lean starting point?&lt;/strong&gt; The &lt;a href="https://kitquarry.com/free" rel="noopener noreferrer"&gt;free sampler&lt;/a&gt; (no email) ships one AGENTS.md as the single source of truth, a thin CLAUDE.md that imports it, and a &lt;code&gt;/record-decisions&lt;/code&gt; skill that appends decisions to a log instead of growing the rules, in formats for Claude Code, Codex, Cursor, Windsurf and Copilot. The &lt;a href="https://kitquarry.com/products/rules-pack" rel="noopener noreferrer"&gt;AI Coding Rules Pack&lt;/a&gt; adds a version-pinning rule (detect versions from package.json and lockfiles before writing code), an agent-safe definition of done and more skills.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why does my coding agent follow an outdated AGENTS.md instead of the code?
&lt;/h3&gt;

&lt;p&gt;The instruction file reaches the model as an instruction, while the code only arrives as the output of a file-reading tool. A paper posted to arXiv in October 2026 argues that models are trained to rank instructions above tool output, so a stale line in the file can beat the code it used to describe, even when the agent notices the conflict.&lt;/p&gt;

&lt;h3&gt;
  
  
  What line should I add so the agent trusts the code over stale docs?
&lt;/h3&gt;

&lt;p&gt;Something like: when a repository's documentation and its existing code disagree, follow the conventions used in the existing code, and point out each inconsistency in your final reply. Put it in your user-level file (~/.claude/CLAUDE.md for Claude Code, ~/.codex/AGENTS.md for Codex) so it applies to every repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I check that the paths and commands in AGENTS.md still exist?
&lt;/h3&gt;

&lt;p&gt;Run a small script in CI or as a pre-commit hook. It scans AGENTS.md and CLAUDE.md for code-formatted paths, &lt;code&gt;@imports&lt;/code&gt;, package.json scripts and make targets, and fails when one no longer exists. The article includes a tested Python version of about 40 lines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does Claude Code read AGENTS.md if I also have a CLAUDE.md?
&lt;/h3&gt;

&lt;p&gt;Not by default. Claude Code reads AGENTS.md only when there is no CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md in the working directory or any folder above it. Your ~/.claude/CLAUDE.md doesn't count. If you keep a CLAUDE.md, start it with &lt;code&gt;@AGENTS.md&lt;/code&gt; so both tools read the same rules.&lt;/p&gt;

&lt;h3&gt;
  
  
  How often should I prune CLAUDE.md or AGENTS.md?
&lt;/h3&gt;

&lt;p&gt;Whenever you change the thing a line describes, and in a quick review every few weeks. Delete rules the agent already follows without being told, rules about code that no longer exists, and anything the agent can read from package.json or the code itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;Official docs, checked 2026-10-26.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;Claude Code docs: How Claude remembers your project (CLAUDE.md, AGENTS.md, imports, prompt audit)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.chatgpt.com/docs/agent-configuration/agents-md" rel="noopener noreferrer"&gt;Codex docs: Custom instructions with AGENTS.md (global and override files)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you want a starting point, KitQuarry's free sampler is on GitHub: &lt;a href="https://github.com/elirankotzero/ai-coding-rules-sampler" rel="noopener noreferrer"&gt;ai-coding-rules-sampler&lt;/a&gt;. It has a short CLAUDE.md/AGENTS.md, security rules and three skills (plan-feature, review-changes, record-decisions) for Claude Code, Cursor, Codex and Windsurf. MIT, free. (I made it.)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>codex</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Parallel agents with git worktrees in Claude Code, Codex and Cursor</title>
      <dc:creator>Eliran Kotzero</dc:creator>
      <pubDate>Sun, 25 Oct 2026 07:04:31 +0000</pubDate>
      <link>https://dev.to/eliran_kotzero_e1781dc487/parallel-agents-with-git-worktrees-in-claude-code-codex-and-cursor-1c95</link>
      <guid>https://dev.to/eliran_kotzero_e1781dc487/parallel-agents-with-git-worktrees-in-claude-code-codex-and-cursor-1c95</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://kitquarry.com/articles/parallel-ai-agents-git-worktrees" rel="noopener noreferrer"&gt;KitQuarry&lt;/a&gt;. Written by Eliran Kotzero for KitQuarry.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; two agents in one checkout overwrite each other's work. Give each agent its own git worktree: a separate folder on its own branch that shares one history. Claude Code, Codex and Cursor can all create worktrees for you. What they don't do is make a fresh checkout runnable or decide how the work fits back together, so set up &lt;code&gt;.env&lt;/code&gt; and dependencies per worktree, split tasks so they touch different files, and run the full test suite on the merged result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why parallel agents in one folder go wrong
&lt;/h2&gt;

&lt;p&gt;An agent that edits files, runs the build and reads the test output assumes nobody else is changing the folder. Run a second agent in the same checkout and each one sees the other's half-finished edits. Tests fail for reasons neither agent caused, and one agent "fixes" the other's work. Separate terminals or chat tabs don't help, because the files are still shared.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://git-scm.com/docs/git-worktree" rel="noopener noreferrer"&gt;git worktree&lt;/a&gt; fixes the file problem. In the &lt;a href="https://learn.chatgpt.com/docs/environments/git-worktrees" rel="noopener noreferrer"&gt;Codex docs'&lt;/a&gt; words, each worktree "has its own copy of every file in your repo but they all share the same metadata (&lt;code&gt;.git&lt;/code&gt; folder) about commits, branches, etc." Commits made in one worktree are visible from the others straight away, because they live in the same repository. The working files are not shared.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a worktree does and doesn't isolate
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Separate per worktree&lt;/th&gt;
&lt;th&gt;Still shared&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tracked files and uncommitted edits&lt;/td&gt;
&lt;td&gt;Commits, branches and remotes (one &lt;code&gt;.git&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The checked-out branch: git allows a branch in only one worktree at a time&lt;/td&gt;
&lt;td&gt;Anything outside the folder: ports, local databases, caches, services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build output and installed dependencies, once you install them there&lt;/td&gt;
&lt;td&gt;Nothing in the repo is copied that git ignores, so &lt;code&gt;.env&lt;/code&gt; and &lt;code&gt;node_modules&lt;/code&gt; start missing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things on the right trip people up in the first week:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ignored files.&lt;/strong&gt; A new worktree is a fresh checkout. The &lt;a href="https://code.claude.com/docs/en/worktrees" rel="noopener noreferrer"&gt;Claude Code docs&lt;/a&gt; say untracked files like &lt;code&gt;.env&lt;/code&gt; "are not present", and the &lt;a href="https://learn.chatgpt.com/docs/reference/troubleshooting" rel="noopener noreferrer"&gt;Codex troubleshooting page&lt;/a&gt; lists "code doesn't run on a worktree" with the same cause. Each tool has a fix, covered below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Things outside the folder.&lt;/strong&gt; Two dev servers still want the same port, and two agents running migrations still hit the same local database. The Codex docs suggest handing a chat back to your main checkout when you "can run only one instance of your app". Otherwise, give each worktree its own port or database in its environment file.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Claude Code: &lt;code&gt;claude --worktree&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;From the &lt;a href="https://code.claude.com/docs/en/worktrees" rel="noopener noreferrer"&gt;worktrees guide&lt;/a&gt;: pass &lt;code&gt;--worktree&lt;/code&gt; (or &lt;code&gt;-w&lt;/code&gt;) with a name, and Claude Code creates the worktree under &lt;code&gt;.claude/worktrees/&amp;lt;name&amp;gt;/&lt;/code&gt; on a new branch called &lt;code&gt;worktree-&amp;lt;name&amp;gt;&lt;/code&gt;. Run the same command with another name in a second terminal for a second isolated session. The repository needs at least one commit first (&lt;a href="https://code.claude.com/docs/en/common-workflows" rel="noopener noreferrer"&gt;common workflows&lt;/a&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# terminal 1&lt;/span&gt;
claude &lt;span class="nt"&gt;--worktree&lt;/span&gt; feature-auth

&lt;span class="c"&gt;# terminal 2&lt;/span&gt;
claude &lt;span class="nt"&gt;--worktree&lt;/span&gt; fix-billing-rounding

&lt;span class="c"&gt;# start from a pull request instead (quote the #)&lt;/span&gt;
claude &lt;span class="nt"&gt;--worktree&lt;/span&gt; &lt;span class="s2"&gt;"#1234"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set it up once per repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ignore the folder.&lt;/strong&gt; Add &lt;code&gt;.claude/worktrees/&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt; so the worktrees don't show up as untracked files in your main checkout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copy ignored files.&lt;/strong&gt; Add a &lt;code&gt;.worktreeinclude&lt;/code&gt; file at the repo root, in &lt;code&gt;.gitignore&lt;/code&gt; syntax. Claude Code copies files that match a pattern &lt;em&gt;and&lt;/em&gt; are gitignored into every worktree it creates, so tracked files are never duplicated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install dependencies.&lt;/strong&gt; They aren't copied. Ask Claude to install them, or run your setup yourself in the worktree directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick the base.&lt;/strong&gt; New worktrees branch from the remote's default branch, usually &lt;code&gt;main&lt;/code&gt;. To branch from your current local &lt;code&gt;HEAD&lt;/code&gt; (unpushed commits included), set &lt;code&gt;"worktree": {"baseRef": "head"}&lt;/code&gt; in settings. It doesn't accept a branch name; for a specific existing branch, create the worktree with git.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .worktreeinclude
.env
.env.local
config/secrets.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you exit, Claude checks the worktree. If it's clean, an unnamed session's worktree and branch are removed automatically, and a named session asks first. If it has changes or new commits, Claude asks whether to keep or remove it. Keep it and you can come back with the &lt;code&gt;claude --worktree &amp;lt;name&amp;gt; --resume&lt;/code&gt; command Claude Code prints on exit. Non-interactive &lt;code&gt;-p&lt;/code&gt; runs aren't cleaned up, so remove those with &lt;code&gt;git worktree remove&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Inside one session you can also isolate subagents. Ask Claude to "use worktrees for your agents", or add &lt;code&gt;isolation: worktree&lt;/code&gt; to a custom subagent's frontmatter in &lt;code&gt;.claude/agents/&lt;/code&gt;. Each subagent gets a temporary worktree, removed automatically if it finishes without changes. One detail from the docs: such a subagent takes its instruction files from the main conversation, not from its worktree, so a CLAUDE.md that differs on the worktree's branch doesn't apply to it.&lt;/p&gt;

&lt;p&gt;Some things carry across worktrees of the same repository: on most setups, "Yes, and don't ask again" Bash approvals are saved to the main checkout's &lt;code&gt;.claude/settings.local.json&lt;/code&gt;, and project-scope plugins load in every worktree. While a session is isolated, Claude Code blocks file edits and git commands aimed at the main checkout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Codex: Worktree mode in the ChatGPT desktop app
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://learn.chatgpt.com/docs/environments/git-worktrees" rel="noopener noreferrer"&gt;Codex worktrees docs&lt;/a&gt; describe this for Codex in the ChatGPT desktop app (and the mobile app controlling a connected computer):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In a new chat, select &lt;strong&gt;Worktree&lt;/strong&gt; under the composer, and optionally a local environment to run setup scripts.&lt;/li&gt;
&lt;li&gt;Choose the starting branch: &lt;code&gt;main&lt;/code&gt;, a feature branch, or your current branch with unstaged changes, which Codex applies to the worktree.&lt;/li&gt;
&lt;li&gt;Submit the prompt. Codex creates the worktree in &lt;code&gt;$CODEX_HOME/worktrees&lt;/code&gt; in a detached HEAD state, so several worktrees don't create a pile of branches.&lt;/li&gt;
&lt;li&gt;When it's done, either click &lt;strong&gt;Create branch here&lt;/strong&gt; to commit, push and open a pull request from the worktree, or &lt;strong&gt;Hand off&lt;/strong&gt; the chat to &lt;strong&gt;Local&lt;/strong&gt; to test it in your usual checkout.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Setup works in two parts. &lt;a href="https://learn.chatgpt.com/docs/environments/local-environment" rel="noopener noreferrer"&gt;Local environments&lt;/a&gt; hold setup scripts (for example &lt;code&gt;npm install&lt;/code&gt; and &lt;code&gt;npm run build&lt;/code&gt;) that run automatically when Codex creates a worktree. The configuration lives in the &lt;code&gt;.codex&lt;/code&gt; folder at the project root, so you can commit it for your team. Ignored files go in the same &lt;code&gt;.worktreeinclude&lt;/code&gt; file as above, and Codex copies an ignored &lt;code&gt;AGENTS.override.md&lt;/code&gt; into managed worktrees on its own. Both copy rules apply to worktrees the desktop app manages, not to ones you create yourself.&lt;/p&gt;

&lt;p&gt;By default Codex keeps your 15 most recent managed worktrees. It doesn't delete one tied to a pinned chat, a chat still in progress, or a permanent worktree, and it saves a snapshot before deleting one so you can restore it from the chat.&lt;/p&gt;

&lt;p&gt;Using the Codex CLI? The docs above cover the app. In the terminal, make the worktree with git (next section) and start Codex in it, for example &lt;code&gt;codex --cd ../app-auth&lt;/code&gt;. Your committed &lt;a href="https://learn.chatgpt.com/docs/agent-configuration/agents-md" rel="noopener noreferrer"&gt;AGENTS.md&lt;/a&gt; is part of every worktree, so Codex loads the same instructions there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cursor: &lt;code&gt;/worktree&lt;/code&gt;, &lt;code&gt;/best-of-n&lt;/code&gt; and the Agents Window
&lt;/h2&gt;

&lt;p&gt;Per &lt;a href="https://cursor.com/docs/configuration/worktrees" rel="noopener noreferrer"&gt;Cursor's worktrees docs&lt;/a&gt;, the UI for worktrees is in the Agents Window: start or move an agent into a worktree there, then review, commit or open a PR from that checkout, or bring the result back into your main workspace. In the IDE you use commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/worktree fix the failing auth tests&lt;/code&gt; runs the rest of that chat in a separate checkout.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/apply-worktree&lt;/code&gt; brings the changes into your main checkout, and &lt;code&gt;/delete-worktree&lt;/code&gt; removes the isolated one.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/best-of-n sonnet,gpt,composer fix the flaky logout test&lt;/code&gt; runs the same task on several models, each in its own worktree. It doesn't merge anything; you pick the winner and apply it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Setup goes in &lt;code&gt;.cursor/worktrees.json&lt;/code&gt;, which Cursor checks when it creates a worktree from the Agents Window, the IDE or the Cursor CLI. &lt;code&gt;setup-worktree&lt;/code&gt; takes an array of commands or a script path, with &lt;code&gt;setup-worktree-unix&lt;/code&gt; and &lt;code&gt;setup-worktree-windows&lt;/code&gt; as OS-specific overrides. &lt;code&gt;$ROOT_WORKTREE_PATH&lt;/code&gt; points at your main checkout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"setup-worktree"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"npm ci"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"cp $ROOT_WORKTREE_PATH/.env .env"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor advises against symlinking dependencies into the worktree and suggests a fast package manager such as pnpm, bun or uv instead. If setup fails, open the Output panel and select &lt;code&gt;Worktrees Setup&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Watch the cleanup. Cursor keeps 25 worktrees per machine by default (&lt;code&gt;cursor.worktreeMaxCount&lt;/code&gt;, checked every &lt;code&gt;cursor.worktreeCleanupIntervalHours&lt;/code&gt;), and the docs say worktrees created outside its manager, for example with &lt;code&gt;git worktree add&lt;/code&gt;, "are eligible for deletion". Commit or push anything you want to keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  Any agent: plain git
&lt;/h2&gt;

&lt;p&gt;For Codex CLI, Copilot, Windsurf or a mix of tools, create the worktrees yourself. Keeping them next to the repo rather than inside it means there's nothing to add to &lt;code&gt;.gitignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git worktree add ../app-auth &lt;span class="nt"&gt;-b&lt;/span&gt; agent/auth
git worktree add ../app-billing &lt;span class="nt"&gt;-b&lt;/span&gt; agent/billing
&lt;span class="nb"&gt;cp&lt;/span&gt; .env ../app-auth/ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cp&lt;/span&gt; .env ../app-billing/
git worktree list

&lt;span class="nb"&gt;cd&lt;/span&gt; ../app-auth &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; claude          &lt;span class="c"&gt;# terminal 1&lt;/span&gt;
&lt;span class="nb"&gt;cd&lt;/span&gt; ../app-billing &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; codex        &lt;span class="c"&gt;# terminal 2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install dependencies in each folder before you trust any test result there. Two errors you'll meet, both from git itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;fatal: 'agent/auth' is already used by worktree at 'â€¦/app-auth'&lt;/code&gt; when you try to check out a branch that another worktree has. Merge it from main instead (&lt;code&gt;git merge agent/auth&lt;/code&gt; doesn't need it checked out), or switch that worktree to another branch first.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git worktree remove&lt;/code&gt; refuses a worktree with modified or untracked files. Commit what you need, then add &lt;code&gt;--force&lt;/code&gt;. Removing the folder keeps its branch; delete that separately with &lt;code&gt;git branch -d&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Split the work so it merges
&lt;/h2&gt;

&lt;p&gt;Worktrees stop agents from overwriting each other's files. They don't stop two agents from making plans that can't both be true. Git merges text, so a rename on one branch and a new call to the old name on another can merge cleanly and then fail to build. A few habits help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Give each agent a different area.&lt;/strong&gt; "Auth middleware" and "billing rounding" merge well. "Refactor the API" and "add an endpoint" don't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Settle shared contracts first.&lt;/strong&gt; If two tasks depend on the same type, schema or route, land that change on &lt;code&gt;main&lt;/code&gt; before starting them, and base both worktrees on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Avoid one shared file that everyone appends to.&lt;/strong&gt; Translation catalogs, route tables and changelogs are where parallel runs lose entries. Have each agent write its own fragment, then merge them in one step that fails on duplicate or missing keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One integrator.&lt;/strong&gt; You, or one designated session, merge the branches and resolve conflicts. Then run the full suite on the merged result, not just on each branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Same rules everywhere.&lt;/strong&gt; Every worktree reads the committed instruction files, so put the commands and a "done means" section in &lt;a href="https://kitquarry.com/articles/agents-md-for-codex-template" rel="noopener noreferrer"&gt;AGENTS.md&lt;/a&gt;, and import it from CLAUDE.md as described in &lt;a href="https://kitquarry.com/articles/agents-md-vs-claude-md-single-source-of-truth" rel="noopener noreferrer"&gt;one source of truth&lt;/a&gt;. Then a Claude Code worktree and a Codex worktree check their work the same way.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each parallel task is still a normal session. A short plan file per worktree keeps it on track; see the &lt;a href="https://kitquarry.com/articles/plan-first-workflow-for-ai-coding" rel="noopener noreferrer"&gt;plan-first workflow&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Where worktrees go&lt;/th&gt;
&lt;th&gt;Automatic cleanup&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.claude/worktrees/&amp;lt;name&amp;gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Clean unnamed sessions removed on exit; otherwise it asks. Subagent and background worktrees swept after &lt;code&gt;cleanupPeriodDays&lt;/code&gt; unless they hold work&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex (desktop app)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$CODEX_HOME/worktrees&lt;/code&gt; (change in Settings &amp;gt; Worktrees)&lt;/td&gt;
&lt;td&gt;Keeps the 15 most recent managed worktrees by default; snapshot before deletion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;Cursor's worktree root&lt;/td&gt;
&lt;td&gt;Keeps 25 per machine by default; the docs say worktrees made outside its manager can be deleted too&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Plain git&lt;/td&gt;
&lt;td&gt;Wherever you put them&lt;/td&gt;
&lt;td&gt;None. &lt;code&gt;git worktree remove&lt;/code&gt;, then &lt;code&gt;git worktree prune&lt;/code&gt; for folders you deleted by hand&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Make sure the repo has at least one commit and your instruction files are committed.&lt;/li&gt;
&lt;li&gt;One task, one worktree, one branch per agent.&lt;/li&gt;
&lt;li&gt;List ignored files in &lt;code&gt;.worktreeinclude&lt;/code&gt; (Claude Code, Codex app) or copy them in &lt;code&gt;.cursor/worktrees.json&lt;/code&gt; (Cursor).&lt;/li&gt;
&lt;li&gt;Install dependencies in each worktree; give each dev server its own port.&lt;/li&gt;
&lt;li&gt;Split tasks by area, and land shared contracts on &lt;code&gt;main&lt;/code&gt; first.&lt;/li&gt;
&lt;li&gt;Merge through one integrator, then run the full test suite on the result.&lt;/li&gt;
&lt;li&gt;Remove finished worktrees and branches; know your tool's automatic cleanup limit.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Want every worktree to start with the same rules?&lt;/strong&gt; The &lt;a href="https://kitquarry.com/free" rel="noopener noreferrer"&gt;free sampler&lt;/a&gt; (no email) ships a shared AGENTS.md with a thin CLAUDE.md that imports it, plus &lt;code&gt;/plan-feature&lt;/code&gt; and &lt;code&gt;/review-changes&lt;/code&gt; for Claude Code, Codex, Cursor, Windsurf and Copilot. The &lt;a href="https://kitquarry.com/products/rules-pack" rel="noopener noreferrer"&gt;AI Coding Rules Pack&lt;/a&gt; adds an agent-safe definition of done and local AGENTS.md examples for risky folders.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Can two AI agents work in the same folder at the same time?
&lt;/h3&gt;

&lt;p&gt;They can, but they edit the same files, so one agent's half-finished change lands in the other's build and tests. Give each agent its own git worktree: a separate checkout on its own branch that shares the repository's history. Claude Code (claude --worktree), Codex in the ChatGPT desktop app (Worktree mode) and Cursor (/worktree or the Agents Window) can each create one for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is my .env missing in the worktree?
&lt;/h3&gt;

&lt;p&gt;A worktree is a fresh checkout, so it only contains tracked files. Gitignored files such as .env or node_modules aren't there. Claude Code and the Codex app copy ignored files listed in a .worktreeinclude file at the repo root; Cursor runs the setup commands in .cursor/worktrees.json, for example cp $ROOT_WORKTREE_PATH/.env .env. Install dependencies in each worktree too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does git say a branch is already used by another worktree?
&lt;/h3&gt;

&lt;p&gt;Git lets a branch be checked out in only one worktree at a time. Check out a different branch in one of them, merge the branch from your main checkout without checking it out, or in Codex use Hand off to move the chat to Local instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does each worktree load my CLAUDE.md or AGENTS.md?
&lt;/h3&gt;

&lt;p&gt;Yes, if the file is committed, because every worktree has its own copy of the tracked files. Edits you make in one worktree don't reach the others until you commit and merge them. One exception: a Claude Code subagent running in its own worktree takes its instruction files from the main conversation. Codex copies an ignored AGENTS.override.md into the worktrees it manages.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I clean up old worktrees?
&lt;/h3&gt;

&lt;p&gt;Claude Code offers to remove a worktree when you exit the session and removes clean unnamed ones automatically. The Codex app keeps your 15 most recent managed worktrees by default, and Cursor keeps 25 per machine by default. For worktrees you made yourself, run &lt;code&gt;git worktree list&lt;/code&gt; and &lt;code&gt;git worktree remove &amp;lt;path&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;Official docs, checked 2026-10-22.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/worktrees" rel="noopener noreferrer"&gt;Claude Code docs: Run parallel sessions with worktrees&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/common-workflows" rel="noopener noreferrer"&gt;Claude Code docs: Common workflows (resume, parallel sessions)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.chatgpt.com/docs/environments/git-worktrees" rel="noopener noreferrer"&gt;Codex docs (ChatGPT Learn): Worktrees&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.chatgpt.com/docs/environments/local-environment" rel="noopener noreferrer"&gt;Codex docs (ChatGPT Learn): Local environments (setup scripts)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.chatgpt.com/docs/reference/troubleshooting" rel="noopener noreferrer"&gt;Codex docs (ChatGPT Learn): Troubleshooting (code doesn't run on a worktree)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.chatgpt.com/docs/agent-configuration/agents-md" rel="noopener noreferrer"&gt;Codex docs (ChatGPT Learn): Custom instructions with AGENTS.md&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cursor.com/docs/configuration/worktrees" rel="noopener noreferrer"&gt;Cursor docs: Worktrees&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/docs/git-worktree" rel="noopener noreferrer"&gt;Git documentation: git-worktree&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you want a starting point, KitQuarry's free sampler is on GitHub: &lt;a href="https://github.com/elirankotzero/ai-coding-rules-sampler" rel="noopener noreferrer"&gt;ai-coding-rules-sampler&lt;/a&gt;. It has a short CLAUDE.md/AGENTS.md, security rules and three skills (plan-feature, review-changes, record-decisions) for Claude Code, Cursor, Codex and Windsurf. MIT, free. (I made it.)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why your long CLAUDE.md gets ignored, and how to fix it</title>
      <dc:creator>Eliran Kotzero</dc:creator>
      <pubDate>Wed, 21 Oct 2026 17:14:34 +0000</pubDate>
      <link>https://dev.to/eliran_kotzero_e1781dc487/why-your-long-claudemd-gets-ignored-and-how-to-fix-it-3hog</link>
      <guid>https://dev.to/eliran_kotzero_e1781dc487/why-your-long-claudemd-gets-ignored-and-how-to-fix-it-3hog</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://kitquarry.com/articles/why-long-claude-md-gets-ignored" rel="noopener noreferrer"&gt;KitQuarry&lt;/a&gt;. Written by Eliran Kotzero for KitQuarry.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; Claude Code treats &lt;code&gt;CLAUDE.md&lt;/code&gt; as context, not enforced configuration. A long file costs context on every session, buries the rules that matter, and tends to hide contradictions. Keep only the facts every session needs (under 200 lines, per Anthropic's docs). Move procedures into skills and file-specific rules into &lt;code&gt;.claude/rules/&lt;/code&gt;. Use &lt;code&gt;@imports&lt;/code&gt; to organize, knowing they don't save tokens. Then turn "run the tests before you say you're done" into a check Claude can run, and enforce it with a Stop hook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a long CLAUDE.md stops working
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;Claude Code memory docs&lt;/a&gt; are explicit: CLAUDE.md content is delivered as a user message after the system prompt, not as part of the system prompt itself. Claude reads it and tries to follow it, "but there's no guarantee of strict compliance, especially for vague or conflicting instructions." Four things make that worse as the file grows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Length.&lt;/strong&gt; The docs say to target under 200 lines per CLAUDE.md file: "Longer files consume more context and reduce adherence." Anthropic's &lt;a href="https://code.claude.com/docs/en/best-practices" rel="noopener noreferrer"&gt;best practices&lt;/a&gt; are blunter: "Bloated CLAUDE.md files cause Claude to ignore your actual instructions!"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lost rules.&lt;/strong&gt; Also from the best practices: if Claude keeps doing something you have a rule against, "the file is probably too long and the rule is getting lost."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contradictions.&lt;/strong&gt; Claude Code concatenates every CLAUDE.md it finds, plus your rules files, instead of letting one override another. If two instructions disagree, the docs say Claude "may pick one arbitrarily". Long files hide those conflicts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vague lines and emphasis inflation.&lt;/strong&gt; "Test your changes" can't be checked. "Run &lt;code&gt;npm test&lt;/code&gt; before committing" can. And if you mark many lines IMPORTANT, none of them stands out. The docs suggest emphasis on one line only.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  First, check what actually loaded
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/context&lt;/code&gt;&lt;/strong&gt; lists the CLAUDE.md and rules files loaded at launch under Memory files. If a file isn't there, Claude can't see it. CLAUDE.md files in subdirectories don't show up there, because they load on demand when Claude reads or edits a file in that folder.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Length warnings.&lt;/strong&gt; When a file is over the recommended length, Claude Code warns at startup and in &lt;code&gt;/status&lt;/code&gt;. It also warns when several files that are each fine add up past a combined limit. Each CLAUDE.md, rules file and &lt;code&gt;@path&lt;/code&gt; import counts as a separate file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/doctor&lt;/code&gt;&lt;/strong&gt; proposes trims for a checked-in CLAUDE.md. It cuts what Claude can derive from the codebase (directory layouts, dependency lists, architecture overviews) and keeps pitfalls, rationale and non-default conventions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/doctor prompt-audit&lt;/code&gt;&lt;/strong&gt; (Claude Code v2.1.283 or later) looks for outdated or conflicting instructions across your CLAUDE.md, AGENTS.md, rules and skills. It only reports; nothing changes until you ask Claude to apply the edits.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fix 1: keep the always-on part short
&lt;/h2&gt;

&lt;p&gt;The docs' rule for CLAUDE.md: "Keep it to facts Claude should hold in every session: build commands, conventions, project layout, 'always do X' rules." The test for every line is Anthropic's: &lt;em&gt;"Would removing this cause Claude to make mistakes?"&lt;/em&gt; If not, cut it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Keep in CLAUDE.md&lt;/th&gt;
&lt;th&gt;Cut or move&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Commands Claude can't guess (test one file, typecheck, lint)&lt;/td&gt;
&lt;td&gt;Anything Claude can learn by reading the code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conventions that differ from the defaults&lt;/td&gt;
&lt;td&gt;Standard language conventions, "write clean code"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Testing instructions and the preferred test runner&lt;/td&gt;
&lt;td&gt;Detailed API docs (link to them instead)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment quirks and gotchas&lt;/td&gt;
&lt;td&gt;File-by-file descriptions, long tutorials, anything that changes often&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is a complete project CLAUDE.md in that spirit, about 25 lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# acme-api: Node 22, Fastify, Postgres&lt;/span&gt;

&lt;span class="gu"&gt;## Commands&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Typecheck: &lt;span class="sb"&gt;`npm run typecheck`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; One test file: &lt;span class="sb"&gt;`npx vitest run path/to/file.test.ts`&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Full suite: &lt;span class="sb"&gt;`npm test`&lt;/span&gt; (needs &lt;span class="sb"&gt;`docker compose up -d db`&lt;/span&gt; first)
&lt;span class="p"&gt;-&lt;/span&gt; Lint and fix: &lt;span class="sb"&gt;`npm run lint -- --fix`&lt;/span&gt;

&lt;span class="gu"&gt;## Conventions that differ from defaults&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Money is integer cents (&lt;span class="sb"&gt;`amountCents`&lt;/span&gt;), never floats
&lt;span class="p"&gt;-&lt;/span&gt; Database access only through &lt;span class="sb"&gt;`src/db/repo/*`&lt;/span&gt;, no raw SQL in route handlers
&lt;span class="p"&gt;-&lt;/span&gt; New env vars must be added to the schema in &lt;span class="sb"&gt;`src/env.ts`&lt;/span&gt;

&lt;span class="gu"&gt;## Definition of done&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Run &lt;span class="sb"&gt;`npm run typecheck`&lt;/span&gt; and the tests for every file you changed,
  then &lt;span class="sb"&gt;`npm test`&lt;/span&gt;, before you say a task is done
&lt;span class="p"&gt;-&lt;/span&gt; Report the exact commands you ran and their results.
  If you didn't run something, say so
&lt;span class="p"&gt;-&lt;/span&gt; Never delete, skip or loosen a test or assertion to get a pass

&lt;span class="gu"&gt;## Workflows (skills, loaded on demand)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; /release, /db-migration, /fix-issue

&lt;span class="c"&gt;&amp;lt;!-- Maintainers: release steps live in .claude/skills/release/SKILL.md --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line is free. Claude Code strips block-level HTML comments from CLAUDE.md before injecting it, so you can leave notes for humans without spending context on them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 2: move procedures into skills
&lt;/h2&gt;

&lt;p&gt;The memory docs say it directly: "If an entry is a multi-step procedure or only matters for one part of the codebase, move it to a skill or a path-scoped rule instead." The &lt;a href="https://code.claude.com/docs/en/skills" rel="noopener noreferrer"&gt;skills docs&lt;/a&gt; explain why it pays off: "Unlike CLAUDE.md content, a skill's body loads only when it's used, so long reference material costs almost nothing until you need it."&lt;/p&gt;

&lt;p&gt;A typical offender is a 20-line "How to release" section that every session pays for. As a skill it looks like this. Save it as &lt;code&gt;.claude/skills/release/SKILL.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;release&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Cuts a new release. Bumps the version, updates CHANGELOG.md, tags and pushes. Use only when the user asks to release or publish a version.&lt;/span&gt;
&lt;span class="na"&gt;disable-model-invocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# Release&lt;/span&gt;

&lt;span class="na"&gt;1. Check the working tree is clean&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="s"&gt;git status --porcelain` must print nothing.&lt;/span&gt;
&lt;span class="s"&gt;2. Run `npm run typecheck` and `npm test`. If anything fails, stop and report.&lt;/span&gt;
&lt;span class="s"&gt;3. Ask which bump (patch, minor or major) if the user didn't say.&lt;/span&gt;
&lt;span class="s"&gt;4. Move the "Unreleased" notes in CHANGELOG.md under the new version heading and commit.&lt;/span&gt;
&lt;span class="s"&gt;5. Run `npm version &amp;lt;bump&amp;gt;`, then `git push --follow-tags`.&lt;/span&gt;
&lt;span class="s"&gt;6. Report the new version and every command you ran.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details matter here. &lt;code&gt;disable-model-invocation: true&lt;/code&gt; means only you can start it with &lt;code&gt;/release&lt;/code&gt;, which is what you want for anything with side effects, and Claude Code then keeps even the description out of context until you call it. Without that field, the description sits in the skill listing every session and the body loads when the skill is used. Also keep skill bodies concise: once a skill loads, its content stays in the conversation across later turns. For writing descriptions that make a skill fire when it should, see &lt;a href="https://kitquarry.com/articles/claude-code-skill-md-that-triggers" rel="noopener noreferrer"&gt;how to write a SKILL.md that actually triggers&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Rules that only matter for some files belong in &lt;code&gt;.claude/rules/&lt;/code&gt; with &lt;code&gt;paths&lt;/code&gt; frontmatter. They load when Claude reads or edits a matching file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;src/api/**/*.ts"&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="c1"&gt;# API handlers&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Validate every request body with the schema in `src/api/schemas/`&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Return errors with `sendError()` from `src/api/errors.ts`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A rules file without &lt;code&gt;paths&lt;/code&gt; loads at launch like CLAUDE.md, so splitting into rules only saves context when you scope them.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If the line isâ€¦&lt;/th&gt;
&lt;th&gt;Put it in&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Needed in every session&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Only relevant to some files or folders&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.claude/rules/*.md&lt;/code&gt; with &lt;code&gt;paths&lt;/code&gt;, or a CLAUDE.md in that folder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A multi-step procedure or reference you need sometimes&lt;/td&gt;
&lt;td&gt;A skill in &lt;code&gt;.claude/skills/&amp;lt;name&amp;gt;/SKILL.md&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Something that must happen every time, no exceptions&lt;/td&gt;
&lt;td&gt;A hook&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Your personal preference for this repo&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CLAUDE.local.md&lt;/code&gt; (add it to &lt;code&gt;.gitignore&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Fix 3: use @imports to organize, not to save tokens
&lt;/h2&gt;

&lt;p&gt;CLAUDE.md can pull in other files with &lt;code&gt;@path/to/file&lt;/code&gt;. What the docs say about it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Relative paths resolve from the file that contains the import, not the working directory. Absolute paths and &lt;code&gt;~/&lt;/code&gt; paths work too.&lt;/li&gt;
&lt;li&gt;Imported files can import others, up to four hops deep.&lt;/li&gt;
&lt;li&gt;Imports are expanded and loaded at launch, so they &lt;strong&gt;don't reduce context&lt;/strong&gt;. In the docs' words, imports "help you organize a long file but don't reduce its context cost."&lt;/li&gt;
&lt;li&gt;An &lt;code&gt;@path&lt;/code&gt; inside backticks or a code block stays literal text, so you can mention a file without importing it.&lt;/li&gt;
&lt;li&gt;The first time a project imports a file from outside your working directory, Claude Code asks you to approve it.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# CLAUDE.md&lt;/span&gt;
@AGENTS.md

&lt;span class="gu"&gt;## Claude Code only&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Workflows are skills in .claude/skills: /release, /db-migration
&lt;span class="p"&gt;-&lt;/span&gt; My personal notes: @~/.claude/acme-api-notes.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@AGENTS.md&lt;/code&gt; line is the useful one. It keeps one rule file shared with Codex, Cursor and other tools, with Claude-only notes underneath. By default, Claude Code (v2.1.277 or later) reads AGENTS.md on its own only when there's no CLAUDE.md or CLAUDE.local.md in your working directory or above it. Once you have one, import AGENTS.md from it. Details in &lt;a href="https://kitquarry.com/articles/agents-md-vs-claude-md-single-source-of-truth" rel="noopener noreferrer"&gt;AGENTS.md vs CLAUDE.md: one source of truth&lt;/a&gt;. The imported file counts toward the same budget, so keep it lean too.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fix 4: make "done" mean the tests passed
&lt;/h2&gt;

&lt;p&gt;Anthropic's best practices put it plainly: "Claude stops when the work looks done. Without a check it can run, 'looks done' is the only signal available." The fix is a check that returns pass or fail (tests, a build, a linter) plus evidence instead of claims: the command Claude ran and what it returned. You can apply it at three levels.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;In the prompt:&lt;/strong&gt; "â€¦then run the tests and fix any failures."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In CLAUDE.md:&lt;/strong&gt; a short, concrete "Definition of done" with exact commands, like the example above. This is still guidance, not enforcement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;As a deterministic gate:&lt;/strong&gt; a &lt;a href="https://code.claude.com/docs/en/hooks#stop" rel="noopener noreferrer"&gt;Stop hook&lt;/a&gt; that runs your check and blocks the turn from ending until it passes. The docs also describe a &lt;code&gt;/goal&lt;/code&gt; condition that a separate evaluator re-checks after every turn, and verification subagents that give a second opinion.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a Stop hook. Register it in &lt;code&gt;.claude/settings.json&lt;/code&gt; (Stop hooks take no matcher):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Stop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;$CLAUDE_PROJECT_DIR&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;/.claude/hooks/require-tests.sh"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"timeout"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the script, &lt;code&gt;.claude/hooks/require-tests.sh&lt;/code&gt; (run &lt;code&gt;chmod +x&lt;/code&gt; on it; it uses &lt;code&gt;jq&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# Stop hook: don't let Claude finish while the test suite fails.&lt;/span&gt;
&lt;span class="nv"&gt;INPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Already continuing because a Stop hook blocked? Let it stop instead of looping.&lt;/span&gt;
&lt;span class="c"&gt;# Delete this check for a strict gate.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$INPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.stop_hook_active'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CLAUDE_PROJECT_DIR&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0

&lt;span class="c"&gt;# No uncommitted changes means nothing to verify (e.g. Claude only answered a question).&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git status &lt;span class="nt"&gt;--porcelain&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nv"&gt;OUTPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;npm &lt;span class="nb"&gt;test &lt;/span&gt;2&amp;gt;&amp;amp;1&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Tests are failing. Fix the code, not the tests, before you finish:"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$OUTPUT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 40 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;How it behaves, per the &lt;a href="https://code.claude.com/docs/en/hooks-guide" rel="noopener noreferrer"&gt;hooks guide&lt;/a&gt; and reference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exit code 2 on a Stop hook prevents Claude from stopping, and Claude receives the stderr text as the reason to continue. Exit 0 lets it stop.&lt;/li&gt;
&lt;li&gt;Stop hooks fire whenever Claude finishes responding, not only when a task is complete. That's why the script skips clean trees, and why the check should be fast. Use your quickest meaningful test command.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;stop_hook_active&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt; when Claude is already continuing because of a Stop hook. With the check in place, Claude is pushed back to work once per stop. Without it, Claude Code still ends the turn after eight consecutive blocks; that count resets whenever Claude calls a tool, and &lt;code&gt;CLAUDE_CODE_STOP_HOOK_BLOCK_CAP&lt;/code&gt; changes the cap.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;/hooks&lt;/code&gt; to confirm the hook is registered. Test the script by hand first: &lt;code&gt;echo '{"stop_hook_active":false}' | CLAUDE_PROJECT_DIR=$PWD .claude/hooks/require-tests.sh; echo $?&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same idea covers other "always" rules. A &lt;code&gt;PostToolUse&lt;/code&gt; hook on &lt;code&gt;Edit|Write&lt;/code&gt; can run your formatter after every edit, and a &lt;code&gt;PreToolUse&lt;/code&gt; hook that exits 2 can block edits to protected files. Then those lines can come out of CLAUDE.md entirely. The docs also offer &lt;code&gt;prompt&lt;/code&gt; and &lt;code&gt;agent&lt;/code&gt; hook types for judgment calls (the agent hook example runs the test suite before Claude stops), but agent hooks are marked experimental, and the docs recommend command hooks for production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same principle in Cursor and AGENTS.md
&lt;/h2&gt;

&lt;p&gt;Cursor's &lt;a href="https://cursor.com/docs/rules" rel="noopener noreferrer"&gt;rules docs&lt;/a&gt; give the same advice: keep rules under 500 lines, split large rules into composable ones, and reference files instead of copying their contents. A rule with &lt;code&gt;alwaysApply: true&lt;/code&gt; is applied to every chat session; otherwise the agent gets it through matching &lt;code&gt;globs&lt;/code&gt; or decides from its &lt;code&gt;description&lt;/code&gt;. So keep always-apply rules for true essentials. Cursor also supports nested &lt;code&gt;AGENTS.md&lt;/code&gt; files in subdirectories, which apply when the agent works with files there. For how the formats map onto each other, see &lt;a href="https://kitquarry.com/articles/cursor-rules-vs-claude-md-vs-agents-md" rel="noopener noreferrer"&gt;Cursor rules vs CLAUDE.md vs AGENTS.md&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 15-minute cleanup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;/context&lt;/code&gt; and note every memory file that loads at launch.&lt;/li&gt;
&lt;li&gt;Count lines: &lt;code&gt;wc -l CLAUDE.md CLAUDE.local.md .claude/CLAUDE.md 2&amp;gt;/dev/null&lt;/code&gt;. Aim for under 200 per file.&lt;/li&gt;
&lt;li&gt;Ask "would removing this cause a mistake?" for every line. Cut what fails.&lt;/li&gt;
&lt;li&gt;Move procedures into skills, file-specific rules into &lt;code&gt;.claude/rules/&lt;/code&gt; with &lt;code&gt;paths&lt;/code&gt;, and personal notes into &lt;code&gt;CLAUDE.local.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Rewrite vague lines as checkable ones with exact commands and paths.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;/doctor prompt-audit&lt;/code&gt; to find conflicts and stale instructions.&lt;/li&gt;
&lt;li&gt;Add the definition of done and the Stop hook.&lt;/li&gt;
&lt;li&gt;Start a fresh session and check that Claude's behavior actually changes. Treat CLAUDE.md like code and prune it when things go wrong.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Want this layout ready-made?&lt;/strong&gt; The &lt;a href="https://kitquarry.com/free" rel="noopener noreferrer"&gt;free sampler&lt;/a&gt; (no email) ships a thin CLAUDE.md that imports a shared AGENTS.md, plus &lt;code&gt;/plan-feature&lt;/code&gt; and &lt;code&gt;/review-changes&lt;/code&gt; as skills. The &lt;a href="https://kitquarry.com/products/rules-pack" rel="noopener noreferrer"&gt;AI Coding Rules Pack&lt;/a&gt; adds an agent-safe definition-of-done rule, local rules next to risky code and more skills, for Claude Code, Cursor, Codex, Windsurf and Copilot. For the session habits that go with it, read the &lt;a href="https://kitquarry.com/articles/plan-first-workflow-for-ai-coding" rel="noopener noreferrer"&gt;plan-first workflow&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How long should CLAUDE.md be?
&lt;/h3&gt;

&lt;p&gt;Anthropic's Claude Code docs say to target under 200 lines per CLAUDE.md file, because longer files consume more context and reduce adherence. For each line, ask whether removing it would cause Claude to make mistakes. If not, cut it, or move it into a skill or a path-scoped rule.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do @imports in CLAUDE.md reduce context usage?
&lt;/h3&gt;

&lt;p&gt;No. Imported files are expanded and loaded into context at launch, alongside the CLAUDE.md that imports them. Imports help you organize a long file, but they don't lower its context cost. To load instructions only when needed, use .claude/rules files with paths frontmatter, or skills.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between CLAUDE.md, .claude/rules and skills?
&lt;/h3&gt;

&lt;p&gt;CLAUDE.md and rules without paths load at the start of every session. Rules with paths frontmatter load when Claude works with matching files. A skill's description sits in the skill listing, and its full body loads only when you or Claude invoke it. With disable-model-invocation: true, even the description stays out of context until you call the skill.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I make Claude Code run tests before it says it's done?
&lt;/h3&gt;

&lt;p&gt;Write a concrete definition of done in CLAUDE.md with the exact test command, and ask Claude to report the commands it ran and their results. For a deterministic gate, add a Stop hook that runs the tests and exits with code 2 when they fail. Claude Code then keeps the conversation going and gives Claude the hook's stderr as the reason to continue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does Claude still ignore a rule after I shortened CLAUDE.md?
&lt;/h3&gt;

&lt;p&gt;Run /context to confirm the file loaded, look for conflicting instructions in other CLAUDE.md files, .claude/rules or ~/.claude, and make the rule specific enough to check. CLAUDE.md is guidance, not enforcement. If something must happen at a fixed point every time, such as after each edit or before Claude finishes, write it as a hook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;Official docs, checked 2026-10-18.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;Claude Code docs: How Claude remembers your project (CLAUDE.md, @imports, .claude/rules)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/best-practices" rel="noopener noreferrer"&gt;Claude Code docs: Best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/skills" rel="noopener noreferrer"&gt;Claude Code docs: Extend Claude with skills&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/hooks-guide" rel="noopener noreferrer"&gt;Claude Code docs: Automate actions with hooks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/hooks" rel="noopener noreferrer"&gt;Claude Code docs: Hooks reference (Stop hooks, exit code 2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/features-overview" rel="noopener noreferrer"&gt;Claude Code docs: Extend Claude Code (CLAUDE.md vs rules vs skills vs hooks)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cursor.com/docs/rules" rel="noopener noreferrer"&gt;Cursor docs: Rules&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;If you want a starting point, KitQuarry's free sampler is on GitHub: &lt;a href="https://github.com/elirankotzero/ai-coding-rules-sampler" rel="noopener noreferrer"&gt;ai-coding-rules-sampler&lt;/a&gt;. It has a short CLAUDE.md/AGENTS.md, security rules and three skills (plan-feature, review-changes, record-decisions) for Claude Code, Cursor, Codex and Windsurf. MIT, free. (I made it.)&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to write a Claude Code skill (SKILL.md) that actually triggers</title>
      <dc:creator>Eliran Kotzero</dc:creator>
      <pubDate>Sun, 18 Oct 2026 06:07:44 +0000</pubDate>
      <link>https://dev.to/eliran_kotzero_e1781dc487/how-to-write-a-claude-code-skill-skillmd-that-actually-triggers-5egi</link>
      <guid>https://dev.to/eliran_kotzero_e1781dc487/how-to-write-a-claude-code-skill-skillmd-that-actually-triggers-5egi</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://kitquarry.com/articles/claude-code-skill-md-that-triggers" rel="noopener noreferrer"&gt;KitQuarry&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; a skill triggers on its &lt;code&gt;description&lt;/code&gt;, not its body. Say what it does and when to use it, in the third person, using the words people actually type, and put the main use case first. Keep the frontmatter to &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; and the same SKILL.md works in Claude Code, Codex and Cursor. Then test it in a fresh session with prompts that should and shouldn't trigger it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How an agent decides to use a skill
&lt;/h2&gt;

&lt;p&gt;Skills use progressive disclosure. When a session starts, the agent sees only a listing: each skill's name and description. The body of &lt;code&gt;SKILL.md&lt;/code&gt; loads when the skill is invoked, either because you typed its name or because the model decided it fits the task. So when a skill "doesn't work", the problem is usually that listing entry, not the instructions underneath it.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code&lt;/strong&gt; cuts each entry's combined &lt;code&gt;description&lt;/code&gt; and &lt;code&gt;when_to_use&lt;/code&gt; text at 1,536 characters. The whole listing gets about 1% of the context window. When it overflows, Claude Code drops descriptions for the skills you use least, and only their names remain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex&lt;/strong&gt; gives the initial skills list at most 2% of the context window (8,000 characters if the window size is unknown) and shortens descriptions first when many skills are installed. &lt;code&gt;skills.max_context_tokens&lt;/code&gt; changes that budget.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Agent Skills spec&lt;/strong&gt;, the open standard all three tools build on, caps &lt;code&gt;description&lt;/code&gt; at 1,024 characters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical rule: front-load the key use case and trigger words, and stay under 1,024 characters so the file is valid everywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the file goes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Project skills&lt;/th&gt;
&lt;th&gt;Personal skills&lt;/th&gt;
&lt;th&gt;Run it yourself&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.claude/skills/&amp;lt;name&amp;gt;/SKILL.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.claude/skills/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/name&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.agents/skills/&amp;lt;name&amp;gt;/SKILL.md&lt;/code&gt;, scanned from the launch directory up to the repo root&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.agents/skills/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;$name&lt;/code&gt;, or pick from &lt;code&gt;/skills&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.cursor/skills/&lt;/code&gt; or &lt;code&gt;.agents/skills/&lt;/code&gt; (also reads &lt;code&gt;.claude/skills/&lt;/code&gt; and &lt;code&gt;.codex/skills/&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;~/.cursor/skills/&lt;/code&gt;, &lt;code&gt;~/.agents/skills/&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Type &lt;code&gt;/&lt;/code&gt; and the skill name&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Name the folder carefully. The spec says &lt;code&gt;name&lt;/code&gt; must match the parent folder and use only lowercase letters, digits and single hyphens, up to 64 characters. In Claude Code the folder name (or &lt;code&gt;name&lt;/code&gt;) becomes the slash command. Anthropic's authoring guide also reserves the words "anthropic" and "claude" in skill names, so avoid names like &lt;code&gt;claude-helper&lt;/code&gt; if you might upload the skill to claude.ai or the API later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Anatomy of a description that triggers
&lt;/h2&gt;

&lt;p&gt;The model reads descriptions to choose a skill for the current request, sometimes out of dozens. Anthropic's authoring guide and OpenAI's Codex docs give the same advice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What, then when.&lt;/strong&gt; One clause on what the skill does, then "Use when..." with concrete situations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Third person.&lt;/strong&gt; "Writes tests for..." rather than "I can help you write tests". The description is injected into the system prompt, and Anthropic warns that an inconsistent point of view can cause discovery problems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The user's words.&lt;/strong&gt; Include phrasings people actually type ("add tests", "coverage", "this is untested") plus file types and tools (&lt;code&gt;.tsx&lt;/code&gt;, pytest, Vitest).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boundaries.&lt;/strong&gt; Codex's docs say to explain when the skill should and should &lt;em&gt;not&lt;/em&gt; trigger. A short "Not for..." clause stops near-misses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Front-loaded.&lt;/strong&gt; If descriptions get shortened, the first sentence is the part that survives.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Helps with tests.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Writes and runs unit and integration tests for changed code. Use when the user asks to add tests, improve coverage, reproduce a bug with a failing test, or says code is untested. Not for end-to-end browser tests.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A working template
&lt;/h2&gt;

&lt;p&gt;Here is a complete skill. Save it as &lt;code&gt;.claude/skills/write-tests/SKILL.md&lt;/code&gt; for Claude Code, or &lt;code&gt;.agents/skills/write-tests/SKILL.md&lt;/code&gt; for Codex and Cursor. It uses only the two fields every tool understands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write-tests&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Writes and runs unit and integration tests for changed code. Use when the user asks to add tests, improve coverage, reproduce a bug with a failing test, or says code is untested. Not for end-to-end browser tests.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="gh"&gt;# Write tests&lt;/span&gt;

&lt;span class="gu"&gt;## Steps&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; List what changed: &lt;span class="sb"&gt;`git diff --name-only HEAD`&lt;/span&gt;. If nothing changed, ask which files to cover.
&lt;span class="p"&gt;2.&lt;/span&gt; Find the test runner and conventions in package.json, pyproject.toml or the existing tests. Match their location, naming and style.
&lt;span class="p"&gt;3.&lt;/span&gt; For each changed behaviour, write a happy-path test, one edge case and one failure case.
&lt;span class="p"&gt;4.&lt;/span&gt; Run the new tests first, then the full suite.
&lt;span class="p"&gt;5.&lt;/span&gt; If a test fails, fix the code or the test. Never delete or weaken an existing assertion to get a pass.

&lt;span class="gu"&gt;## Report&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Files added or changed
&lt;span class="p"&gt;-&lt;/span&gt; Cases covered, one line each
&lt;span class="p"&gt;-&lt;/span&gt; The exact test command you ran and its result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As it grows, keep &lt;code&gt;SKILL.md&lt;/code&gt; as the overview and move detail into files it links to directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;write-tests/
â”œâ”€â”€ SKILL.md          # required: frontmatter + steps
â”œâ”€â”€ references/
â”‚   â””â”€â”€ fixtures.md   # optional: read only when SKILL.md points to it
â””â”€â”€ scripts/
    â””â”€â”€ coverage.sh   # optional: executed, not loaded into context
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why it is shaped this way. The steps are imperative and end in an explicit output, which the Codex docs recommend. The "never weaken an assertion" line is concrete enough to check in review. Long reference material sits one level deep, so it costs no context until it is needed. Anthropic and the spec both recommend keeping &lt;code&gt;SKILL.md&lt;/code&gt; under 500 lines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make side-effect skills manual-only
&lt;/h2&gt;

&lt;p&gt;Some skills should never fire on their own: committing, deploying, sending messages. Each tool has a switch.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Only you can invoke it&lt;/th&gt;
&lt;th&gt;Only the model can invoke it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;&lt;code&gt;disable-model-invocation: true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;user-invocable: false&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;&lt;code&gt;disable-model-invocation: true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Not documented&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;allow_implicit_invocation: false&lt;/code&gt; in &lt;code&gt;agents/openai.yaml&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Not documented&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .claude/skills/commit-work/SKILL.md (Claude Code; Cursor reads the same field)&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;commit-work&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Runs all checks, then stages and commits the current work with a Conventional Commits message. Only when the user explicitly asks to commit.&lt;/span&gt;
&lt;span class="na"&gt;disable-model-invocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="c1"&gt;# .agents/skills/commit-work/agents/openai.yaml (Codex)&lt;/span&gt;
&lt;span class="na"&gt;policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allow_implicit_invocation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Claude Code, a skill with &lt;code&gt;disable-model-invocation: true&lt;/code&gt; is left out of the model's listing completely, so it also costs no context until you call it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Claude Code extras, and when to skip them
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;when_to_use&lt;/code&gt;: extra trigger phrases, appended to the description and counted toward the 1,536-character cap.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;paths&lt;/code&gt;: globs that limit automatic loading to matching files. Cursor supports &lt;code&gt;paths&lt;/code&gt; too.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;argument-hint&lt;/code&gt; and &lt;code&gt;$ARGUMENTS&lt;/code&gt; (or &lt;code&gt;$0&lt;/code&gt;, &lt;code&gt;$1&lt;/code&gt;): arguments, as in &lt;code&gt;/fix-issue 123&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A line like &lt;code&gt;!`git diff HEAD`&lt;/code&gt;: dynamic context. Claude Code runs the command and inlines its output before Claude reads the skill.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;allowed-tools&lt;/code&gt;: pre-approves tools for the turn that invokes the skill. The grant clears when you send your next message, and it doesn't block other tools. Read it before running skills you didn't write.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;context: fork&lt;/code&gt;: runs the skill in a subagent that can't see your conversation, so the skill must stand on its own.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These fields are Claude Code extensions, not part of the spec. Cursor documents its own small set (&lt;code&gt;paths&lt;/code&gt;, &lt;code&gt;disable-model-invocation&lt;/code&gt;, &lt;code&gt;metadata&lt;/code&gt; and a few display fields), and the Codex docs list only &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; plus &lt;code&gt;agents/openai.yaml&lt;/code&gt;. Uploading a skill to claude.ai or the Skills API fails with an "Unexpected key(s)" error if the frontmatter holds anything beyond &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;license&lt;/code&gt;, &lt;code&gt;compatibility&lt;/code&gt;, &lt;code&gt;metadata&lt;/code&gt; and &lt;code&gt;allowed-tools&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a skill doesn't trigger: a checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The frontmatter isn't on line 1.&lt;/strong&gt; Claude Code reads frontmatter only when the opening &lt;code&gt;---&lt;/code&gt; is the file's first line. Otherwise the whole file, markers included, is treated as content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The YAML doesn't parse.&lt;/strong&gt; Claude Code still loads the skill, but with no fields set, so &lt;code&gt;/name&lt;/code&gt; works while automatic matching has no description to use. An unquoted description containing ": " is a common cause. Wrap it in quotes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wrong folder for the tool.&lt;/strong&gt; Claude Code's documented locations are under &lt;code&gt;.claude/skills&lt;/code&gt;; Codex scans &lt;code&gt;.agents/skills&lt;/code&gt;. A skill in only one of them is invisible to the other tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The description is vague,&lt;/strong&gt; or it describes the implementation rather than the request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Too many skills.&lt;/strong&gt; When the listing overflows its budget, descriptions get dropped or shortened. In Claude Code, &lt;code&gt;/doctor&lt;/code&gt; estimates the listing's cost and &lt;code&gt;/skill-doctor&lt;/code&gt; flags skills you never use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's switched off:&lt;/strong&gt; &lt;code&gt;disable-model-invocation&lt;/code&gt;, a &lt;code&gt;skillOverrides&lt;/code&gt; entry in Claude Code settings, or &lt;code&gt;enabled = false&lt;/code&gt; under &lt;code&gt;[[skills.config]]&lt;/code&gt; in &lt;code&gt;~/.codex/config.toml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It's nested.&lt;/strong&gt; In Claude Code, skills in a &lt;code&gt;.claude/skills&lt;/code&gt; folder below where you started load only after Claude reads or edits a file in that subdirectory.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If a skill triggers but Claude stops following it later in the session: Claude Code doesn't re-read &lt;code&gt;SKILL.md&lt;/code&gt; on later turns, and after auto-compaction it keeps only the first 5,000 tokens of each invoked skill. Put the most important rules at the top, word them as standing instructions ("run the tests after every edit"), and move anything that must hold every time into a hook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test it like code
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start a fresh session.&lt;/strong&gt; Anthropic's docs point out that leftover context from writing the skill hides gaps in the instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confirm it is listed.&lt;/strong&gt; In Claude Code, ask "What skills are available?" or run &lt;code&gt;/skills&lt;/code&gt;. In Codex, run &lt;code&gt;/skills&lt;/code&gt;. In Cursor, open Customize, then Skills.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write 3â€“5 prompts that should trigger it&lt;/strong&gt; and 2â€“3 near-misses that shouldn't. For the template above, "add a Playwright test for checkout" is a good near-miss.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run each prompt with the skill on and off&lt;/strong&gt; and compare the results. In Claude Code, set the skill to &lt;code&gt;"off"&lt;/code&gt; in &lt;code&gt;skillOverrides&lt;/code&gt; for the baseline run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validate the frontmatter&lt;/strong&gt; with &lt;code&gt;claude plugin validate .claude/skills&lt;/code&gt; (Claude Code v2.1.233 or later) or &lt;code&gt;skills-ref validate ./write-tests&lt;/code&gt; from the spec's reference library.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Tune the description, not the body, until it triggers when it should. If it fires too often, narrow the description or make the skill manual-only.&lt;/p&gt;

&lt;h2&gt;
  
  
  One source, three tools
&lt;/h2&gt;

&lt;p&gt;Because all three tools follow the same spec, a SKILL.md with just &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt; runs everywhere. The catch is duplication. Cursor also reads &lt;code&gt;.claude/skills&lt;/code&gt; and &lt;code&gt;.agents/skills&lt;/code&gt;, so a repo that carries the same skill in several folders should keep the copies identical, ideally generated from one source. That is how our kits are built: one master source compiled into each tool's native folder and checked by a format validator. For how skills relate to rule files, see &lt;a href="https://kitquarry.com/articles/cursor-rules-vs-claude-md-vs-agents-md" rel="noopener noreferrer"&gt;Cursor rules vs CLAUDE.md vs AGENTS.md&lt;/a&gt;, and for the workflow these skills support, the &lt;a href="https://kitquarry.com/articles/plan-first-workflow-for-ai-coding" rel="noopener noreferrer"&gt;plan-first workflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want working examples?&lt;/strong&gt; The &lt;a href="https://kitquarry.com/free" rel="noopener noreferrer"&gt;free sampler&lt;/a&gt; (no email) includes &lt;code&gt;/plan-feature&lt;/code&gt; and &lt;code&gt;/review-changes&lt;/code&gt; as SKILL.md files for Claude Code and Codex, plus the same workflows for Cursor, Windsurf and Copilot. The &lt;a href="https://kitquarry.com/products/rules-pack" rel="noopener noreferrer"&gt;Rules Pack&lt;/a&gt; adds &lt;code&gt;/fix-bug&lt;/code&gt;, &lt;code&gt;/security-audit&lt;/code&gt;, &lt;code&gt;/write-tests&lt;/code&gt; and a manual-only &lt;code&gt;/commit-work&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Do I need both name and description in SKILL.md?
&lt;/h3&gt;

&lt;p&gt;For portability, yes. The Agent Skills spec, Codex and Cursor all require name and description. Claude Code treats every field as optional (it falls back to the folder name and the first non-empty line of the body), but the description is what Claude matches against, so always write one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does my skill run with /name but never trigger on its own?
&lt;/h3&gt;

&lt;p&gt;Usually the description is too vague or doesn't use the words people actually type. Other causes: disable-model-invocation is set, the YAML frontmatter fails to parse (Claude Code then loads the skill with no fields), or so many skills are installed that the listing dropped its description.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where do Codex skills go, and how do I run one?
&lt;/h3&gt;

&lt;p&gt;Put them in .agents/skills/&amp;lt;name&amp;gt;/SKILL.md. Codex scans .agents/skills in every directory from where you launch it up to the repository root, plus ~/.agents/skills for personal skills. Mention a skill with $name, or pick it from /skills.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can one SKILL.md work in Claude Code, Codex and Cursor?
&lt;/h3&gt;

&lt;p&gt;Yes, if you stick to the Agent Skills spec fields (name, description, and optionally license, compatibility, metadata, allowed-tools). Copy the same folder to .claude/skills for Claude Code and .agents/skills for Codex. Cursor reads .cursor/skills and .agents/skills, and also .claude/skills and .codex/skills.&lt;/p&gt;

&lt;h3&gt;
  
  
  How long can a skill description be?
&lt;/h3&gt;

&lt;p&gt;The Agent Skills spec allows up to 1,024 characters. Claude Code cuts the combined description and when_to_use text at 1,536 characters in its listing, and Codex shortens descriptions when many skills are installed. Put the main use case and trigger words in the first sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;p&gt;Official docs, checked 2026-10-16.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://code.claude.com/docs/en/skills" rel="noopener noreferrer"&gt;Claude Code docs: Extend Claude with skills&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices" rel="noopener noreferrer"&gt;Anthropic: Skill authoring best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.openai.com/codex/skills" rel="noopener noreferrer"&gt;OpenAI Codex docs: Build skills&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cursor.com/docs/context/skills" rel="noopener noreferrer"&gt;Cursor docs: Agent Skills&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://agentskills.io/specification" rel="noopener noreferrer"&gt;Agent Skills specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Want ready-made SKILL.md examples to copy? The free sampler is on GitHub: &lt;a href="https://github.com/elirankotzero/ai-coding-rules-sampler" rel="noopener noreferrer"&gt;elirankotzero/ai-coding-rules-sampler&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
