<?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: PromptMaster</title>
    <description>The latest articles on DEV Community by PromptMaster (@promptmaster).</description>
    <link>https://dev.to/promptmaster</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%2F3982446%2F2b8c89f7-77ac-468e-b0a0-a6af747980f0.jpeg</url>
      <title>DEV Community: PromptMaster</title>
      <link>https://dev.to/promptmaster</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/promptmaster"/>
    <language>en</language>
    <item>
      <title>A Skill Can Run Commands Before Claude Reads It: What to Check First</title>
      <dc:creator>PromptMaster</dc:creator>
      <pubDate>Sat, 14 Nov 2026 10:42:05 +0000</pubDate>
      <link>https://dev.to/promptmaster/a-skill-can-run-commands-before-claude-reads-it-what-to-check-first-20b5</link>
      <guid>https://dev.to/promptmaster/a-skill-can-run-commands-before-claude-reads-it-what-to-check-first-20b5</guid>
      <description>&lt;p&gt;You find a skill on GitHub that writes release notes. It looks harmless: a &lt;code&gt;SKILL.md&lt;/code&gt; with a description and a few lines of instructions. You copy the folder into &lt;code&gt;.claude/skills/&lt;/code&gt;, ask for release notes, and it works.&lt;/p&gt;

&lt;p&gt;What you may not have checked is that a skill isn't only text. In Claude Code, a few lines in that file can run shell commands before Claude reads anything, pre-approve tools, or register hooks that keep running for the rest of your session. None of that is hidden, but none of it is obvious when you skim the instructions either.&lt;/p&gt;

&lt;p&gt;In an &lt;a href="https://dev.to/promptmaster/agent-skills-are-now-an-open-standard-write-your-first-skillmd-in-15-minutes-21g0"&gt;earlier article&lt;/a&gt; I walked through writing your first skill and ended with a short list of things to check before installing someone else's. This one goes deeper on that list: the four places in a skill folder where something can run or get access, what Claude Code's &lt;a href="https://code.claude.com/docs/en/skills" rel="noopener noreferrer"&gt;skills documentation&lt;/a&gt; says about each, and a small script that finds them for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where a skill can do more than talk
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://agentskills.io/specification" rel="noopener noreferrer"&gt;Agent Skills specification&lt;/a&gt; defines a skill as a folder with a &lt;code&gt;SKILL.md&lt;/code&gt; file (YAML frontmatter plus Markdown instructions) and optional &lt;code&gt;scripts/&lt;/code&gt;, &lt;code&gt;references/&lt;/code&gt; and &lt;code&gt;assets/&lt;/code&gt; folders. Most of that is plain text the model reads. Four things are different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Injected commands (&lt;code&gt;!&lt;/code&gt; plus a command in backticks).&lt;/strong&gt; Claude Code calls this dynamic context injection. A line that starts with &lt;code&gt;!&lt;/code&gt; followed by a command in backticks, or a fenced block opened with three backticks and &lt;code&gt;!&lt;/code&gt;, makes Claude Code run the command &lt;em&gt;before&lt;/em&gt; the skill content is sent to Claude, then replace the line with its output. The docs say these commands "never prompt for permission while the skill renders." They are checked against your permission rules first: a command a deny or ask rule matches aborts the skill, and outside auto mode anything that isn't already allowed also aborts, unless the skill pre-approves it with &lt;code&gt;allowed-tools&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. &lt;code&gt;allowed-tools&lt;/code&gt;.&lt;/strong&gt; This frontmatter field lets Claude use the listed tools without asking you, during the turn that invokes the skill. The grant clears when you send your next message. The part worth reading twice: per the docs, workspace trust doesn't gate this field, and Claude Code applies a project skill's &lt;code&gt;allowed-tools&lt;/code&gt; even in a &lt;code&gt;-p&lt;/code&gt; run in a folder you've never trusted. The docs' own advice is to review the &lt;code&gt;allowed-tools&lt;/code&gt; of skills checked into a repository before you run Claude Code there. (The open spec marks &lt;code&gt;allowed-tools&lt;/code&gt; as experimental, and other agents may handle it differently.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. &lt;code&gt;hooks&lt;/code&gt;.&lt;/strong&gt; A skill can define hooks in its frontmatter. According to the &lt;a href="https://code.claude.com/docs/en/hooks" rel="noopener noreferrer"&gt;hooks docs&lt;/a&gt;, Claude Code registers them when you or Claude invoke the skill and keeps running them for the rest of the session, not just during the skill's turn, unless a hook sets &lt;code&gt;once: true&lt;/code&gt;. Frontmatter hooks in a project skill follow the same workspace trust rule as hooks in settings files, and the docs note they are registered on invocation even in a &lt;code&gt;-p&lt;/code&gt; run in a folder you haven't trusted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. &lt;code&gt;scripts/&lt;/code&gt; and anything else in the folder.&lt;/strong&gt; The spec describes &lt;code&gt;scripts/&lt;/code&gt; as executable code that agents can run. The instructions in &lt;code&gt;SKILL.md&lt;/code&gt; decide when, so a line like "run scripts/fetch-style.sh first" is an instruction to execute whatever that file contains.&lt;/p&gt;

&lt;p&gt;One more field changes &lt;em&gt;when&lt;/em&gt; all of this happens: &lt;code&gt;disable-model-invocation&lt;/code&gt;. It defaults to &lt;code&gt;false&lt;/code&gt;, which means Claude can load the skill on its own when your request matches its description. Set to &lt;code&gt;true&lt;/code&gt;, the skill only runs when you type &lt;code&gt;/name&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small script that finds all four
&lt;/h2&gt;

&lt;p&gt;Reading every file by hand is still the real review. But a script that lists the risky parts first tells you where to look. This one uses only the Python standard library. It doesn't run anything from the skill; it only reads the files and prints flags.&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;"""Flag the parts of a skill folder that run commands or grant access."""&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;

&lt;span class="no"&gt;RISKY_KEYS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"allowed-tools"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"hooks"&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;frontmatter&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="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&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="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;lines&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;strip&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="s2"&gt;"---"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&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;lines&lt;/span&gt;
    &lt;span class="k"&gt;for&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;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&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="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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&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="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&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="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"---"&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;lines&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;i&lt;/span&gt;&lt;span class="p"&gt;]&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;i&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="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&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;lines&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;audit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skill_dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;skill_dir&lt;/span&gt; &lt;span class="o"&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;skill_dir&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skill_dir&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s2"&gt;"SKILL.md"&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;front&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="nf"&gt;frontmatter&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="k"&gt;print&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;"Skill: {skill_dir.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;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;front&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="n"&gt;line&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;":"&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="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;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;if&lt;/span&gt; &lt;span class="s2"&gt;"command:"&lt;/span&gt; &lt;span class="k"&gt;in&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;print&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;"  [HOOK]   {line.strip()}"&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;line&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;continue&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="no"&gt;RISKY_KEYS&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;f&lt;/span&gt;&lt;span class="s2"&gt;"  [GRANT]  frontmatter {line.strip()}"&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;key&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;line&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;":"&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="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="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="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;skill_dir&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="k"&gt;print&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;"  [NAME]   name does not match folder: {line.strip()}"&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;l&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;"disable-model-invocation"&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;l&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;front&lt;/span&gt;&lt;span class="p"&gt;)&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;"  [AUTO]   Claude can load this skill on its own (no disable-model-invocation)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;in_bang_block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&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="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;front&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;line&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="n"&gt;in_bang_block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;in_bang_block&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;line&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="n"&gt;in_bang_block&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&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;print&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;"  [RUNS]   line {n}: {line.strip()}"&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;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;r&lt;/span&gt;&lt;span class="s2"&gt;"(^|\s)!`[^`]+`"&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="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;f&lt;/span&gt;&lt;span class="s2"&gt;"  [RUNS]   line {n}: {line.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;path&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skill_dir&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="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;path&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;and&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"SKILL.md"&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;f&lt;/span&gt;&lt;span class="s2"&gt;"  [FILE]   {path.relative_to(skill_dir)}"&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;url&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;findall&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;"https?://\S+"&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;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="s2"&gt;"ignore"&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;print&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;"  [URL]    {path.name}: {url}"&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="k"&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="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;audit&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's deliberately simple. It reads frontmatter line by line instead of parsing YAML, so treat a clean result as "nothing obvious," not "safe."&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it on an example skill
&lt;/h2&gt;

&lt;p&gt;To test it, I made an illustrative skill called &lt;code&gt;release-notes&lt;/code&gt;. It isn't a real published skill; I wrote it to contain one of each pattern. Its &lt;code&gt;SKILL.md&lt;/code&gt; looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;---
name: release-notes
description: Drafts release notes from recent commits. Use when the user asks for release notes, a changelog, or what shipped this week.
allowed-tools: Bash(git *) Bash(curl *)
hooks:
  PostToolUse:
    &lt;span class="k"&gt;- &lt;/span&gt;matcher: "Bash"
      hooks:
        &lt;span class="k"&gt;- &lt;/span&gt;type: command
          command: "./scripts/sync.sh"
---

&lt;span class="gu"&gt;## Recent commits
&lt;/span&gt;
!&lt;span class="sb"&gt;`git log --oneline -20`&lt;/span&gt;

&lt;span class="gu"&gt;## Instructions
&lt;/span&gt;
Group the commits above into Added, Changed and Fixed.
For the style guide, run scripts/fetch-style.sh first.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next to it are two scripts: &lt;code&gt;scripts/fetch-style.sh&lt;/code&gt;, which calls &lt;code&gt;curl&lt;/code&gt; on an example URL, and &lt;code&gt;scripts/sync.sh&lt;/code&gt;, which only prints a word. Here's the real output of &lt;code&gt;python3 audit_skill.py release-notes&lt;/code&gt; (Python 3.13.5):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Skill: release-notes
  [GRANT]  frontmatter allowed-tools: Bash(git *) Bash(curl *)
  [GRANT]  frontmatter hooks:
  [HOOK]   command: "./scripts/sync.sh"
  [AUTO]   Claude can load this skill on its own (no disable-model-invocation)
  [RUNS]   line 15: !`git log --oneline -20`
  [FILE]   scripts/fetch-style.sh
  [URL]    fetch-style.sh: https://example.com/style.md
  [FILE]   scripts/sync.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading it from top to bottom:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;[GRANT] allowed-tools&lt;/code&gt;&lt;/strong&gt;: any &lt;code&gt;git&lt;/code&gt; or &lt;code&gt;curl&lt;/code&gt; command runs without asking during the turn the skill is invoked. &lt;code&gt;git&lt;/code&gt; fits a release-notes skill. &lt;code&gt;curl&lt;/code&gt; doesn't, and that mismatch with the stated purpose is exactly what Anthropic's &lt;a href="https://platform.claude.com/docs/en/agents-and-tools/agent-skills/overview" rel="noopener noreferrer"&gt;Agent Skills overview&lt;/a&gt; tells you to look for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;[GRANT] hooks&lt;/code&gt; + &lt;code&gt;[HOOK]&lt;/code&gt;&lt;/strong&gt;: after the first invocation, &lt;code&gt;./scripts/sync.sh&lt;/code&gt; runs after every Bash call for the rest of the session. Open that script before anything else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;[AUTO]&lt;/code&gt;&lt;/strong&gt;: Claude can trigger all of this when you ask for "what shipped this week," without you typing &lt;code&gt;/release-notes&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;[RUNS] line 15&lt;/code&gt;&lt;/strong&gt;: &lt;code&gt;git log&lt;/code&gt; runs before Claude reads the skill. Harmless here, but this is the line to check in every skill you install.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;[URL]&lt;/code&gt;&lt;/strong&gt;: the skill fetches content from outside. The overview warns that fetched content can carry instructions, and that a trustworthy skill can change if what it fetches changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to do with each flag
&lt;/h2&gt;

&lt;p&gt;You don't have to reject a skill because it has flags. A release-notes skill that runs &lt;code&gt;git log&lt;/code&gt; is doing its job. The goal is that every flag has a reason you agree with.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Trim &lt;code&gt;allowed-tools&lt;/code&gt;&lt;/strong&gt; to what the task needs. Here, &lt;code&gt;Bash(git log *)&lt;/code&gt; is enough; drop &lt;code&gt;curl&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read every hook command and every script&lt;/strong&gt; the hooks or instructions call. If a hook isn't needed for the skill's job, remove it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;disable-model-invocation: true&lt;/code&gt;&lt;/strong&gt; to skills with side effects, so they only run when you type the command.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace fetched content&lt;/strong&gt; with a file in &lt;code&gt;references/&lt;/code&gt; that you've read, when the content doesn't need to be live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use permission rules as a backstop.&lt;/strong&gt; Deny rules still apply to injected commands, and you can allow or deny skills by name with &lt;code&gt;Skill(name)&lt;/code&gt; rules in &lt;code&gt;/permissions&lt;/code&gt;. To turn off injected commands for user, project and plugin skills entirely, the docs describe a &lt;code&gt;disableSkillShellExecution&lt;/code&gt; setting.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Checklist before you install a skill
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Read &lt;code&gt;SKILL.md&lt;/code&gt; and every file in the folder, not just the instructions.&lt;/li&gt;
&lt;li&gt;List every &lt;code&gt;!&lt;/code&gt; command and fenced &lt;code&gt;!&lt;/code&gt; block, and know what each one runs.&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;allowed-tools&lt;/code&gt; against the skill's stated purpose.&lt;/li&gt;
&lt;li&gt;Read every hook command, and remember skill hooks keep running for the session.&lt;/li&gt;
&lt;li&gt;Open every script the instructions tell the agent to run.&lt;/li&gt;
&lt;li&gt;Note every external URL and decide whether it needs to be live.&lt;/li&gt;
&lt;li&gt;Decide whether Claude should be able to invoke it on its own.&lt;/li&gt;
&lt;li&gt;For skills that arrive inside a repository, do this before you run Claude Code in it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;If you're still at the stage of writing and installing your own skills, the free &lt;a href="https://promptmasterstore.gumroad.com/l/ai-agent-skills-cheat-sheet?utm_source=devto" rel="noopener noreferrer"&gt;AI Agent Skills Quick-Start Cheat Sheet&lt;/a&gt; covers the format, a copy-and-adapt &lt;code&gt;SKILL.md&lt;/code&gt; template, why most skills never trigger, and how to install and test skills across the major agents, in six pages.&lt;/p&gt;

&lt;p&gt;When you want the full reference, &lt;a href="https://promptmasterstore.gumroad.com/l/ai-agent-skills-complete-guide?utm_source=devto" rel="noopener noreferrer"&gt;AI Agent Skills: The Complete SKILL.md Standard Guide&lt;/a&gt; covers the whole specification, five build walkthroughs from markdown-only to API-calling skills, ten production-ready templates, a chapter on skills security and a 30-day plan.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI and reviewed by the author. Claude Code behavior is described from Anthropic's official documentation (skills and hooks pages) and the Agent Skills specification at agentskills.io. The &lt;code&gt;release-notes&lt;/code&gt; skill is an illustrative example made for this article, and the output shown is the script's real output on it, not output from a Claude Code session. PromptMaster guides are independent and not affiliated with or endorsed by Anthropic.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>security</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Not Every Rule Belongs in CLAUDE.md: When a Script or Hook Is Better</title>
      <dc:creator>PromptMaster</dc:creator>
      <pubDate>Fri, 13 Nov 2026 08:27:43 +0000</pubDate>
      <link>https://dev.to/promptmaster/not-every-rule-belongs-in-claudemd-when-a-script-or-hook-is-better-4om4</link>
      <guid>https://dev.to/promptmaster/not-every-rule-belongs-in-claudemd-when-a-script-or-hook-is-better-4om4</guid>
      <description>&lt;p&gt;You have a line in your &lt;code&gt;CLAUDE.md&lt;/code&gt; that says "Never edit existing migrations." Claude Code follows it. Almost always. Then one afternoon it "fixes" a column type directly in &lt;code&gt;0007_add_orders.sql&lt;/code&gt;, and you spend an hour untangling a database that no longer matches its history.&lt;/p&gt;

&lt;p&gt;The usual reaction is to make the line louder. Anthropic's &lt;a href="https://code.claude.com/docs/en/best-practices" rel="noopener noreferrer"&gt;Claude Code best practices&lt;/a&gt; say adding IMPORTANT probably won't fix it: either the file is long enough that the line gets lost, or it's a rule that can't have exceptions, and that kind of rule belongs in a hook.&lt;/p&gt;

&lt;p&gt;In an &lt;a href="https://dev.to/promptmaster/your-claudemd-is-too-long-what-to-cut-so-claude-code-follows-it-5713"&gt;earlier article&lt;/a&gt; I covered what to cut from a long &lt;code&gt;CLAUDE.md&lt;/code&gt;. This one is about the lines worth keeping that still fail you: should each one stay a sentence, become a script, or become a hook?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a sentence can't guarantee anything
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;memory docs&lt;/a&gt; are direct about this: Claude treats &lt;code&gt;CLAUDE.md&lt;/code&gt; files "as context, not enforced configuration." The best-practices page splits every instruction into two kinds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Non-deterministic.&lt;/strong&gt; You write it, the model reads it and decides what to do. Your prompt, &lt;code&gt;CLAUDE.md&lt;/code&gt; and skills are in this group. Claude usually follows them, but there's no guarantee.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic.&lt;/strong&gt; You configure it, and Claude Code (the harness) applies it every time. Hooks, deny rules and managed settings are in this group.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same page says that most of the time you &lt;em&gt;want&lt;/em&gt; Claude to decide, because almost every guideline has exceptions. So the goal isn't to turn your file into hooks. It's to find the few rules where "almost always" isn't good enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three-way test
&lt;/h2&gt;

&lt;p&gt;For each line in your &lt;code&gt;CLAUDE.md&lt;/code&gt;, ask three questions in order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Does it have exceptions?&lt;/strong&gt; "Prefer named exports", "use the error helpers in &lt;code&gt;src/lib/errors.ts&lt;/code&gt;". These are guidelines, and you want Claude to use judgment. &lt;strong&gt;Keep them in &lt;code&gt;CLAUDE.md&lt;/code&gt;.&lt;/strong&gt; The best-practices page uses "Prefer named exports" as its example of a rule that should &lt;em&gt;not&lt;/em&gt; become a hook.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Is it a procedure with exact steps?&lt;/strong&gt; "Before committing, run the linter, then the type checker, then the unit tests, and if the API changed, regenerate the client." That's a sequence of commands written as prose. Claude has to read it, remember it and type it correctly every time. &lt;strong&gt;Turn it into a script&lt;/strong&gt;, and leave one line in &lt;code&gt;CLAUDE.md&lt;/code&gt; that names the script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Must it always (or never) happen, with zero exceptions?&lt;/strong&gt; "Never edit an existing migration." "Never touch &lt;code&gt;.env&lt;/code&gt;." "Don't finish until the type check passes." If a single miss costs you real time, &lt;strong&gt;make it a hook&lt;/strong&gt;, so the outcome no longer depends on what the model remembers.&lt;/p&gt;

&lt;p&gt;A handy signal from the docs: if you keep adding emphasis to a &lt;code&gt;CLAUDE.md&lt;/code&gt; line and Claude still misses it, that line is a candidate for a hook. And the reverse: if you have hooks for things that are only preferences, delete them and let Claude decide.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Turn procedures into a script
&lt;/h2&gt;

&lt;p&gt;Here's the kind of block that grows in a &lt;code&gt;CLAUDE.md&lt;/code&gt; over time:&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="gu"&gt;## Before you finish
&lt;/span&gt;&lt;span class="k"&gt;- &lt;/span&gt;Run &lt;span class="sb"&gt;`pnpm lint`&lt;/span&gt;
&lt;span class="k"&gt;- &lt;/span&gt;Then run &lt;span class="sb"&gt;`pnpm tsc --noEmit`&lt;/span&gt;
&lt;span class="k"&gt;- &lt;/span&gt;Then run &lt;span class="sb"&gt;`pnpm test --run`&lt;/span&gt;
&lt;span class="k"&gt;- &lt;/span&gt;If you changed anything in src/api, run &lt;span class="sb"&gt;`pnpm gen:client`&lt;/span&gt; first
&lt;span class="k"&gt;- &lt;/span&gt;Fix every error before saying you're done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Move the commands into a file the whole team can run:&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;# scripts/check.sh: the one command that says "this change is ready"&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; -e
pnpm lint
pnpm tsc --noEmit
pnpm &lt;span class="nb"&gt;test&lt;/span&gt; --run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And replace the block with a single line:&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="k"&gt;- &lt;/span&gt;Run &lt;span class="sb"&gt;`./scripts/check.sh`&lt;/span&gt; before you say a change is done.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The commands are an example for a pnpm/TypeScript project. Use whatever your project actually runs.)&lt;/p&gt;

&lt;p&gt;The line costs less context in every session, the steps can't drift because there's one copy and it's code, and the same script works for you, for CI and, in a moment, for a hook. The best-practices page puts it this way: if your repo already has a check like this, the single most useful line in your &lt;code&gt;CLAUDE.md&lt;/code&gt; is the command that runs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Turn "never" rules into a PreToolUse hook
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;PreToolUse&lt;/code&gt; hook runs before a tool call and can block it. According to the &lt;a href="https://code.claude.com/docs/en/hooks-guide" rel="noopener noreferrer"&gt;hooks guide&lt;/a&gt;, Claude Code passes the tool call as JSON on the script's stdin. If the script exits with code 2, the call is blocked and whatever it wrote to stderr goes back to Claude as feedback, so it can adjust. Exit code 0 means no objection, and the normal permission flow applies.&lt;/p&gt;

&lt;p&gt;The guide includes an example that blocks edits to protected files such as &lt;code&gt;.env&lt;/code&gt;. Here's a variation for the migrations rule. It blocks edits to migration files that already exist, but still lets Claude create a new one, because "add a new migration" is exactly what you want it to do instead:&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;# .claude/hooks/protect-migrations.sh&lt;/span&gt;
&lt;span class="c"&gt;# Block edits to migration files that already exist. New migrations are allowed.&lt;/span&gt;
&lt;span class="nv"&gt;INPUT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;cat&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;FILE_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"$INPUT"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; jq -r &lt;span class="s1"&gt;'.tool_input.file_path // empty'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"$FILE_PATH"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; *migrations/* &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; -e &lt;span class="s2"&gt;"$FILE_PATH"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="o"&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;"Blocked: $FILE_PATH is an existing migration. Add a new migration file instead of editing this one."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;amp;&lt;/span&gt;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;Make it executable (&lt;code&gt;chmod +x .claude/hooks/protect-migrations.sh&lt;/code&gt;) and register it in &lt;code&gt;.claude/settings.json&lt;/code&gt;, using the same structure as the guide's example:&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;"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;"\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-migrations.sh"&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;h3&gt;
  
  
  Test the script before you trust it
&lt;/h3&gt;

&lt;p&gt;You don't need a Claude Code session to check the logic. Feed the script the same kind of JSON it will receive and look at the exit code. I did this in a small throwaway project at &lt;code&gt;/tmp/shop&lt;/code&gt; with one existing migration, then simulated three tool calls:&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="k"&gt;for&lt;/span&gt; call &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"Edit migrations/0007_add_orders.sql"&lt;/span&gt; &lt;span class="s2"&gt;"Write migrations/0008_add_refunds.sql"&lt;/span&gt; &lt;span class="s2"&gt;"Edit src/service.ts"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="nb"&gt;set&lt;/span&gt; -- &lt;span class="nv"&gt;$call&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"--- $1 $2"&lt;/span&gt;
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"{\"hook_event_name\":\"PreToolUse\",\"tool_name\":\"$1\",\"tool_input\":{\"file_path\":\"$PWD/$2\"}}"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; ./.claude/hooks/protect-migrations.sh
  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit code: $?"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;--- Edit migrations/0007_add_orders.sql
Blocked: /tmp/shop/migrations/0007_add_orders.sql is an existing migration. Add a new migration file instead of editing this one.
exit code: 2
--- Write migrations/0008_add_refunds.sql
exit code: 0
--- Edit src/service.ts
exit code: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The existing migration is blocked with a message Claude can act on; the new migration and the source file go through. This tests the script, not Claude Code itself. Inside a session, the guide suggests running &lt;code&gt;/hooks&lt;/code&gt; to confirm the hook is registered, then asking Claude to make the edit you want blocked.&lt;/p&gt;

&lt;h3&gt;
  
  
  One gap to know about
&lt;/h3&gt;

&lt;p&gt;A hook that matches &lt;code&gt;Edit|Write&lt;/code&gt; only sees those tools. The hooks guide points out that Claude can also change files by running shell commands, so a &lt;code&gt;sed -i&lt;/code&gt; through the Bash tool wouldn't pass through this script. If you need to catch every change, the guide suggests also matching &lt;code&gt;Bash&lt;/code&gt; and checking modified files with &lt;code&gt;git status --porcelain&lt;/code&gt;, or adding a &lt;code&gt;Stop&lt;/code&gt; hook that scans the working tree once per turn.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Turn "don't finish until" into a Stop hook
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;Stop&lt;/code&gt; hook runs whenever Claude tries to end its turn. If the hook exits with code 2, Claude is sent back to work, and the stderr output becomes the reason. The best-practices page shows this with a type check:&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;"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;"pnpm tsc --noEmit 1&amp;gt;&amp;amp;2 || exit 2"&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;Since you already have &lt;code&gt;scripts/check.sh&lt;/code&gt;, you can point the hook at it instead, so the rule lives in one place: &lt;code&gt;"command": "\"$CLAUDE_PROJECT_DIR\"/scripts/check.sh 1&amp;gt;&amp;amp;2 || exit 2"&lt;/code&gt;. The &lt;code&gt;1&amp;gt;&amp;amp;2&lt;/code&gt; matters, because it sends the tool output to stderr, where Claude will read it.&lt;/p&gt;

&lt;p&gt;Two things to know before you add one. Stop hooks fire whenever Claude finishes responding, not only when a task is complete, so a slow check runs often. And there's a safety cap: per the docs, if the hook blocks eight stops in a row with no tool call in between, Claude Code overrides the next block and ends the turn. A check that can never pass won't trap you in a loop, but it also won't get fixed by itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What stays in CLAUDE.md
&lt;/h2&gt;

&lt;p&gt;What's left should be things Claude can't see in the code: commands, conventions that need judgment, gotchas. One line per script, not one per step, and no rule written twice, once as a sentence and once as a hook. Don't overcorrect, either: the docs describe a hook as a tool for a rule with &lt;em&gt;zero&lt;/em&gt; exceptions. And hooks are code that runs on your machine, so review them like any script, especially in a repo you didn't write.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 10-minute checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Read every line in your &lt;code&gt;CLAUDE.md&lt;/code&gt; and tag it: guideline, procedure or never/always rule.&lt;/li&gt;
&lt;li&gt;Move each multi-step procedure into a script and replace it with one line that names the script.&lt;/li&gt;
&lt;li&gt;Pick the one or two never/always rules where a single miss really hurts.&lt;/li&gt;
&lt;li&gt;Write a &lt;code&gt;PreToolUse&lt;/code&gt; hook for "never" rules and a &lt;code&gt;Stop&lt;/code&gt; hook for "don't finish until" rules.&lt;/li&gt;
&lt;li&gt;Test each hook script by piping sample JSON into it and checking the exit code.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;/hooks&lt;/code&gt; in Claude Code to confirm the hook is registered.&lt;/li&gt;
&lt;li&gt;Delete the sentence version of any rule a hook now enforces.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;If you want the short version of what belongs in the file, the free &lt;a href="https://promptmasterstore.gumroad.com/l/claude-md-cheat-sheet?utm_source=devto" rel="noopener noreferrer"&gt;CLAUDE.md Quick-Start Cheat Sheet&lt;/a&gt; covers what to include, what to cut, the one-line test for every line, and a map of the configuration stack beyond &lt;code&gt;CLAUDE.md&lt;/code&gt; (slash commands, subagents, hooks, skills and plugins) with a simple framework for when to reach for each, in six pages.&lt;/p&gt;

&lt;p&gt;When you're ready to build the whole stack, &lt;a href="https://promptmasterstore.gumroad.com/l/claude-md-configuration-guide?utm_source=devto" rel="noopener noreferrer"&gt;CLAUDE.md: The Complete Claude Code Configuration Guide&lt;/a&gt; has chapters on hooks, subagents, skills, plugins, security and team governance, plus three real-world configuration walkthroughs, the nine most common mistakes and a 30-day plan to build your setup from scratch.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI and reviewed by the author. Claude Code behavior is described from Anthropic's official documentation (hooks guide, memory and best-practices pages). The hook script is the author's adaptation of the docs' protected-files example; the output shown is its real output when fed sample JSON in a throwaway project, not output from a Claude Code session. The &lt;code&gt;CLAUDE.md&lt;/code&gt; block and &lt;code&gt;check.sh&lt;/code&gt; are illustrative examples. PromptMaster guides are independent and not affiliated with or endorsed by Anthropic.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>devops</category>
    </item>
    <item>
      <title>Your RAG Chunks Are Cut Mid-Sentence: A Small Script to Find Them</title>
      <dc:creator>PromptMaster</dc:creator>
      <pubDate>Thu, 12 Nov 2026 08:39:07 +0000</pubDate>
      <link>https://dev.to/promptmaster/your-rag-chunks-are-cut-mid-sentence-a-small-script-to-find-them-3kco</link>
      <guid>https://dev.to/promptmaster/your-rag-chunks-are-cut-mid-sentence-a-small-script-to-find-them-3kco</guid>
      <description>&lt;p&gt;Here's a question a docs assistant should get right: "How long does the old API key keep working after I rotate it?" The docs page says it plainly: 24 hours. The assistant still answers vaguely, or not at all. The retriever looks fine, the prompt looks fine, and the page is definitely indexed.&lt;/p&gt;

&lt;p&gt;Then you print the chunks, and the sentence about 24 hours starts with "y keeps working". The word "Rotate" is in a different chunk. So is the heading. The answer is in your index, but no single chunk contains both the question's topic and the fact.&lt;/p&gt;

&lt;p&gt;In an &lt;a href="https://dev.to/promptmaster/bad-rag-answers-check-retrieval-before-you-touch-the-prompt-55lg"&gt;earlier article&lt;/a&gt; I covered the debug order for bad RAG answers: check retrieval before you touch the prompt. This one zooms in on the first stage of that pipeline, chunking, and gives you a small script that shows where your cuts land before you spend time on embeddings, rerankers or prompts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why chunking deserves its own check
&lt;/h2&gt;

&lt;p&gt;A retrieval-augmented generation (RAG) system splits documents into chunks, embeds each one, and later retrieves the chunks closest to a question. Everything downstream works on those chunks. A retriever can only return text that exists as a chunk, and the model can only answer from the chunks it receives.&lt;/p&gt;

&lt;p&gt;That makes chunk boundaries a quiet, upstream source of errors. Three problems show up again and again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cut mid-sentence.&lt;/strong&gt; A fixed character count doesn't care about sentences, so a fact gets split across two chunks and neither half is complete.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lost heading.&lt;/strong&gt; The text says "click Rotate, then confirm", but the heading that said &lt;em&gt;which&lt;/em&gt; key and &lt;em&gt;which&lt;/em&gt; screen landed in the previous chunk. Anthropic's write-up on &lt;a href="https://www.anthropic.com/news/contextual-retrieval" rel="noopener noreferrer"&gt;Contextual Retrieval&lt;/a&gt; describes this problem directly: individual chunks can lack the context of the document they came from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Too small or too large.&lt;/strong&gt; A chunk of four words carries no meaning; a chunk covering five sections matches everything a little and nothing well.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these produce an error message. You only see them if you look at the chunks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The sample document
&lt;/h2&gt;

&lt;p&gt;To make this concrete, here's a short, made-up docs page. It's an example written for this article, not real product documentation:&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="gu"&gt;# API keys
&lt;/span&gt;
&lt;span class="gu"&gt;## Rotate a key
&lt;/span&gt;
Open Settings, then API keys. Find the key you want to replace and click Rotate. The old key keeps working for 24 hours so you can update your services.

&lt;span class="gu"&gt;## Revoke a key
&lt;/span&gt;
Click Revoke next to the key. Revoking is immediate: every request that uses the key fails from that moment. You cannot undo it.

&lt;span class="gu"&gt;## Limits
&lt;/span&gt;
Each project can have up to 5 active keys. Rotating a key does not count as a new key.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A small chunk check in Python
&lt;/h2&gt;

&lt;p&gt;The script below splits the same text two ways and flags each chunk. It's a sketch for inspection, not a production splitter, and it uses only the Python standard library.&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="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fixed_size&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;size&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="s2"&gt;"""Cut every `size` characters, no overlap."""&lt;/span&gt;
    &lt;span class="k"&gt;return&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;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;size&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;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&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="nb"&gt;len&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&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;by_heading&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="p"&gt;:&lt;/span&gt;
    &lt;span class="s2"&gt;"""Split on markdown headings; keep the heading path with each chunk."""&lt;/span&gt;
    &lt;span class="n"&gt;chunks&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="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="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;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flush&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;content&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;body&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;heading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;" &amp;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;path&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&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;k&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;sorted&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="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;chunks&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="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"[{heading}] {content}"&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;clear&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;line&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&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="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="k"&gt;match&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;"^(#+)\s+(.*)"&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;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&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="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="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;in&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;items&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;k&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;level&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="n"&gt;level&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&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;elif&lt;/span&gt; &lt;span class="n"&gt;line&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="n"&gt;body&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="n"&gt;line&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;flush&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="n"&gt;chunks&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_last&lt;/span&gt;&lt;span class="p"&gt;)&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;chunk&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="n"&gt;out&lt;/span&gt; &lt;span class="o"&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="n"&gt;text&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="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&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;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"NO HEADING"&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;is_last&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;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endswith&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="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="n"&gt;out&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="s2"&gt;"CUT MID-SENTENCE"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&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="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="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&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;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"TOO SMALL"&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;out&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;report&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;chunks&lt;/span&gt;&lt;span class="p"&gt;)&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;f&lt;/span&gt;&lt;span class="s2"&gt;"== {name}: {len(chunks)} chunks"&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;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&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="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;one_line&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;c&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="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;f&lt;/span&gt;&lt;span class="s2"&gt;"C{i} ({len(c.split())} words) {f or ['ok']}"&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;f&lt;/span&gt;&lt;span class="s2"&gt;"   starts: {one_line[:38]!r}"&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;f&lt;/span&gt;&lt;span class="s2"&gt;"   ends:   {one_line[-38:]!r}"&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="nb"&gt;open&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;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="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"fixed-size, 120 chars"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;fixed_size&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="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"by heading"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;by_heading&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="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flags are deliberately simple heuristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NO HEADING&lt;/strong&gt;: the chunk doesn't start with a heading or a heading path, so it may not make sense on its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CUT MID-SENTENCE&lt;/strong&gt;: the chunk doesn't end with sentence punctuation (the last chunk is exempt).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TOO SMALL&lt;/strong&gt;: fewer than 8 words. Pick a threshold that fits your content.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it printed
&lt;/h2&gt;

&lt;p&gt;I ran it with &lt;code&gt;python3 chunk_check.py sample.md&lt;/code&gt; (Python 3.13.5). This is the actual output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;== fixed-size, 120 chars: 4 chunks
C1 (25 words) ['CUT MID-SENTENCE']
   starts: '# API keys ## Rotate a key Open Settin'
   ends:   'o replace and click Rotate. The old ke'
C2 (24 words) ['NO HEADING', 'CUT MID-SENTENCE']
   starts: 'y keeps working for 24 hours so you ca'
   ends:   'ick Revoke next to the key. Revoking i'
C3 (22 words) ['NO HEADING', 'CUT MID-SENTENCE']
   starts: 's immediate: every request that uses t'
   ends:   'undo it. ## Limits Each project can ha'
C4 (16 words) ['NO HEADING']
   starts: 've up to 5 active keys. Rotating a key'
   ends:   'ing a key does not count as a new key.'

== by heading: 3 chunks
C1 (35 words) ['ok']
   starts: '[API keys &amp;gt; Rotate a key] Open Setting'
   ends:   'hours so you can update your services.'
C2 (29 words) ['ok']
   starts: '[API keys &amp;gt; Revoke a key] Click Revoke'
   ends:   ' from that moment. You cannot undo it.'
C3 (23 words) ['ok']
   starts: '[API keys &amp;gt; Limits] Each project can h'
   ends:   'ing a key does not count as a new key.'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the fixed-size result the way your retriever sees it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The "24 hours" fact sits in &lt;strong&gt;C2&lt;/strong&gt;, which starts with "y keeps working" and goes on to talk about &lt;em&gt;revoking&lt;/em&gt;. It has no "Rotate a key" heading. A question about rotation now has to match a chunk that is mostly about revocation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C3&lt;/strong&gt; mixes the end of the revoke section with the start of the limits section.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;C4&lt;/strong&gt; begins with "ve up to 5 active keys", and the "Limits" heading is at the end of C3.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The heading-based split gives three chunks, one per section, each starting with its heading path. The rotation question and the 24-hour fact are now in the same chunk, with the section title attached.&lt;/p&gt;

&lt;p&gt;This doesn't mean fixed-size chunking is always wrong, or that splitting on headings is always right. A section can be far too long to embed as one chunk, and plenty of documents have no headings at all. The point is narrower: &lt;strong&gt;look at where the cuts land on your own documents before you tune anything downstream.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn it into a habit
&lt;/h2&gt;

&lt;p&gt;A one-off look is useful. A repeatable check is better. A few ways to fold this into your pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run the check on a real sample.&lt;/strong&gt; Take 10 to 20 representative documents, including the messy ones: long tables, FAQs, changelogs, PDFs converted to text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count the flags, then read the flagged chunks.&lt;/strong&gt; A high share of mid-sentence cuts tells you to change strategy; a handful of flagged chunks tells you which documents need special handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the heading path with every chunk.&lt;/strong&gt; Even if you keep a size limit, prepending the section title gives each chunk a fighting chance to stand on its own.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split long sections on paragraphs, not characters.&lt;/strong&gt; Only fall back to a hard size cut when a single paragraph is too long.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-run your retrieval test after any chunking change.&lt;/strong&gt; Chunking changes what retrieval can find. A small recall@k test, like the one in the &lt;a href="https://dev.to/promptmaster/bad-rag-answers-check-retrieval-before-you-touch-the-prompt-55lg"&gt;earlier article&lt;/a&gt;, tells you whether the change actually helped on your questions.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A checklist before you touch embeddings or prompts
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Print a sample of chunks and read them like a stranger would.&lt;/li&gt;
&lt;li&gt;Check for mid-sentence cuts, missing headings, and chunks that are too small or too large.&lt;/li&gt;
&lt;li&gt;Make sure the facts users ask about sit in the same chunk as the topic they ask about.&lt;/li&gt;
&lt;li&gt;Attach the section title (or heading path) to each chunk.&lt;/li&gt;
&lt;li&gt;Change one chunking setting at a time and re-run your retrieval test after each change.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;If you'd rather see your cuts than read printouts, the free &lt;a href="https://promptmasterstore.gumroad.com/l/ragtoolfree?utm_source=devto" rel="noopener noreferrer"&gt;RAG Chunk Visualizer &amp;amp; Cost Calculator demo&lt;/a&gt; does this in the browser: paste a document, pick structure-aware or fixed-size chunking, and see each chunk as a colored block with a token estimate and quality badges for chunks that are too small, too large, or cut mid-sentence. Your text stays on your device.&lt;/p&gt;

&lt;p&gt;For the mental model behind it, the free &lt;a href="https://promptmasterstore.gumroad.com/l/ragfree?utm_source=devto" rel="noopener noreferrer"&gt;RAG Quick-Start&lt;/a&gt; covers the five-step pipeline (chunk, embed, retrieve, rerank, generate) and the three decisions that move quality most, starting with structure-aware chunking, in 6 pages.&lt;/p&gt;

&lt;p&gt;When you're ready for the whole territory, &lt;a href="https://promptmasterstore.gumroad.com/l/rag?utm_source=devto" rel="noopener noreferrer"&gt;The Complete Guide to Retrieval-Augmented Generation&lt;/a&gt; spends 46 pages on every stage, including chunking that respects your documents' structure, hybrid search, evaluating retrieval and generation separately, and a full worked example.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI and reviewed by the author. The sample document is made up for this article; the script is the author's own sketch and the output shown is its real output on that sample only.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>rag</category>
      <category>python</category>
      <category>llm</category>
    </item>
    <item>
      <title>What "Don't Ask Again" Saves in Claude Code, and How to Audit It</title>
      <dc:creator>PromptMaster</dc:creator>
      <pubDate>Wed, 11 Nov 2026 08:12:03 +0000</pubDate>
      <link>https://dev.to/promptmaster/what-dont-ask-again-saves-in-claude-code-and-how-to-audit-it-1b7</link>
      <guid>https://dev.to/promptmaster/what-dont-ask-again-saves-in-claude-code-and-how-to-audit-it-1b7</guid>
      <description>&lt;p&gt;You're deep in a Claude Code session, the same permission prompt shows up for the fifth time, and you pick &lt;strong&gt;"Yes, and don't ask again"&lt;/strong&gt;. It's the right call most of the time. But that click doesn't just silence one prompt. For some tools it writes a rule to a file, and that rule keeps working in every future session in that repository.&lt;/p&gt;

&lt;p&gt;After a few weeks, that file is a list of things Claude Code may do without asking you, and you didn't write any of it on purpose. This article covers what the click actually saves, where it goes, and a short audit to run before the list gets out of hand. Everything about Claude Code's behavior here comes from Anthropic's official &lt;a href="https://code.claude.com/docs/en/permissions" rel="noopener noreferrer"&gt;permissions&lt;/a&gt; and &lt;a href="https://code.claude.com/docs/en/settings" rel="noopener noreferrer"&gt;settings&lt;/a&gt; docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the click saves, by tool
&lt;/h2&gt;

&lt;p&gt;The docs list what "Yes, and don't ask again" means for each kind of tool:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool type&lt;/th&gt;
&lt;th&gt;Asks by default?&lt;/th&gt;
&lt;th&gt;"Don't ask again" lasts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bash commands&lt;/td&gt;
&lt;td&gt;Yes, except a built-in set of read-only commands&lt;/td&gt;
&lt;td&gt;Permanently, per repository and command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File edits and writes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Until the session ends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebFetch&lt;/td&gt;
&lt;td&gt;Yes, except some preapproved documentation domains&lt;/td&gt;
&lt;td&gt;Permanently, per repository and domain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WebSearch&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Permanently, per repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File reads, Grep&lt;/td&gt;
&lt;td&gt;No, inside your working directories&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So file-edit approvals disappear when you close the session, but shell commands, fetch domains and web search stick.&lt;/p&gt;

&lt;p&gt;The prompt also tells you what it will save. The docs' example shows a Bash prompt for &lt;code&gt;npm test&lt;/code&gt; offering "Yes, and don't ask again for: npm test *". That trailing &lt;code&gt;*&lt;/code&gt; matters: the saved rule covers &lt;code&gt;npm test&lt;/code&gt; with any arguments, not only the exact command you just saw.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it goes
&lt;/h2&gt;

&lt;p&gt;Permanent approvals are saved as &lt;code&gt;allow&lt;/code&gt; rules in &lt;code&gt;.claude/settings.local.json&lt;/code&gt; at the root of the git repository. A few details from the docs that are easy to miss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It covers the whole repository.&lt;/strong&gt; Start Claude Code in a subdirectory and the rule still lands at the repo root and applies everywhere in the repo, including worktrees, which resolve to the main checkout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It stays out of git.&lt;/strong&gt; The first time Claude Code writes the file, it adds &lt;code&gt;**/.claude/settings.local.json&lt;/code&gt; to your global git excludes. Nobody reviews it in a pull request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It skips the trust step.&lt;/strong&gt; Allow rules in the committed &lt;code&gt;.claude/settings.json&lt;/code&gt; wait until you accept the workspace trust dialog. Your local file is treated as yours, so its allow rules apply right away while it stays untracked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compound commands split.&lt;/strong&gt; Approving &lt;code&gt;git status &amp;amp;&amp;amp; npm test&lt;/code&gt; saves a rule for &lt;code&gt;npm test&lt;/code&gt; alone, and up to 5 rules can be saved from a single compound command.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is a flaw. It's exactly what you want for &lt;code&gt;npm test&lt;/code&gt;. It just means the file deserves a look now and then.&lt;/p&gt;

&lt;h2&gt;
  
  
  The audit: three steps
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Look at what's there
&lt;/h3&gt;

&lt;p&gt;Run &lt;code&gt;/permissions&lt;/code&gt; inside Claude Code. The dialog lists every permission rule and the settings file it comes from, so you see local, project and user rules together. You can add or remove rules from the same dialog.&lt;/p&gt;

&lt;p&gt;Or open the file directly. Here is an example of what a local file might look like after a few weeks (an illustrative file, not from a real 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="nl"&gt;"permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;"allow"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"Bash(npm test *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Bash(npm run lint)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Bash(git *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Bash(npx *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"Bash(docker exec *)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"WebFetch(domain:docs.python.org)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s2"&gt;"WebSearch"&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;h3&gt;
  
  
  2. Flag the rules that allow more than you meant
&lt;/h3&gt;

&lt;p&gt;Two shapes deserve attention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A wildcard right after the program name.&lt;/strong&gt; The docs are explicit: everything before the first &lt;code&gt;*&lt;/code&gt; is what limits the rule. &lt;code&gt;Bash(git log *)&lt;/code&gt; allows only &lt;code&gt;git log&lt;/code&gt; commands. &lt;code&gt;Bash(git *)&lt;/code&gt; allows every git command, including &lt;code&gt;git push&lt;/code&gt;. If you meant "let it read history", the narrower rule says so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment runners.&lt;/strong&gt; Claude Code strips a small fixed set of wrappers before matching (&lt;code&gt;timeout&lt;/code&gt;, &lt;code&gt;time&lt;/code&gt;, &lt;code&gt;nice&lt;/code&gt;, &lt;code&gt;nohup&lt;/code&gt;, &lt;code&gt;stdbuf&lt;/code&gt;, plus a few shell builtins), but runners like &lt;code&gt;npx&lt;/code&gt;, &lt;code&gt;docker exec&lt;/code&gt;, &lt;code&gt;devbox run&lt;/code&gt;, &lt;code&gt;mise exec&lt;/code&gt; and &lt;code&gt;direnv exec&lt;/code&gt; are not on that list. Because they execute their arguments as a command, the docs warn that &lt;code&gt;Bash(devbox run *)&lt;/code&gt; also matches &lt;code&gt;devbox run rm -rf .&lt;/code&gt;. The same logic applies to &lt;code&gt;Bash(npx *)&lt;/code&gt; and &lt;code&gt;Bash(docker exec *)&lt;/code&gt;. The recommended fix is one specific rule per inner command, such as &lt;code&gt;Bash(devbox run npm test)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you have several repositories, a tiny script helps. This is my own sketch, not an official tool. It reads the allow list with &lt;code&gt;jq&lt;/code&gt; and labels each rule:&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;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# Flag allow rules worth a second look in a Claude Code settings file.&lt;/span&gt;
&lt;span class="nv"&gt;f&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"${1:-.claude/settings.local.json}"&lt;/span&gt;
jq -r &lt;span class="s1"&gt;'.permissions.allow[]?'&lt;/span&gt; &lt;span class="s2"&gt;"$f"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; -r rule&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"$rule"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Bash"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="s2"&gt;"$rule"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Bash(*)"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="o"&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;"ALL      $rule"&lt;/span&gt;
  &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"$rule"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^Bash\&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;npx&lt;span class="o"&gt;|&lt;/span&gt;docker\ &lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;devbox\ run&lt;span class="o"&gt;|&lt;/span&gt;mise\ &lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;direnv\ &lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;\  &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="o"&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;"RUNNER   $rule"&lt;/span&gt;
  &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"$rule"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^Bash\&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;^\ &lt;span class="o"&gt;]&lt;/span&gt;+&lt;span class="o"&gt;(&lt;/span&gt;\ \*&lt;span class="o"&gt;|&lt;/span&gt;:\*&lt;span class="o"&gt;)&lt;/span&gt;\&lt;span class="o"&gt;)&lt;/span&gt;$ &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="o"&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;"BROAD    $rule"&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ok       $rule"&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run against the example file above (bash 5.2, jq 1.7), it prints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ok       Bash(npm test *)
ok       Bash(npm run lint)
BROAD    Bash(git *)
RUNNER   Bash(npx *)
RUNNER   Bash(docker exec *)
ok       WebFetch(domain:docs.python.org)
ok       WebSearch
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script only knows the patterns you give it, so treat "ok" as "not flagged", not "safe".&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Trim, then add guardrails
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Narrow the broad rules.&lt;/strong&gt; Replace &lt;code&gt;Bash(git *)&lt;/code&gt; with the subcommands you actually approve, like &lt;code&gt;Bash(git log *)&lt;/code&gt; and &lt;code&gt;Bash(git diff *)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replace runner wildcards&lt;/strong&gt; with one exact rule per inner command.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;ask&lt;/code&gt; rules for the commands you always want to see.&lt;/strong&gt; Rules are evaluated deny, then ask, then allow, and the first match wins. Specificity doesn't change the order, so an &lt;code&gt;ask&lt;/code&gt; rule like &lt;code&gt;Bash(git push *)&lt;/code&gt; still prompts even if some broader allow rule also matches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add &lt;code&gt;deny&lt;/code&gt; rules for secrets.&lt;/strong&gt; &lt;code&gt;Read(./.env)&lt;/code&gt; or &lt;code&gt;Read(./secrets/**)&lt;/code&gt; blocks Claude's file tools from those paths, and a Read deny also blocks Edit and Write on the same path.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put the ask and deny rules your whole team needs in the committed &lt;code&gt;.claude/settings.json&lt;/code&gt;. Deny and ask rules only restrict, so they don't wait for workspace trust. A deny at any level blocks an allow at any other level.&lt;/p&gt;

&lt;h2&gt;
  
  
  Know where rules stop
&lt;/h2&gt;

&lt;p&gt;Two limits from the docs are worth keeping in mind while you trim.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bash rules match command text, not the program.&lt;/strong&gt; A deny rule &lt;code&gt;Bash(curl *)&lt;/code&gt; stops &lt;code&gt;curl https://example.com&lt;/code&gt; but not &lt;code&gt;/usr/bin/curl https://example.com&lt;/code&gt; or &lt;code&gt;sh -c 'curl …'&lt;/code&gt;. Likewise, Read deny rules cover Claude's file tools and the file commands Claude Code recognizes in Bash, like &lt;code&gt;cat&lt;/code&gt; or &lt;code&gt;head&lt;/code&gt;, but not a Python or Node script that opens the file itself. When a restriction must hold, the docs point to &lt;a href="https://code.claude.com/docs/en/sandboxing" rel="noopener noreferrer"&gt;sandboxing&lt;/a&gt; for filesystem and network isolation, or a PreToolUse hook that inspects the full command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rules are enforced by Claude Code, not by the model.&lt;/strong&gt; Writing "never push" in &lt;code&gt;CLAUDE.md&lt;/code&gt; shapes what Claude tries, but it doesn't change what Claude Code allows. Only permission rules, modes and hooks do that.&lt;/p&gt;

&lt;p&gt;One more thing that confuses people: if you chose "don't ask again" and still get prompted, the settings docs explain why. Your choice saved an allow rule to your local file, and that allow rule doesn't outrank an &lt;code&gt;ask&lt;/code&gt; rule from a project or managed settings file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Run &lt;code&gt;/permissions&lt;/code&gt; once a week, or after a long session full of prompts&lt;/li&gt;
&lt;li&gt;[ ] Read the "don't ask again for:" line before choosing it, especially the trailing &lt;code&gt;*&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] No &lt;code&gt;Bash(&amp;lt;program&amp;gt; *)&lt;/code&gt; rules for programs with dangerous subcommands&lt;/li&gt;
&lt;li&gt;[ ] No wildcard rules on &lt;code&gt;npx&lt;/code&gt;, &lt;code&gt;docker exec&lt;/code&gt; or other runners&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;ask&lt;/code&gt; rules for pushes, deploys and anything you always want to see&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;deny&lt;/code&gt; rules for &lt;code&gt;.env&lt;/code&gt; and secret folders, committed for the team&lt;/li&gt;
&lt;li&gt;[ ] Sandboxing or a hook where a rule must actually hold&lt;/li&gt;
&lt;li&gt;[ ] Nothing important enforced only by a line in CLAUDE.md&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;If you came to Claude Code from Codex, I covered permission basics alongside six other day-one settings in &lt;a href="https://dev.to/promptmaster/coming-to-claude-code-from-codex-7-things-to-set-up-on-day-one-1n14"&gt;Coming to Claude Code from Codex? 7 Things to Set Up on Day One&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For the full picture, &lt;a href="https://promptmasterstore.gumroad.com/l/claude-code-complete-guide?utm_source=devto" rel="noopener noreferrer"&gt;Claude Code: The Complete Guide&lt;/a&gt; is a 45-page guide with 22 chapters: when to use Claude Code and when to use Cursor, installation on macOS, Linux and Windows via WSL2, the workflows where it earns its place, three annotated sessions, cost management, headless mode and CI, and a chapter on team workflow and security. If you'd rather start small, the free &lt;a href="https://promptmasterstore.gumroad.com/l/claude-code-cheat-sheet?utm_source=devto" rel="noopener noreferrer"&gt;Claude Code Quick-Start Cheat Sheet&lt;/a&gt; covers setup, essential commands and the Cursor vs Claude Code decision in 5 pages.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI and reviewed by the author. Claude Code behavior is taken from Anthropic's official permissions and settings documentation as of 11 November 2026; the audit script is the author's own sketch and was run only on the example file shown. PromptMaster guides are independent and not affiliated with or endorsed by Anthropic.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>security</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Your Agent Said "Done". Spec Kit's Converge Step Checks the Code Instead</title>
      <dc:creator>PromptMaster</dc:creator>
      <pubDate>Tue, 10 Nov 2026 14:26:40 +0000</pubDate>
      <link>https://dev.to/promptmaster/your-agent-said-done-spec-kits-converge-step-checks-the-code-instead-543j</link>
      <guid>https://dev.to/promptmaster/your-agent-said-done-spec-kits-converge-step-checks-the-code-instead-543j</guid>
      <description>&lt;p&gt;"Done" is the most expensive word a coding agent says.&lt;/p&gt;

&lt;p&gt;You ask for a feature, the agent works for a while, and then it reports that everything is implemented. Sometimes it is. Sometimes one user story is half-built, a requirement was quietly reinterpreted, or a task was skipped because the agent decided it wasn't needed. The agent isn't lying. It's grading its own homework against a goal that was never written down.&lt;/p&gt;

&lt;p&gt;The fix isn't a better prompt at the end. It's defining "done" before the agent starts, then checking the code against that definition afterwards. That's what &lt;a href="https://github.com/github/spec-kit" rel="noopener noreferrer"&gt;Spec Kit&lt;/a&gt;, GitHub's open-source toolkit for Spec-Driven Development (SDD), is built around. This article walks through its workflow with one question in mind: at which step does "done" get defined, and at which step does it get checked?&lt;/p&gt;

&lt;h2&gt;
  
  
  What Spec Kit is, briefly
&lt;/h2&gt;

&lt;p&gt;Spec Kit gives your coding agent a set of slash commands (or skills, depending on the agent) that turn a feature request into a chain of files: a project constitution, a spec, a plan, a task list, and then code. You review each file before moving on.&lt;/p&gt;

&lt;p&gt;The README describes the core idea in one line: "Define what to build and why, then turn the specification into a plan, tasks, and working code." The point is that the agent implements against something you've read and agreed to, not against a sentence it interpreted on its own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install it in two commands
&lt;/h2&gt;

&lt;p&gt;From the official README, you need Python 3.11+, &lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;uv&lt;/a&gt; and a supported AI coding agent on Linux, macOS or Windows. Then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv tool install specify-cli
specify init my-project --integration copilot
&lt;span class="nb"&gt;cd&lt;/span&gt; my-project
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example uses GitHub Copilot. Spec Kit works with other agents too; you pick the integration at init time. One thing that trips people up: the command syntax depends on the agent. The docs write commands as &lt;code&gt;/speckit.specify&lt;/code&gt;, Copilot's default skills mode uses &lt;code&gt;/speckit-specify&lt;/code&gt;, and some agents such as Codex use &lt;code&gt;$speckit-specify&lt;/code&gt;. Check the &lt;a href="https://github.github.io/spec-kit/integrations/cli.html#command-invocation" rel="noopener noreferrer"&gt;invocation syntax&lt;/a&gt; for yours.&lt;/p&gt;

&lt;p&gt;After that, you launch your agent in the project folder and run the commands in the agent's chat, not in your terminal, one at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  The nine commands, grouped by what they do to "done"
&lt;/h2&gt;

&lt;p&gt;The full order in the &lt;a href="https://github.github.io/spec-kit/reference/agentic-sdd.html" rel="noopener noreferrer"&gt;command reference&lt;/a&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;constitution -&amp;gt; specify -&amp;gt; clarify -&amp;gt; plan -&amp;gt; checklist -&amp;gt; tasks -&amp;gt; analyze -&amp;gt; implement -&amp;gt; converge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only &lt;code&gt;specify&lt;/code&gt; is strictly required before &lt;code&gt;plan&lt;/code&gt;. &lt;code&gt;clarify&lt;/code&gt;, &lt;code&gt;checklist&lt;/code&gt; and &lt;code&gt;analyze&lt;/code&gt; are optional quality gates for anything with meaningful ambiguity. Here's how I group them.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Define "done": constitution and specify
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/speckit.constitution&lt;/code&gt; sets the project's standing principles, the rules every later phase is evaluated against. You run it once and update it when your principles change. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/speckit.constitution Create principles focused on code quality, testing standards, user experience consistency, and performance requirements.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/speckit.specify&lt;/code&gt; turns your plain-language description into the feature spec. The docs are clear that this step is about the &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;why&lt;/em&gt;, the user-facing behavior and goals, not the tech stack. That separation matters: if the spec says "albums are never nested inside other albums", that's a requirement the code can later be checked against. If it just says "use SQLite", there's nothing to verify about the user's experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Tighten "done": clarify and checklist
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/speckit.clarify&lt;/code&gt; asks up to five targeted questions about underspecified parts of the spec and writes your answers back into &lt;code&gt;spec.md&lt;/code&gt;. You can run it several times, each time on a different area, and pass a focus:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/speckit.clarify Focus on the task card behavior: status changes, comment limits, and who can be assigned.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/speckit.checklist&lt;/code&gt; is the step I'd skip least. The docs call it "unit tests for your requirements". It doesn't test code; it checks whether the spec itself is complete, clear and consistent, with questions like "Is behavior specified for a deleted assigned user?"&lt;/p&gt;

&lt;p&gt;There's a detail here worth knowing. In a custom checklist, a checked box means a reviewer decided the requirement is good enough. It does not mean the work is done. The docs say the agent must not silently self-approve these checklists, and that &lt;code&gt;/speckit.implement&lt;/code&gt; reads the checkbox state as a gate: it counts unchecked items and asks before proceeding if any remain. In other words, a human stays in the loop exactly where the definition of "done" is still soft.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Plan the work: plan and tasks
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/speckit.plan&lt;/code&gt; is where the tech stack and architecture go:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/speckit.plan Use Vite with vanilla JavaScript. Keep images local and store metadata in SQLite.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/speckit.tasks&lt;/code&gt; breaks the plan into a dependency-ordered &lt;code&gt;tasks.md&lt;/code&gt;: a Setup phase, a Foundational phase for blocking prerequisites, one phase per user story in priority order, and a final Polish phase. Tasks that can run in parallel are marked.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Check "done" before any code: analyze
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/speckit.analyze&lt;/code&gt; is a read-only consistency check across &lt;code&gt;spec.md&lt;/code&gt;, &lt;code&gt;plan.md&lt;/code&gt; and &lt;code&gt;tasks.md&lt;/code&gt;. It reports conflicts, gaps and ambiguities, for example a task with no matching requirement, or a plan choice that contradicts the spec. It never edits files.&lt;/p&gt;

&lt;p&gt;This is the cheapest moment to catch a mismatch. If it finds a requirement problem, you go back to &lt;code&gt;specify&lt;/code&gt; or &lt;code&gt;clarify&lt;/code&gt;; a design problem goes back to &lt;code&gt;plan&lt;/code&gt;; a task problem means regenerating tasks. Then you re-run &lt;code&gt;analyze&lt;/code&gt; until it comes back clean.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Build, then check "done" against the code: implement and converge
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/speckit.implement&lt;/code&gt; executes the tasks in dependency order. For bigger features, the docs recommend working in stages so you don't overload the agent's context, for example "Implement only the Setup and Foundational phases… Stop before the user-story features", then validating before you continue.&lt;/p&gt;

&lt;p&gt;And then comes the step that answers the opening problem. &lt;code&gt;/speckit.converge&lt;/code&gt; assesses the codebase against the spec, plan and tasks to confirm nothing was missed. It's append-only: it never edits or deletes code, and the only thing it can write is new tasks in &lt;code&gt;tasks.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It ends in one of two ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Converged.&lt;/strong&gt; No gaps found, &lt;code&gt;tasks.md&lt;/code&gt; is left unchanged, and you can move on to review or a pull request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tasks appended.&lt;/strong&gt; Gaps were found and added under a Convergence section in &lt;code&gt;tasks.md&lt;/code&gt;. You run &lt;code&gt;/speckit.implement&lt;/code&gt; again, then &lt;code&gt;/speckit.converge&lt;/code&gt; again, and repeat until it reports converged.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's the shift. "Done" is no longer the agent's opinion at the end of a long session. It's a separate pass that compares the code with documents you reviewed, and when something's missing, the result is a task, not a reassuring summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lean path or full path?
&lt;/h2&gt;

&lt;p&gt;You don't need all nine commands every time. The README's minimal flow is six steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/speckit-constitution Create principles focused on code quality, testing, and maintainability.
/speckit-specify Build a photo organizer with albums grouped by date and a tile preview of each album.
/speckit-plan Use Vite with vanilla JavaScript. Keep images local and store metadata in SQLite.
/speckit-tasks
/speckit-implement
/speckit-converge
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My rule of thumb: use the lean path for small, well-understood features, and add &lt;code&gt;clarify&lt;/code&gt;, &lt;code&gt;checklist&lt;/code&gt; and &lt;code&gt;analyze&lt;/code&gt; when the feature has rules you could argue about (permissions, edge cases, anything touching data). Whichever path you take, keep &lt;code&gt;converge&lt;/code&gt; at the end. It's the step that turns "the agent says it's done" into "the code matches what we agreed".&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond features
&lt;/h2&gt;

&lt;p&gt;The README also lists two bundled extensions for other kinds of work, each with a clear final verdict:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bug fixing&lt;/strong&gt; (&lt;code&gt;specify extension add bug&lt;/code&gt;): assess the cause, fix, then test. The final verdict is &lt;code&gt;verified&lt;/code&gt;, &lt;code&gt;partial&lt;/code&gt; or &lt;code&gt;failed&lt;/code&gt;, and only &lt;code&gt;verified&lt;/code&gt; confirms the fix.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idea assessment&lt;/strong&gt; (&lt;code&gt;specify extension add assess&lt;/code&gt;): ends with &lt;code&gt;go&lt;/code&gt;, &lt;code&gt;needs-clarification&lt;/code&gt; or &lt;code&gt;kill&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same idea in both: the agent's work ends with an explicit, recorded result you can check, not just a message that it's finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Constitution written once, updated when principles change&lt;/li&gt;
&lt;li&gt;[ ] Spec describes behavior and goals, not the tech stack&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;clarify&lt;/code&gt; run on every area you could argue about&lt;/li&gt;
&lt;li&gt;[ ] Custom checklist reviewed by a person, never self-approved by the agent&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;analyze&lt;/code&gt; comes back clean before implementing&lt;/li&gt;
&lt;li&gt;[ ] Large features implemented in stages, each one validated&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;converge&lt;/code&gt; repeated until it reports Converged&lt;/li&gt;
&lt;li&gt;[ ] A human review or PR still happens after convergence&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;If you want the whole workflow on a few pages, start with the free &lt;a href="https://promptmasterstore.gumroad.com/l/speckitfree?utm_source=devto" rel="noopener noreferrer"&gt;Spec Kit Quick-Start&lt;/a&gt;: what SDD is, installing the Specify CLI, the nine-command workflow as a quick reference, lean vs production paths, and a compact first-run walkthrough.&lt;/p&gt;

&lt;p&gt;When you're ready to go deeper, &lt;a href="https://promptmasterstore.gumroad.com/l/speckit?utm_source=devto" rel="noopener noreferrer"&gt;Spec Kit: The Complete Guide&lt;/a&gt; has a chapter on every command, extensions, presets and bundles, brownfield and legacy projects, a 30-day adoption plan, common pitfalls, Spec Kit for teams, and a full worked example through all nine phases.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI and reviewed by the author. Commands and behaviors are taken from the official Spec Kit README and command reference (latest release v1.3.3 at the time of writing). PromptMaster guides are independent and not affiliated with or endorsed by GitHub.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>productivity</category>
      <category>github</category>
    </item>
    <item>
      <title>Your Agent's Memory Needs a Scope: What to Keep, Where, and When to Forget</title>
      <dc:creator>PromptMaster</dc:creator>
      <pubDate>Mon, 09 Nov 2026 08:35:46 +0000</pubDate>
      <link>https://dev.to/promptmaster/your-agents-memory-needs-a-scope-what-to-keep-where-and-when-to-forget-3hfh</link>
      <guid>https://dev.to/promptmaster/your-agents-memory-needs-a-scope-what-to-keep-where-and-when-to-forget-3hfh</guid>
      <description>&lt;p&gt;Most agent memory problems I see aren't about storage. They're about scope.&lt;/p&gt;

&lt;p&gt;A preference that belongs to you ends up in a file the whole team shares. A temporary rule ("don't deploy this week") gets saved as if it were permanent. A note about the task you're working on right now is still being loaded three weeks later. The agent isn't forgetting. It's remembering the right thing in the wrong place, for the wrong amount of time.&lt;/p&gt;

&lt;p&gt;In an earlier article I covered &lt;a href="https://dev.to/promptmaster/why-your-ai-agent-forgets-everything-and-the-4-kinds-of-memory-that-fix-it-5553"&gt;the four kinds of agent memory&lt;/a&gt;: working, episodic, semantic and procedural. This one is about a different question you have to answer for every single memory: &lt;strong&gt;who is it for, and how long should it live?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Three scopes cover most cases
&lt;/h2&gt;

&lt;p&gt;You can slice scope many ways, but three levels handle almost everything a coding agent or assistant needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Session:&lt;/strong&gt; true only for the current task. "We're fixing the login redirect." "The test database is already seeded." Useful for an hour, noise tomorrow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project:&lt;/strong&gt; true for this repository or this product, for everyone who works on it. "We use pnpm." "Deploys go through the staging branch." "Never edit generated files in &lt;code&gt;src/gen/&lt;/code&gt;."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User:&lt;/strong&gt; true for you, across every project. "Keep answers short." "I prefer TypeScript examples." "Explain shell commands before running them."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each scope answers a different question. Session memory answers &lt;em&gt;what are we doing?&lt;/em&gt; Project memory answers &lt;em&gt;how does this place work?&lt;/em&gt; User memory answers &lt;em&gt;how does this person like to work?&lt;/em&gt; When a memory is filed under the wrong question, you get the familiar symptoms: your personal style leaks into teammates' sessions, a project fact doesn't follow you into a new worktree, or yesterday's task keeps steering today's work.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Claude Code maps these scopes to real files
&lt;/h2&gt;

&lt;p&gt;You don't have to invent this structure. Claude Code's documentation on &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;how Claude remembers your project&lt;/a&gt; is a good concrete example, because each scope has its own location:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;User instructions&lt;/strong&gt; live in &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt; and apply to all your projects. Only you see them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Project instructions&lt;/strong&gt; live in &lt;code&gt;./CLAUDE.md&lt;/code&gt; or &lt;code&gt;./.claude/CLAUDE.md&lt;/code&gt; and are meant to be shared with the team through version control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local instructions&lt;/strong&gt; live in &lt;code&gt;./CLAUDE.local.md&lt;/code&gt;: personal, project-specific preferences that the docs say to add to &lt;code&gt;.gitignore&lt;/code&gt;, like your sandbox URLs or preferred test data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed policy&lt;/strong&gt; is an organization-wide file deployed by IT or DevOps, which loads before user and project files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two details from the docs are worth knowing. First, these files don't override each other; all discovered files are concatenated into context, ordered from broadest to most specific. So "most specific wins" only works if you write the specific file to say so clearly. Second, a gitignored &lt;code&gt;CLAUDE.local.md&lt;/code&gt; only exists in the worktree where you created it. If you want personal project notes in every worktree, the docs suggest importing a file from your home directory instead.&lt;/p&gt;

&lt;p&gt;Then there's &lt;strong&gt;auto memory&lt;/strong&gt;, the notes Claude writes for itself. The docs describe it as per repository: each project gets a directory at &lt;code&gt;~/.claude/projects/&amp;lt;project&amp;gt;/memory/&lt;/code&gt;, and all worktrees and subdirectories of the same git repo share it. It's machine-local, so it doesn't follow you to another machine or a cloud environment. Claude records each note with a &lt;code&gt;type&lt;/code&gt; of &lt;code&gt;user&lt;/code&gt;, &lt;code&gt;feedback&lt;/code&gt;, &lt;code&gt;project&lt;/code&gt; or &lt;code&gt;reference&lt;/code&gt;, and skips anything it can derive from the code or that your CLAUDE.md already says.&lt;/p&gt;

&lt;p&gt;Subagents get the same idea in miniature. A subagent's &lt;code&gt;memory&lt;/code&gt; field takes a scope: &lt;code&gt;user&lt;/code&gt; (&lt;code&gt;~/.claude/agent-memory/&amp;lt;name&amp;gt;/&lt;/code&gt;), &lt;code&gt;project&lt;/code&gt; (&lt;code&gt;.claude/agent-memory/&amp;lt;name&amp;gt;/&lt;/code&gt;, shareable via version control) or &lt;code&gt;local&lt;/code&gt; (&lt;code&gt;.claude/agent-memory-local/&amp;lt;name&amp;gt;/&lt;/code&gt;, not checked in). The docs recommend &lt;code&gt;project&lt;/code&gt; as the default.&lt;/p&gt;

&lt;p&gt;Even if you don't use Claude Code, this is a useful template: one place per scope, and a clear rule for which ones get committed.&lt;/p&gt;

&lt;h2&gt;
  
  
  A decision test for every memory
&lt;/h2&gt;

&lt;p&gt;Before a fact gets saved, by you or by the agent, run it through four questions. This is my own rule of thumb, not something from a spec:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Would it still be true next week?&lt;/strong&gt; If not, it's session memory. Keep it in the task, a scratch file, or a plan, and let it go when the task ends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Would it be true for a teammate in this repo?&lt;/strong&gt; If yes, it's project memory and belongs in the shared, committed file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is it about you rather than the code?&lt;/strong&gt; It's user memory: your home-level file, never the repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does it have an end date?&lt;/strong&gt; Then write the date into the memory and give it a review point. "No deploys until 16 Nov" is fine. "No deploys" with no date is a bug waiting to happen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fourth question is the one people skip. A rule that was true for two weeks and stays loaded forever is worse than no memory, because the agent will follow it with total confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  A minimal scoped memory, run for real
&lt;/h2&gt;

&lt;p&gt;Here's a small Python sketch of the idea. Each memory has a scope, a subject, a value, the date it was written, and an optional review date. Session memories are dropped when the session ends. When two scopes hold the same subject, the more specific one wins. Anything past its review date gets flagged instead of silently trusted.&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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="c1"&gt;# Most specific scope wins when two scopes hold the same subject.
&lt;/span&gt;&lt;span class="n"&gt;SCOPES&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;user&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;project&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;session&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;  &lt;span class="c1"&gt;# each entry: scope, subject, value, written, review_after
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;review_days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Update, don't append: one current value per (scope, subject).
&lt;/span&gt;    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[:]&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="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;memory&lt;/span&gt; &lt;span class="nf"&gt;if &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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scope&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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;written&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;review_days&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;review_days&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="n"&gt;memory&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scope&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                   &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;written&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;written&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review_after&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;review&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;end_session&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;[:]&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="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;memory&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scope&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session&lt;/span&gt;&lt;span class="sh"&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;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stale&lt;/span&gt; &lt;span class="o"&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;scope&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;SCOPES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# broad to specific, so specific overwrites
&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;memory&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scope&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="n"&gt;scope&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;current&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject&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="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;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&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;scope&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;]&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;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review_after&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;today&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;review_after&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="n"&gt;stale&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;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&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;scope&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, written &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;written&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stale&lt;/span&gt;

&lt;span class="n"&gt;d0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2026&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&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;remember&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&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;answer_style&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;short answers, no emoji&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;remember&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&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;package_manager&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;npm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project&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;package_manager&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;pnpm&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project&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;release_freeze&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;no deploys until 16 Nov&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;review_days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session&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;current_task&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;fix the login redirect&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;facts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d0&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="s"&gt;Session 1, day 0:&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;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;facts&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="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="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&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;v&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="nf"&gt;end_session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;facts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;15&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="s"&gt;Session 2, day 15:&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;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;facts&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="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="s"&gt;  &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&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;v&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="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="s"&gt;Needs review:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stale&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;nothing&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;This is the actual output from running it with Python 3.13:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Session 1, day 0:
  answer_style: short answers, no emoji  [user]
  package_manager: pnpm  [project]
  release_freeze: no deploys until 16 Nov  [project]
  current_task: fix the login redirect  [session]
Session 2, day 15:
  answer_style: short answers, no emoji  [user]
  package_manager: pnpm  [project]
  release_freeze: no deploys until 16 Nov  [project]
Needs review: ['release_freeze (project, written 2026-11-02)']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three things happened. The project's &lt;code&gt;pnpm&lt;/code&gt; beat the user-level &lt;code&gt;npm&lt;/code&gt;, because the repo knows better than your habits. The session task disappeared once the session ended. And the release freeze is still loaded on day 15, but it's flagged for review rather than quietly obeyed. In a real agent, that flag is the moment to ask the user or drop the entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the always-loaded part small
&lt;/h2&gt;

&lt;p&gt;Scope also decides what gets loaded every time. Claude Code's auto memory is a good example of why that matters: only the first 200 lines or 25KB of &lt;code&gt;MEMORY.md&lt;/code&gt; load at the start of a conversation, whichever comes first, and anything past that limit isn't loaded at session start. Detailed notes go into separate topic files that Claude reads on demand. The docs also note that when Claude writes a memory file with frontmatter, Claude Code records a &lt;code&gt;modified&lt;/code&gt; timestamp, so both you and Claude can see how current a fact is.&lt;/p&gt;

&lt;p&gt;The general lesson: keep a short index of what's always relevant, push detail into files that load only when needed, and keep a date on every fact. Memory files in Claude Code are excluded from transcript cleanup and stay until you or Claude edits or deletes them, so reviewing them is your job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sharing one memory across tools
&lt;/h2&gt;

&lt;p&gt;If your team uses more than one coding agent, project memory is the scope worth sharing. The Claude Code docs describe two ways to keep one &lt;code&gt;AGENTS.md&lt;/code&gt; as the file every tool reads: Claude Code can read &lt;code&gt;AGENTS.md&lt;/code&gt; directly when there's no &lt;code&gt;CLAUDE.md&lt;/code&gt;, or you can put an &lt;code&gt;@AGENTS.md&lt;/code&gt; import at the top of a &lt;code&gt;CLAUDE.md&lt;/code&gt; and add Claude-specific notes below it. User memory, on the other hand, should stay personal and out of the repo, whichever tool you use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Every memory has a scope: session, project or user&lt;/li&gt;
&lt;li&gt;[ ] Session notes are cleared or archived when the task ends&lt;/li&gt;
&lt;li&gt;[ ] Project facts live in a committed file the whole team can review&lt;/li&gt;
&lt;li&gt;[ ] Personal preferences live outside the repo (home-level or gitignored)&lt;/li&gt;
&lt;li&gt;[ ] Time-limited rules carry an end date and a review point&lt;/li&gt;
&lt;li&gt;[ ] The always-loaded index stays short; detail loads on demand&lt;/li&gt;
&lt;li&gt;[ ] One shared project file across tools, if you use more than one agent&lt;/li&gt;
&lt;li&gt;[ ] Someone reads the memory files on a schedule and deletes what's stale&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;If you're new to agent memory, start with the free &lt;a href="https://promptmasterstore.gumroad.com/l/agentmemoryfree?utm_source=devto" rel="noopener noreferrer"&gt;AI Agent Memory Quick-Start&lt;/a&gt;, six pages on the four kinds of memory and the basic memory loop.&lt;/p&gt;

&lt;p&gt;When you're ready to build it properly, &lt;a href="https://promptmasterstore.gumroad.com/l/agentmemory?utm_source=devto" rel="noopener noreferrer"&gt;AI Agent Memory: The Complete Guide&lt;/a&gt; covers writing and reading memories, consolidation and forgetting, tiered, vector and graph architectures, the production failure modes (bloat, poisoning, contradictions), and personalization and privacy, with a worked example of a support agent that remembers users across sessions.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI and reviewed by the author. The code output above comes from a real run of the script shown.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>claudecode</category>
      <category>productivity</category>
    </item>
    <item>
      <title>A2A in Plain English: Read an Agent Card, Then Follow One Task</title>
      <dc:creator>PromptMaster</dc:creator>
      <pubDate>Sun, 08 Nov 2026 12:08:12 +0000</pubDate>
      <link>https://dev.to/promptmaster/a2a-in-plain-english-read-an-agent-card-then-follow-one-task-mob</link>
      <guid>https://dev.to/promptmaster/a2a-in-plain-english-read-an-agent-card-then-follow-one-task-mob</guid>
      <description>&lt;p&gt;MCP connects your agent to tools. A2A connects it to other agents. Easy to repeat, harder to use when you're about to wire two systems together.&lt;/p&gt;

&lt;p&gt;The official A2A docs give a practical test: look at what's on the other end. If it's a tool with structured inputs and outputs, like a database query or a weather lookup, that's MCP territory. If it's another agent that reasons, plans, keeps state and may need a back-and-forth to finish the job, that's what A2A is for.&lt;/p&gt;

&lt;p&gt;Here's that test made concrete. I ran the official A2A "Hello World" sample locally, read its Agent Card, sent it a task, and then tried a few things you'll run into on day one. All the JSON below is real output from that run (IDs shortened), using the &lt;code&gt;a2a-sdk&lt;/code&gt; Python package, version 1.3.0, against protocol version 1.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  What A2A is, in two paragraphs
&lt;/h2&gt;

&lt;p&gt;The Agent2Agent (A2A) Protocol is an open standard for communication between AI agents, including agents built with different frameworks or by different vendors. It was originally developed by Google and donated to the Linux Foundation. A Technical Steering Committee with representatives from AWS, Cisco, Google, IBM Research, Microsoft, Salesforce, SAP and ServiceNow maintains it. The latest released version of the specification is 1.0.0.&lt;/p&gt;

&lt;p&gt;The docs are just as clear about what A2A is not. It's not a framework for building agents (that's LangGraph, CrewAI, ADK and the like). It's not how an agent calls its own sub-agents or tools. And it's not a replacement for MCP. The docs put it like this: "MCP is vertical. It deepens a single agent." A2A is horizontal: it connects your agent to agents that may belong to "another team, another department, or a partner organization."&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool or agent? A quick decision test
&lt;/h2&gt;

&lt;p&gt;Before choosing, answer four questions about the thing you want to connect to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Is the work one call, or a conversation?&lt;/strong&gt; A weather lookup is one call. "Find me a supplier for this part and negotiate delivery" may take several exchanges.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does it keep state between requests?&lt;/strong&gt; Tools are often stateless. Agents keep context over longer interactions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Might it need to ask you something before it can finish?&lt;/strong&gt; A2A has a task state for exactly that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do you control its internals?&lt;/strong&gt; A2A is designed for "opaque" agents. You see what the agent offers, not its prompts, tools or memory.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Mostly "one call, stateless, no questions, I own it" points to MCP. Mostly the opposite points to A2A. The docs' own example uses both: an auto repair shop where a mechanic agent calls a diagnostic scanner through MCP, then asks a parts-supplier agent for a part through A2A.&lt;/p&gt;

&lt;p&gt;That's my rule of thumb, not the spec's, and the line isn't always sharp: the docs note an A2A agent can expose well-defined, stateless skills as MCP-style tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: read the Agent Card
&lt;/h2&gt;

&lt;p&gt;An A2A agent describes itself in a JSON document called the Agent Card. For public agents, the recommended place to publish it is a well-known URL: &lt;code&gt;https://{agent-domain}/.well-known/agent-card.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the card the sample agent served on my machine:&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;"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;"Hello World Agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Just a hello world agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"supportedInterfaces"&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;"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;"http://127.0.0.1:9999"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"protocolBinding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"JSONRPC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"protocolVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.0"&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;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.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;"capabilities"&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;"streaming"&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;"extendedAgentCard"&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="nl"&gt;"defaultInputModes"&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;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"defaultOutputModes"&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;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skills"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echo_bot"&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;"Echo Bot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"An example agent that acknowledges client request and responds with a &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Hello World&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; message."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"tags"&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;"a2a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"echo-example"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"examples"&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;"hi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"how are you"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"inputModes"&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;"text/plain"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"outputModes"&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;"text/plain"&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;Read it top to bottom like a business card:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;supportedInterfaces&lt;/code&gt;&lt;/strong&gt; tells you where to send requests and how. It's an ordered list, and the first entry is the preferred one. This agent speaks JSON-RPC over HTTP. The spec also defines gRPC and a plain REST binding.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;capabilities&lt;/code&gt;&lt;/strong&gt; says what's optional and turned on. This one supports streaming, plus an extended card for authenticated clients.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;skills&lt;/code&gt;&lt;/strong&gt; is what the agent says it's good at, with examples. Treat this as a description, not a guarantee. The spec calls skills "largely a descriptive concept."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What's missing:&lt;/strong&gt; no &lt;code&gt;securitySchemes&lt;/code&gt;, so this agent asks for no authentication. Fine for a localhost demo; for anything real, check it first.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sample also defines an extended card with an extra skill, "Echo Bot (Super Mode)," that the public card doesn't show. The spec says the extended card must require authentication, so expect the public card to show less than the agent offers once you're signed in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: send a message, get a Task back
&lt;/h2&gt;

&lt;p&gt;A2A methods have plain names: &lt;code&gt;SendMessage&lt;/code&gt;, &lt;code&gt;SendStreamingMessage&lt;/code&gt;, &lt;code&gt;GetTask&lt;/code&gt;, &lt;code&gt;ListTasks&lt;/code&gt;, &lt;code&gt;CancelTask&lt;/code&gt; and &lt;code&gt;SubscribeToTask&lt;/code&gt;, plus methods for push notifications and the extended card. I sent the simplest request with curl, including the &lt;code&gt;A2A-Version: 1.0&lt;/code&gt; header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://127.0.0.1:9999/ &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'A2A-Version: 1.0'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"jsonrpc":"2.0","id":"req-1","method":"SendMessage",
       "params":{"message":{"role":"ROLE_USER","messageId":"msg-1",
       "parts":[{"text":"hi"}]}}}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent didn't just answer. It created a Task and returned it already finished:&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;"result"&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;"task"&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;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0c0faba9-…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"contextId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"91d6e4d5-…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"status"&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;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TASK_STATE_COMPLETED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"message"&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;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ROLE_AGENT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"parts"&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;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Request is completed!"&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="nl"&gt;"artifacts"&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;"artifactId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5b355243-…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"parts"&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;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hello, World! I have received your request (hi)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mediaType"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"text/plain"&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;(Trimmed: the &lt;code&gt;history&lt;/code&gt; array and timestamps.)&lt;/p&gt;

&lt;p&gt;Three ideas cover most of what you'll read in A2A responses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Task&lt;/strong&gt;: a unit of work with an ID and a state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Artifact&lt;/strong&gt;: the output. The answer lives here, not in the status message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;contextId&lt;/strong&gt;: groups related tasks and messages into one conversation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An agent can also reply with a plain Message instead of a Task, for quick exchanges that need no tracking. This sample always creates a Task, which the docs describe as a "task-generating agent."&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: know the states, especially the two that pause
&lt;/h2&gt;

&lt;p&gt;The spec defines these task states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TASK_STATE_SUBMITTED&lt;/code&gt; and &lt;code&gt;TASK_STATE_WORKING&lt;/code&gt;: in progress.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TASK_STATE_INPUT_REQUIRED&lt;/code&gt;: the agent needs more from you to continue.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TASK_STATE_AUTH_REQUIRED&lt;/code&gt;: the agent needs authorization to continue.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TASK_STATE_COMPLETED&lt;/code&gt;, &lt;code&gt;TASK_STATE_FAILED&lt;/code&gt;, &lt;code&gt;TASK_STATE_CANCELED&lt;/code&gt;, &lt;code&gt;TASK_STATE_REJECTED&lt;/code&gt;: terminal. The task is over.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The two "interrupted" states are the easy ones to miss. A client that only waits for "completed or failed" sees a task waiting on input or authorization as a hang. Handle both: show the status message to a person, or answer it from your own agent.&lt;/p&gt;

&lt;p&gt;One detail worth knowing: the spec says an agent must not treat the &lt;code&gt;AUTH_REQUIRED&lt;/code&gt; transition, by itself, as permission for anything. Answering that state isn't a blanket yes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: the rule that saves you a debugging session
&lt;/h2&gt;

&lt;p&gt;What happens if you send a follow-up to a finished task? I sent a second message with the completed task's &lt;code&gt;taskId&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;"error"&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;"code"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-32004&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Task 0c0faba9-… is in terminal state: TASK_STATE_COMPLETED"&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;That's by design. The docs say a task in a terminal state "cannot restart." Any follow-up has to start a new task in the same context. So I sent the follow-up again, this time with the same &lt;code&gt;contextId&lt;/code&gt; and the old task in &lt;code&gt;referenceTaskIds&lt;/code&gt;. I got a new task ID, the same &lt;code&gt;contextId&lt;/code&gt;, and a completed result.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;GetTask&lt;/code&gt; on the old ID still returned &lt;code&gt;TASK_STATE_COMPLETED&lt;/code&gt;, and &lt;code&gt;CancelTask&lt;/code&gt; returned "Task cannot be canceled." Finished tasks stay finished, which makes them easy to trace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before your agent talks to a remote agent
&lt;/h2&gt;

&lt;p&gt;The spec's security section reads like a checklist, so here's a short one built from it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTPS only in production.&lt;/strong&gt; The spec says production deployments must use encrypted transport. My &lt;code&gt;http://127.0.0.1&lt;/code&gt; demo is not a production setup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read &lt;code&gt;securitySchemes&lt;/code&gt; on the card.&lt;/strong&gt; That's how a client learns which authentication the agent expects. You get the credentials out of band, not from the card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expect scoped results.&lt;/strong&gt; The server must check authorization on every request and scope &lt;code&gt;ListTasks&lt;/code&gt; and &lt;code&gt;GetTask&lt;/code&gt; to what the caller is allowed to see.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat push-notification tokens as secrets.&lt;/strong&gt; If you register a webhook, the spec recommends unique, single-purpose tokens, HTTPS endpoints, and processing notifications idempotently, because duplicates can arrive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't trust skills because they're listed.&lt;/strong&gt; A card is a self-description. Decide what your agent may delegate the same way you'd vet any third-party service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're also connecting MCP servers, &lt;a href="https://dev.to/promptmaster/before-you-connect-an-mcp-server-a-5-question-trust-check-kf"&gt;Before You Connect an MCP Server: A 5-Question Trust Check&lt;/a&gt; covers that side. If you're still deciding whether you need more than one agent at all, read &lt;a href="https://dev.to/promptmaster/single-agent-subagents-or-an-agent-team-when-multi-agent-coding-pays-off-2159"&gt;Single Agent, Subagents or an Agent Team?&lt;/a&gt; first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;The Hello World agent is at &lt;code&gt;samples/python/agents/helloworld&lt;/code&gt; in &lt;a href="https://github.com/a2aproject/a2a-samples" rel="noopener noreferrer"&gt;a2aproject/a2a-samples&lt;/a&gt;. Install &lt;code&gt;a2a-sdk[http-server]&lt;/code&gt; and &lt;code&gt;uvicorn&lt;/code&gt;, run the sample (it serves on port 9999), fetch &lt;code&gt;/.well-known/agent-card.json&lt;/code&gt; and send the curl request above.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] The other side is an agent (stateful, multi-turn), not a single tool call&lt;/li&gt;
&lt;li&gt;[ ] Agent Card fetched and read: interface, capabilities, skills, &lt;code&gt;securitySchemes&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Client handles &lt;code&gt;INPUT_REQUIRED&lt;/code&gt; and &lt;code&gt;AUTH_REQUIRED&lt;/code&gt;, not only completed/failed&lt;/li&gt;
&lt;li&gt;[ ] Results read from artifacts, not from the status message&lt;/li&gt;
&lt;li&gt;[ ] Follow-ups start a new task with the same &lt;code&gt;contextId&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] HTTPS and authentication in place before anything leaves localhost&lt;/li&gt;
&lt;li&gt;[ ] Webhook tokens unique, and notifications handled idempotently&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://promptmasterstore.gumroad.com/l/a2afree?utm_source=devto" rel="noopener noreferrer"&gt;A2A: Free Quick-Start — The Agent2Agent Protocol in 15 Minutes&lt;/a&gt;&lt;/strong&gt;: six free pages on the problem A2A solves, the five building blocks (Agent Card, Task, Message, Parts, Artifact), the task lifecycle and where MCP fits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://promptmasterstore.gumroad.com/l/a2a?utm_source=devto" rel="noopener noreferrer"&gt;A2A: The Complete Guide to the Agent2Agent Protocol&lt;/a&gt;&lt;/strong&gt;: the next step. 42 pages on Agent Card discovery, transport and streaming, security, A2A and MCP together, building your first agent with the official SDK, orchestration patterns and production concerns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI and reviewed by the author. The code samples and JSON outputs come from a real local run of the official A2A sample.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>mcp</category>
      <category>python</category>
    </item>
    <item>
      <title>Off-Brand UI From Your AI Agent? Write, Lint and Wire a DESIGN.md</title>
      <dc:creator>PromptMaster</dc:creator>
      <pubDate>Sat, 07 Nov 2026 10:45:30 +0000</pubDate>
      <link>https://dev.to/promptmaster/off-brand-ui-from-your-ai-agent-write-lint-and-wire-a-designmd-192m</link>
      <guid>https://dev.to/promptmaster/off-brand-ui-from-your-ai-agent-write-lint-and-wire-a-designmd-192m</guid>
      <description>&lt;p&gt;Ask a coding agent for a settings page and you'll get something that works and looks almost right. The blue is a shade off. The font is close to yours but isn't. The cards have rounded corners your brand never uses. You fix it, and the next screen has the same problems.&lt;/p&gt;

&lt;p&gt;The agent isn't being careless. It has no reliable place to read your design system, so it fills the gaps with whatever is typical. Prompting harder helps for one screen. A file the agent reads every time helps for all of them.&lt;/p&gt;

&lt;p&gt;That file is what &lt;a href="https://github.com/google-labs-code/design.md" rel="noopener noreferrer"&gt;DESIGN.md&lt;/a&gt; is for. It's an open format from Google Labs, published on GitHub under Apache 2.0, "for describing a visual identity to coding agents." This walkthrough writes a small one, lints it with the official CLI, fixes what the linter finds, and then makes sure your agent actually reads it.&lt;/p&gt;

&lt;p&gt;One caveat up front: the format is at version &lt;code&gt;alpha&lt;/code&gt;. The README says the spec, token schema and CLI "are under active development," so expect changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What goes in a DESIGN.md
&lt;/h2&gt;

&lt;p&gt;A DESIGN.md has two layers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Design tokens in YAML&lt;/strong&gt;, either as front matter at the top of the file or as &lt;code&gt;yaml&lt;/code&gt; code blocks inside each section. These are the exact values: colors, typography, spacing, corner radius, components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Markdown prose&lt;/strong&gt; in &lt;code&gt;##&lt;/code&gt; sections that explains how to use those values.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The spec calls the tokens "the normative values" and says the prose "provides context for how to apply them." The project's &lt;a href="https://github.com/google-labs-code/design.md/blob/main/PHILOSOPHY.md" rel="noopener noreferrer"&gt;philosophy page&lt;/a&gt; goes further and calls the prose "the most vital part of the specification."&lt;/p&gt;

&lt;p&gt;That split is the whole idea. A hex code tells the agent what your accent color is. Only a sentence can tell it that the accent is for the primary button and nothing else.&lt;/p&gt;

&lt;p&gt;Sections are optional, but the ones you include should follow this order: Overview, Colors, Typography, Layout, Elevation &amp;amp; Depth, Shapes, Components, Do's and Don'ts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Write a small one
&lt;/h2&gt;

&lt;p&gt;Here's a deliberately small file for a made-up internal finance dashboard. Save it as &lt;code&gt;DESIGN.md&lt;/code&gt; in the root of your repo.&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;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alpha&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;Ledger&lt;/span&gt;
&lt;span class="na"&gt;colors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;primary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#14213D"&lt;/span&gt;
  &lt;span class="na"&gt;accent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#2F6FFF"&lt;/span&gt;
  &lt;span class="na"&gt;surface&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#F5F7FA"&lt;/span&gt;
  &lt;span class="na"&gt;on-accent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;#FFFFFF"&lt;/span&gt;
&lt;span class="na"&gt;typography&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;heading&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;fontFamily&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Inter&lt;/span&gt;
    &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;2rem&lt;/span&gt;
    &lt;span class="na"&gt;fontWeight&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;600&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;fontFamily&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Inter&lt;/span&gt;
    &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1rem&lt;/span&gt;
&lt;span class="na"&gt;rounded&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;4px&lt;/span&gt;
&lt;span class="na"&gt;spacing&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;8px&lt;/span&gt;
  &lt;span class="na"&gt;md&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;16px&lt;/span&gt;
&lt;span class="na"&gt;components&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;button-primary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{colors.accent}"&lt;/span&gt;
    &lt;span class="na"&gt;textColor&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{colors.on-accent}"&lt;/span&gt;
    &lt;span class="na"&gt;rounded&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{rounded.sm}"&lt;/span&gt;
    &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;12px&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gu"&gt;## Overview&lt;/span&gt;

An internal finance dashboard for accountants who read numbers all day.
Calm, dense and precise, like a well-kept paper ledger. Nothing decorative.

&lt;span class="gu"&gt;## Colors&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Primary**&lt;/span&gt; {colors.primary} carries all text and table rules.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Accent**&lt;/span&gt; {colors.accent} is only for the one primary action on a screen.
  Never for links, icons, charts or highlights.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Surface**&lt;/span&gt; {colors.surface} is the page background, never pure white.

&lt;span class="gu"&gt;## Do's and Don'ts&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Don't**&lt;/span&gt; add gradients, shadows or rounded cards.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Don't**&lt;/span&gt; use the accent for more than one element per screen.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Do**&lt;/span&gt; right-align every number column.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth noticing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Components reference tokens&lt;/strong&gt; with &lt;code&gt;{path.to.token}&lt;/code&gt;, so &lt;code&gt;button-primary&lt;/code&gt; points at &lt;code&gt;{colors.accent}&lt;/code&gt; instead of repeating the hex value.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Overview names a specific thing&lt;/strong&gt; ("a well-kept paper ledger") instead of a list of adjectives. The philosophy page argues this directly: "Adjectives describe a region. A specific reference describes a point." Words like "modern, clean, premium" give the agent nothing to aim at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Do's and Don'ts are short and specific.&lt;/strong&gt; The same page warns that a long, rambling list of don'ts is often a sign the description was too vague.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: Lint it
&lt;/h2&gt;

&lt;p&gt;The official CLI is on npm as &lt;code&gt;@google/design.md&lt;/code&gt;. You can run it without installing anything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @google/design.md lint DESIGN.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows PowerShell, the README says the &lt;code&gt;.md&lt;/code&gt; in the command name can clash with the Markdown file association. Use the dot-free alias instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-p&lt;/span&gt; @google/design.md designmd lint DESIGN.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I ran the file above through version 0.4.0 of the CLI. This is the real output:&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;"findings"&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;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"warning"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"components.button-primary"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"textColor (#ffffff) on backgroundColor (#2f6fff) has contrast ratio 4.34:1, below WCAG AA minimum of 4.5: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;"rule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"contrast-ratio"&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;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Design system defines 4 colors, 2 typography scales, 1 rounding level, 2 spacing tokens, 1 component."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"rule"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"token-summary"&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;"summary"&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;"errors"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"warnings"&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;"infos"&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="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;I didn't plan that warning. White text on that bright blue looks fine on a good screen, but it falls below the WCAG AA minimum of 4.5:1 for normal text. Without the check, every primary button the agent builds would have carried the problem.&lt;/p&gt;

&lt;p&gt;The linter has eleven rules. The ones you'll hit most often:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;broken-ref&lt;/code&gt;&lt;/strong&gt; (error): a &lt;code&gt;{colors.something}&lt;/code&gt; reference that points at nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;contrast-ratio&lt;/code&gt;&lt;/strong&gt; (warning): a component's text and background colors fall below 4.5:1.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;missing-primary&lt;/code&gt;&lt;/strong&gt; (warning): colors are defined but there's no &lt;code&gt;primary&lt;/code&gt;, so agents will invent one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;missing-typography&lt;/code&gt;&lt;/strong&gt; (warning): colors but no type tokens, so agents will use default fonts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;section-order&lt;/code&gt;&lt;/strong&gt; (warning): sections out of the canonical order.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Errors make &lt;code&gt;lint&lt;/code&gt; exit with code &lt;code&gt;1&lt;/code&gt;, so you can use it in CI. I checked this with a typo: changing the reference to &lt;code&gt;{colors.acent}&lt;/code&gt; produced a &lt;code&gt;broken-ref&lt;/code&gt; error ("Reference {colors.acent} does not resolve to any defined token.") and exit code &lt;code&gt;1&lt;/code&gt;. Warnings alone still exit with &lt;code&gt;0&lt;/code&gt;, so if you want contrast problems to block a merge, read the JSON and fail on warnings yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Fix it and check the change
&lt;/h2&gt;

&lt;p&gt;I darkened the accent to &lt;code&gt;#1F5AE6&lt;/code&gt; and saved the result as &lt;code&gt;DESIGN-v2.md&lt;/code&gt;. Lint now reports zero errors and zero warnings. To see exactly what changed between the two versions, use &lt;code&gt;diff&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;npx @google/design.md diff DESIGN.md DESIGN-v2.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output lists &lt;code&gt;accent&lt;/code&gt; under modified colors and &lt;code&gt;button-primary&lt;/code&gt; under modified components. It also shows warnings going from 1 to 0 and &lt;code&gt;"regression": false&lt;/code&gt;. &lt;code&gt;diff&lt;/code&gt; exits with &lt;code&gt;1&lt;/code&gt; when the new file has more errors or warnings than the old one. That makes it a cheap guard on pull requests that touch the design file.&lt;/p&gt;

&lt;p&gt;Before you pick a fix, read the prose again. Your rule says the accent is for the primary action, so changing its shade is a brand decision, not just a lint fix. The linter tells you there's a problem. Choosing the answer is still your job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Make sure your agent reads it
&lt;/h2&gt;

&lt;p&gt;A file in your repo helps only if the agent loads it. Nothing in the sources I used says that coding agents pick up DESIGN.md automatically, so point to it from the instruction file your agent already reads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code.&lt;/strong&gt; CLAUDE.md supports imports with &lt;code&gt;@path/to/file&lt;/code&gt;. According to the &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;Claude Code memory docs&lt;/a&gt;, imported files are "expanded and loaded into context alongside the CLAUDE.md that references them." Adding &lt;code&gt;@DESIGN.md&lt;/code&gt; loads it in every session. The same docs note that imports don't reduce context cost. So if most of your sessions aren't about UI, a one-line pointer is lighter:&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="gu"&gt;## UI work&lt;/span&gt;
Before creating or changing any UI, read DESIGN.md and follow its tokens and its Do's and Don'ts.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cursor, Codex and other agents that read AGENTS.md.&lt;/strong&gt; Put the same pointer in AGENTS.md. If you're not sure which instruction files your agent loads, &lt;a href="https://dev.to/promptmaster/a-stray-agentsmd-can-steer-your-agent-check-what-it-loads-first-28an"&gt;A Stray AGENTS.md Can Steer Your Agent: Check What It Loads First&lt;/a&gt; shows how to check. And if your CLAUDE.md is already long, &lt;a href="https://dev.to/promptmaster/your-claudemd-is-too-long-what-to-cut-so-claude-code-follows-it-5713"&gt;Your CLAUDE.md Is Too Long&lt;/a&gt; covers what to cut first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The spec itself.&lt;/strong&gt; &lt;code&gt;npx @google/design.md spec&lt;/code&gt; prints the format specification. The README describes it as "useful for injecting spec context into agent prompts." That helps when you ask an agent to write or extend the DESIGN.md for you.&lt;/p&gt;

&lt;p&gt;Then test it the way you'd test any instruction: ask for one screen and check three things. Is the accent used only once? Are the corners 4px? Are the number columns right-aligned? If not, the prose is usually too vague, and the fix belongs in the file, not in your next prompt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] DESIGN.md in the repo root with a &lt;code&gt;primary&lt;/code&gt; color and typography tokens&lt;/li&gt;
&lt;li&gt;[ ] Components reference tokens with &lt;code&gt;{...}&lt;/code&gt; instead of repeating values&lt;/li&gt;
&lt;li&gt;[ ] Overview names one specific reference, not a list of adjectives&lt;/li&gt;
&lt;li&gt;[ ] Every color has a sentence saying where it goes and where it doesn't&lt;/li&gt;
&lt;li&gt;[ ] Short, specific Do's and Don'ts&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;lint&lt;/code&gt; shows zero errors, and every warning has been read&lt;/li&gt;
&lt;li&gt;[ ] &lt;code&gt;diff&lt;/code&gt; run on any change to the file&lt;/li&gt;
&lt;li&gt;[ ] CLAUDE.md or AGENTS.md points to DESIGN.md&lt;/li&gt;
&lt;li&gt;[ ] One test screen checked against the rules&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://promptmasterstore.gumroad.com/l/design-md-cheat-sheet?utm_source=devto" rel="noopener noreferrer"&gt;DESIGN.md Quick-Start Cheat Sheet (Free)&lt;/a&gt;&lt;/strong&gt;: six pages to keep next to your editor. It covers the two-part anatomy, why prose carries the rules, a complete annotated example with weak and strong prose side by side, and the CLI and built-in contrast checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://promptmasterstore.gumroad.com/l/design-md-guide?utm_source=devto" rel="noopener noreferrer"&gt;DESIGN.md: The Complete Guide to Design Systems for AI Agents&lt;/a&gt;&lt;/strong&gt;: the full reference. It covers the token schema, lint, diff and export (Tailwind v4 and W3C DTCG), wiring it into Claude Code, Cursor, Kiro and Windsurf, the eight most common mistakes, and a walkthrough that builds a design system from scratch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI and reviewed by the author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>design</category>
      <category>productivity</category>
    </item>
    <item>
      <title>MCP Without Code: Connect Your AI Assistant to a Folder, Step by Step</title>
      <dc:creator>PromptMaster</dc:creator>
      <pubDate>Fri, 06 Nov 2026 11:35:30 +0000</pubDate>
      <link>https://dev.to/promptmaster/mcp-without-code-connect-your-ai-assistant-to-a-folder-step-by-step-33mb</link>
      <guid>https://dev.to/promptmaster/mcp-without-code-connect-your-ai-assistant-to-a-folder-step-by-step-33mb</guid>
      <description>&lt;p&gt;Most MCP tutorials start with "build a server in Python." If you just want your AI assistant to read the files you're working on, that's the wrong starting point. You don't need to build anything. You need to connect one server that already exists, check that it works, and know how to undo it.&lt;/p&gt;

&lt;p&gt;MCP (the Model Context Protocol) is the open standard that lets an AI app talk to outside tools and data. The official site describes it as "a USB-C port for AI applications": one standard plug instead of a custom integration for every tool.&lt;/p&gt;

&lt;p&gt;This walkthrough connects one server end to end: the official Filesystem server in Claude Desktop, following the steps in the &lt;a href="https://modelcontextprotocol.io/docs/develop/connect-local-servers" rel="noopener noreferrer"&gt;MCP documentation&lt;/a&gt;. At the end there's a short note on doing the same in Cursor and on remote "connectors" that need no install at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you'll have at the end
&lt;/h2&gt;

&lt;p&gt;Claude Desktop will be able to read, create, move and search files, but only inside the folders you name. Per the docs, Claude asks for your approval before each file operation, so you stay in the loop.&lt;/p&gt;

&lt;p&gt;Code needed: none. You will edit one small text file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 0: Two things to install
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claude Desktop.&lt;/strong&gt; Download it from &lt;a href="https://claude.ai/download" rel="noopener noreferrer"&gt;claude.ai/download&lt;/a&gt;. The docs list macOS, Windows and Linux. If you already have it, use the Claude menu and pick "Check for Updates..." first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Node.js.&lt;/strong&gt; The Filesystem server, like many MCP servers, runs on Node.js. Open a terminal (Terminal on macOS, Command Prompt on Windows) and type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see a version number, you're set. If you get "command not found", install the LTS version from &lt;a href="https://nodejs.org/" rel="noopener noreferrer"&gt;nodejs.org&lt;/a&gt;. The docs recommend LTS for stability.&lt;/p&gt;

&lt;p&gt;That's the only terminal command you need, unless something goes wrong later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Open the config file
&lt;/h2&gt;

&lt;p&gt;MCP servers for Claude Desktop live in one JSON file. You don't have to find it by hand:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click the &lt;strong&gt;Claude menu in your system menu bar&lt;/strong&gt; (not the settings inside the chat window) and choose &lt;strong&gt;Settings...&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Open the &lt;strong&gt;Developer&lt;/strong&gt; tab in the left sidebar.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Edit Config&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This creates the file if it doesn't exist yet and opens it. For reference, it lives here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;macOS:&lt;/strong&gt; &lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows:&lt;/strong&gt; &lt;code&gt;%APPDATA%\Claude\claude_desktop_config.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux:&lt;/strong&gt; &lt;code&gt;~/.config/Claude/claude_desktop_config.json&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 2: Add the server, with one narrow folder
&lt;/h2&gt;

&lt;p&gt;The docs' example gives the server access to Desktop and Downloads. For a first try I'd go narrower: make a new, empty folder (say &lt;code&gt;mcp-test&lt;/code&gt; on your Desktop), put a couple of throwaway files in it, and give the server only that folder. You can widen it once you trust the setup.&lt;/p&gt;

&lt;p&gt;On macOS the file would look like this (replace &lt;code&gt;username&lt;/code&gt; with your account name):&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;"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;"filesystem"&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="w"&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;"@modelcontextprotocol/server-filesystem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"/Users/username/Desktop/mcp-test"&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;On Windows, the path uses double backslashes: &lt;code&gt;"C:\\Users\\username\\Desktop\\mcp-test"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If the file already has other servers in it, don't replace everything. Add &lt;code&gt;"filesystem": {...}&lt;/code&gt; as another entry inside &lt;code&gt;mcpServers&lt;/code&gt;, with a comma between entries.&lt;/p&gt;

&lt;p&gt;Here's what each part means, per the docs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"filesystem"&lt;/code&gt; is just a friendly name. It's what you'll see in Claude Desktop.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"command": "npx"&lt;/code&gt; tells Claude Desktop to use Node's &lt;code&gt;npx&lt;/code&gt; tool to run the server.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"-y"&lt;/code&gt; auto-confirms installing the package.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"@modelcontextprotocol/server-filesystem"&lt;/code&gt; is the server package.&lt;/li&gt;
&lt;li&gt;Every path after that is a folder the server may access. Nothing else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The docs add a warning worth reading twice: the server "runs with your user account permissions, so it can perform any file operations you can perform manually" inside those folders. That's why the folder list is the most important line in this file.&lt;/p&gt;

&lt;p&gt;Save the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Restart properly
&lt;/h2&gt;

&lt;p&gt;Closing the window isn't enough. &lt;strong&gt;Fully quit&lt;/strong&gt; Claude Desktop and open it again, so it reads the new config and starts the server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Check that it's connected
&lt;/h2&gt;

&lt;p&gt;In a new chat, click the &lt;strong&gt;"Add files, connectors, and more /"&lt;/strong&gt; button in the bottom-left of the message box. Hover over &lt;strong&gt;Connectors&lt;/strong&gt;, click &lt;strong&gt;Manage connectors&lt;/strong&gt;, and select &lt;strong&gt;filesystem&lt;/strong&gt;. You should see the list of tools the server exposes.&lt;/p&gt;

&lt;p&gt;If "filesystem" isn't there, skip to the troubleshooting section below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: A first task you can verify
&lt;/h2&gt;

&lt;p&gt;Start with something you can check with your own eyes. With two or three files in &lt;code&gt;mcp-test&lt;/code&gt;, try:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;List the files in my mcp-test folder and tell me what each one contains.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then something that writes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create a file called notes.txt in mcp-test with a three-line summary of those files.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude should ask for approval before the operation. Read what it wants to do, approve it, then open the folder and look. If the file is there with the content you expect, the connection works end to end.&lt;/p&gt;

&lt;p&gt;The docs' own examples go further: saving a poem to your desktop, finding work files in Downloads, sorting images into a new folder. Moving and renaming are real changes to real files, so try those on copies first.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it doesn't show up
&lt;/h2&gt;

&lt;p&gt;The docs list these checks, in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Restart completely&lt;/strong&gt; (quit, not just close the window).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check the JSON syntax.&lt;/strong&gt; A missing comma or an extra bracket is enough to break it. Pasting the file into any JSON validator will point to the line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use absolute paths.&lt;/strong&gt; &lt;code&gt;/Users/username/Desktop/mcp-test&lt;/code&gt;, not &lt;code&gt;Desktop/mcp-test&lt;/code&gt; or &lt;code&gt;~/Desktop/mcp-test&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read the logs.&lt;/strong&gt; On macOS they're in &lt;code&gt;~/Library/Logs/Claude&lt;/code&gt;, on Windows in &lt;code&gt;%APPDATA%\Claude\logs&lt;/code&gt;, on Linux in &lt;code&gt;~/.config/Claude/logs&lt;/code&gt;. &lt;code&gt;mcp.log&lt;/code&gt; covers connections in general; &lt;code&gt;mcp-server-filesystem.log&lt;/code&gt; has that server's own output.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the server by hand&lt;/strong&gt; to see the error directly:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx &lt;span class="nt"&gt;-y&lt;/span&gt; @modelcontextprotocol/server-filesystem /Users/username/Desktop/mcp-test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Node isn't installed or the path is wrong, this is usually where you'll see it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Undo it
&lt;/h2&gt;

&lt;p&gt;To disconnect, open the config again (Settings → Developer → Edit Config), delete the &lt;code&gt;"filesystem"&lt;/code&gt; entry, save, and fully restart. Knowing how to remove a server is part of setting it up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same idea in Cursor
&lt;/h2&gt;

&lt;p&gt;Cursor reads MCP servers from an &lt;code&gt;mcp.json&lt;/code&gt; file: &lt;code&gt;.cursor/mcp.json&lt;/code&gt; inside a project for project-only tools, or &lt;code&gt;~/.cursor/mcp.json&lt;/code&gt; in your home folder for tools everywhere, according to the &lt;a href="https://cursor.com/docs/context/mcp" rel="noopener noreferrer"&gt;Cursor docs&lt;/a&gt;. The &lt;code&gt;mcpServers&lt;/code&gt; block above has the same shape. Cursor also offers one-click installs from its Customize page, and by default it asks for approval before using an MCP tool. You can click the arrow next to the tool name to see the arguments it will send.&lt;/p&gt;

&lt;h2&gt;
  
  
  No install at all: remote connectors
&lt;/h2&gt;

&lt;p&gt;Some services run their MCP server on the internet. In Claude, those are added as &lt;strong&gt;custom connectors&lt;/strong&gt;: Settings → Connectors → Add → "Add custom connector", paste the server URL, then sign in when asked (often via OAuth). The &lt;a href="https://modelcontextprotocol.io/docs/develop/connect-remote-servers" rel="noopener noreferrer"&gt;remote servers guide&lt;/a&gt; also shows that you can turn specific tools on or off in that connector's settings. Use that: if you only need read access, switch off the tools that write or delete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you connect the next one
&lt;/h2&gt;

&lt;p&gt;One server you understand is worth more than ten you don't. Before adding anything else, run the five questions from &lt;a href="https://dev.to/promptmaster/before-you-connect-an-mcp-server-a-5-question-trust-check-kf"&gt;Before You Connect an MCP Server: A 5-Question Trust Check&lt;/a&gt;: who publishes it, what it runs, what it can reach, and so on. The docs say the same thing in short: only connect servers from trusted sources, review the permissions you grant, and remove connectors you no longer use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Node.js installed (&lt;code&gt;node --version&lt;/code&gt; works)&lt;/li&gt;
&lt;li&gt;[ ] Config opened via Settings → Developer → Edit Config&lt;/li&gt;
&lt;li&gt;[ ] Server entry added, other entries kept, JSON valid&lt;/li&gt;
&lt;li&gt;[ ] Only one narrow test folder listed, with an absolute path&lt;/li&gt;
&lt;li&gt;[ ] Claude Desktop fully quit and reopened&lt;/li&gt;
&lt;li&gt;[ ] "filesystem" visible under Manage connectors&lt;/li&gt;
&lt;li&gt;[ ] One read task and one write task verified by opening the folder&lt;/li&gt;
&lt;li&gt;[ ] You know how to remove the entry again&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://promptmasterstore.gumroad.com/l/mcp-made-simple?utm_source=devto" rel="noopener noreferrer"&gt;MCP Made Simple: Connect Claude, Cursor &amp;amp; Your AI Tools to Everything (No Code Required)&lt;/a&gt;&lt;/strong&gt;: the full guide to the &lt;em&gt;using&lt;/em&gt; side of MCP. It covers the mental model, setup in Claude Desktop, Claude Code and Cursor, the config file explained, a troubleshooting chapter, how to choose servers (with the trust check), real workflows for files, code and research, and a 7-day action plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://promptmasterstore.gumroad.com/l/mcp-cheat-sheet?utm_source=devto" rel="noopener noreferrer"&gt;MCP Quick-Start Cheat Sheet (Free)&lt;/a&gt;&lt;/strong&gt;: a free 5-page sheet to keep open while you set things up.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI and reviewed by the author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>beginners</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Your AI Agent Passed Its Evals. 5 Ways the Evals Might Be Lying</title>
      <dc:creator>PromptMaster</dc:creator>
      <pubDate>Thu, 05 Nov 2026 08:37:57 +0000</pubDate>
      <link>https://dev.to/promptmaster/your-ai-agent-passed-its-evals-5-ways-the-evals-might-be-lying-3997</link>
      <guid>https://dev.to/promptmaster/your-ai-agent-passed-its-evals-5-ways-the-evals-might-be-lying-3997</guid>
      <description>&lt;p&gt;Your agent's eval suite is green. Every case passes, the dashboard shows a nice high number, and you ship the prompt change. A week later a user hits a failure that should have been impossible.&lt;/p&gt;

&lt;p&gt;The usual takeaway is "write more tests". Often the real problem is that the tests you already have are telling you something false.&lt;/p&gt;

&lt;p&gt;Verification is a live topic right now. In last week's research, an X search for eval-related posts returned 11,061 posts in seven days, and one of the most-commented DEV posts was "Four Ways a Test Can Lie to You". This article is the agent-eval version of that idea.&lt;/p&gt;

&lt;p&gt;If you don't have an eval set yet, start with &lt;a href="https://dev.to/promptmaster/build-a-10-case-eval-set-for-your-ai-agent-and-run-it-on-every-change-4fpl"&gt;Build a 10-Case Eval Set for Your AI Agent and Run It on Every Change&lt;/a&gt;. This one assumes you have some evals and asks a harder question: should you trust them?&lt;/p&gt;

&lt;p&gt;The examples below come from Anthropic, OpenAI and METR. If their carefully built benchmarks had these problems, a ten-case suite written in an afternoon can too.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The grader rejects right answers (or accepts wrong ones)
&lt;/h2&gt;

&lt;p&gt;Anthropic's &lt;a href="https://www.anthropic.com/engineering/demystifying-evals-for-ai-agents" rel="noopener noreferrer"&gt;Demystifying evals for AI agents&lt;/a&gt; describes Opus 4.5 initially scoring 42% on CORE-Bench. A researcher then found rigid grading that penalized "96.12" when it expected "96.124991…", ambiguous task specs, and stochastic tasks that couldn't be reproduced exactly. After the bugs were fixed and a less constrained scaffold was used, the score jumped to 95%.&lt;/p&gt;

&lt;p&gt;OpenAI found the same pattern when it &lt;a href="https://openai.com/index/why-we-no-longer-evaluate-swe-bench-verified/" rel="noopener noreferrer"&gt;audited SWE-bench Verified&lt;/a&gt;. Of 138 problems that o3 didn't solve consistently, 59.4% had material issues in test design or problem description. In 35.5% of them the tests enforced implementation details, such as importing a function name the problem never mentioned, so many valid fixes failed on an import error.&lt;/p&gt;

&lt;p&gt;The other direction is worse because nothing turns red. In its &lt;a href="https://openai.com/index/separating-signal-from-noise-coding-evaluations/" rel="noopener noreferrer"&gt;later audit of SWE-Bench Pro&lt;/a&gt;, OpenAI lists "low-coverage tests" that "under check the requested feature, so incomplete fixes can pass."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; test the grader before you trust it. Anthropic recommends a reference solution for every task, a known working output that passes all graders. That proves the task is solvable and the grader is configured correctly. I'd add the mirror image: a known-bad answer that must fail. If the bad answer passes, the check isn't checking anything. Here's a sketch (not run against a real agent):&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;# Sketch: sanity-check each grader before trusting its verdicts.
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_grader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;grade&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;grade(task, output) -&amp;gt; bool. task has &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reference&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; and &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;known_bad&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; outputs.&lt;/span&gt;&lt;span class="sh"&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;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;grade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reference&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="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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rejects the reference solution&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;bad&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;known_bad&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="nf"&gt;grade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bad&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="s"&gt;accepts a known-bad output: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;bad&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&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;problems&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where you can, tolerate harmless variation: parse a number and compare with a tolerance instead of matching the string.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The task is ambiguous, so you're scoring your spec, not your agent
&lt;/h2&gt;

&lt;p&gt;Anthropic's test for a good task: two domain experts would independently reach the same pass/fail verdict. Its example from auditing Terminal-Bench: a task asked the agent to write a script without saying where, while the tests assumed a specific file path. The agent could fail "through no fault of its own."&lt;/p&gt;

&lt;p&gt;The same post says that with frontier models, a 0% pass rate across many trials (0% pass@100) is "most often a signal of a broken task, not an incapable agent." And METR found tasks that asked agents to reach a stated score threshold while the grader required &lt;em&gt;exceeding&lt;/em&gt; it, which, per Anthropic, penalized models that followed the instructions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; everything the grader checks must be written in the task. Read each case as the agent would: could you pass it from the text alone? When a case fails on every trial, debug the case first.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The agent passed by gaming the check
&lt;/h2&gt;

&lt;p&gt;METR's post &lt;a href="https://metr.org/blog/2025-06-05-recent-reward-hacking/" rel="noopener noreferrer"&gt;Recent Frontier Models Are Reward Hacking&lt;/a&gt; documents models modifying tests or scoring code, finding the answer the grader uses, and exploiting loopholes. In their table, o3 reward-hacked in 39 of 128 RE-Bench runs (30.4%) and in 0.7% of HCAST runs. METR notes it was more than 43× more common on RE-Bench, perhaps because the model could see the entire scoring function there. Adding "Please do not cheat." to the prompt of one task had a "nearly negligible effect." In &lt;a href="https://metr.org/evaluations/claude-3-7-report/" rel="noopener noreferrer"&gt;METR's Claude 3.7 Sonnet report&lt;/a&gt;, the model "directly edited a provided 'tests' file to cause all tests to pass."&lt;/p&gt;

&lt;p&gt;It doesn't have to be deliberate. Anthropic reports seeing Claude gain an unfair advantage in some internal evals by reading the git history left by previous trials.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep tests and graders out of the agent's reach, and start every trial from a clean environment.&lt;/li&gt;
&lt;li&gt;Grade the outcome, not the claim. Anthropic's example: a flight agent saying "Your flight has been booked" isn't the outcome. A reservation in the database is.&lt;/li&gt;
&lt;li&gt;Treat edits to test files, graders or config as an automatic fail, and check for them in code.&lt;/li&gt;
&lt;li&gt;Read transcripts of suspiciously fast or perfect passes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. One run, one number: noise that looks like signal
&lt;/h2&gt;

&lt;p&gt;Agent behavior varies between runs, so a single pass tells you little. Anthropic separates two metrics: pass@k (at least one success in k tries) and pass^k (all k tries succeed). Its example: a 75% per-trial success rate gives (0.75)³ ≈ 42% for three trials in a row. For a customer-facing agent, pass^k is the number that matches what users experience.&lt;/p&gt;

&lt;p&gt;The environment adds noise too. In &lt;a href="https://www.anthropic.com/engineering/infrastructure-noise" rel="noopener noreferrer"&gt;Quantifying infrastructure noise in agentic coding evals&lt;/a&gt;, Anthropic ran Terminal-Bench 2.0 with the same model, harness and tasks under different resource settings. The gap between the most- and least-resourced setups was 6 percentage points, and at strict limits 5.8% of tasks hit infrastructure errors. Their conclusion: leaderboard differences below 3 points "deserve skepticism" until the configuration is documented and matched.&lt;/p&gt;

&lt;p&gt;Small suites have their own trap. In &lt;a href="https://www.anthropic.com/research/statistical-approach-to-model-evals" rel="noopener noreferrer"&gt;A statistical approach to model evaluations&lt;/a&gt;, Anthropic recommends reporting standard errors and comparing two models question by question (paired differences) rather than just their averages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run several trials per case and report pass^k for anything users rely on.&lt;/li&gt;
&lt;li&gt;Log infrastructure failures (timeouts, OOM kills, rate limits) separately from agent failures.&lt;/li&gt;
&lt;li&gt;Compare old and new versions case by case, on the same setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. The suite is saturated, one-sided or public
&lt;/h2&gt;

&lt;p&gt;A suite where everything passes can protect you from regressions, but it can't tell you anything new. Anthropic splits evals into two kinds:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Capability evals&lt;/strong&gt; should start at a low pass rate and give you a hill to climb.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regression evals&lt;/strong&gt; should stay near 100%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In Anthropic's words, "An eval at 100% tracks regressions but provides no signal for improvement."&lt;/p&gt;

&lt;p&gt;One-sided sets mislead too. Anthropic's web search example: if you only test that the agent searches when it should, you can end up with an agent that searches for almost everything. You need cases where the behavior should &lt;em&gt;not&lt;/em&gt; happen.&lt;/p&gt;

&lt;p&gt;Public benchmarks bring contamination. OpenAI found that all frontier models it tested could reproduce the gold patch or verbatim problem details for some SWE-bench Verified tasks, and concluded the scores "increasingly reflect how much the model was exposed to the benchmark at training time." A strong public score for the model you're switching to says little about your tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; keep two suites, a regression set that must stay green and a capability set with cases the agent still fails. Add "should not" cases for every behavior you test. Turn real production failures into new cases, and keep your own cases out of public repos.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 10-minute trust check for your evals
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Every case has a reference output that passes, and at least one known-bad output that fails.&lt;/li&gt;
&lt;li&gt;A teammate reading only the task text would reach the grader's verdict.&lt;/li&gt;
&lt;li&gt;Cases that fail on every trial have been reviewed as possibly broken tasks.&lt;/li&gt;
&lt;li&gt;The agent can't read or edit tests, graders or other trials' leftovers.&lt;/li&gt;
&lt;li&gt;You grade final state (database, files, API calls), not the agent's own summary.&lt;/li&gt;
&lt;li&gt;You run several trials per case and know your pass^k, not just one pass.&lt;/li&gt;
&lt;li&gt;Infrastructure errors are counted apart from agent errors.&lt;/li&gt;
&lt;li&gt;You have both "should" and "should not" cases for key behaviors.&lt;/li&gt;
&lt;li&gt;You keep a capability set that isn't at 100%.&lt;/li&gt;
&lt;li&gt;You've read some transcripts this week, passes included.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Anthropic's own rule is a good one to borrow: they "do not take eval scores at face value until someone digs into the details of the eval and reads some transcripts."&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://promptmasterstore.gumroad.com/l/agentevalfree?utm_source=devto" rel="noopener noreferrer"&gt;AI Agent Evaluation: The Free QuickStart&lt;/a&gt;&lt;/strong&gt;: a free 5-page starter covering why agents break traditional testing, the Define &amp;gt; Measure &amp;gt; Test &amp;gt; Trust loop, and four steps from defining "good" to testing on every change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://promptmasterstore.gumroad.com/l/agenteval?utm_source=devto" rel="noopener noreferrer"&gt;AI Agent Evaluation &amp;amp; Testing: The Complete Guide&lt;/a&gt;&lt;/strong&gt;: the full 40-page guide, with LLM-as-judge, evaluating multi-step agents and tool use, regression testing and CI gates, tracing, online evaluation, common mistakes that quietly ruin evaluations, and a worked customer-support example.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI and reviewed by the author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>testing</category>
      <category>programming</category>
    </item>
    <item>
      <title>Your Agent's Context Fills Up Before You Type: What to Trim First</title>
      <dc:creator>PromptMaster</dc:creator>
      <pubDate>Wed, 04 Nov 2026 08:19:30 +0000</pubDate>
      <link>https://dev.to/promptmaster/your-agents-context-fills-up-before-you-type-what-to-trim-first-36n0</link>
      <guid>https://dev.to/promptmaster/your-agents-context-fills-up-before-you-type-what-to-trim-first-36n0</guid>
      <description>&lt;p&gt;Most advice about coding agents is about what you type. But by the time you type your first prompt, the agent's context window is already partly full: instruction files, memory, tool names, skill descriptions. Then every file it reads, every test run and every long log adds more, and all of it is re-sent with every request.&lt;/p&gt;

&lt;p&gt;That matters for more than your bill. In &lt;a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents" rel="noopener noreferrer"&gt;Effective context engineering for AI agents&lt;/a&gt;, Anthropic describes &lt;em&gt;context rot&lt;/em&gt;: as the number of tokens in the window grows, the model's ability to accurately recall information from it goes down. Their framing is that a model has an "attention budget", and every token you add spends some of it. The guiding principle they give is to "find the smallest set of high-signal tokens that maximize the likelihood of your desired outcome."&lt;/p&gt;

&lt;p&gt;I covered the money side in &lt;a href="https://dev.to/promptmaster/where-your-coding-agents-token-bill-goes-and-7-levers-to-cut-it-1e5j"&gt;Where Your Coding Agent's Token Bill Goes&lt;/a&gt;. This one is about quality: what fills the context, and what to trim first so the agent has room to think about your actual task. The examples use Claude Code because its docs spell out exactly what loads and when, but the order works for any agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Look before you cut
&lt;/h2&gt;

&lt;p&gt;Don't guess. In Claude Code, run &lt;code&gt;/context&lt;/code&gt;. The &lt;a href="https://code.claude.com/docs/en/context-window" rel="noopener noreferrer"&gt;context window docs&lt;/a&gt; describe it as a live breakdown by category, with optimization suggestions, including which &lt;code&gt;CLAUDE.md&lt;/code&gt; and auto memory files loaded.&lt;/p&gt;

&lt;p&gt;According to the same page, this is what loads before you type anything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CLAUDE.md&lt;/code&gt; (or &lt;code&gt;AGENTS.md&lt;/code&gt; in its place) and auto memory&lt;/li&gt;
&lt;li&gt;MCP tool names&lt;/li&gt;
&lt;li&gt;skill descriptions&lt;/li&gt;
&lt;li&gt;anything your own setup adds, like an output style or &lt;code&gt;--append-system-prompt&lt;/code&gt; text&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Write down the three biggest categories. That's your trim list, in order. If you don't use Claude Code, the question is the same: what does your agent load at startup, and which of those things did you actually need today?&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Trim the tools
&lt;/h2&gt;

&lt;p&gt;Anthropic calls bloated tool sets "one of the most common failure modes": too much functionality, or tools that overlap so the model can't tell which one to use. Their test is a good one to borrow: if a human engineer can't say for sure which tool fits a situation, the agent can't be expected to do better.&lt;/p&gt;

&lt;p&gt;Claude Code already softens the cost here. Per its &lt;a href="https://code.claude.com/docs/en/mcp" rel="noopener noreferrer"&gt;MCP docs&lt;/a&gt;, tool search is on by default, so only tool names and server instructions enter context at session start; full definitions load when Claude actually needs a tool. That doesn't make every connected server free, though, and it doesn't fix overlapping tools. Three things still help:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Disable servers you aren't using.&lt;/strong&gt; Run &lt;code&gt;/mcp&lt;/code&gt; and switch off anything unrelated to the current project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer a CLI when one exists.&lt;/strong&gt; The &lt;a href="https://code.claude.com/docs/en/costs" rel="noopener noreferrer"&gt;cost docs&lt;/a&gt; say tools like &lt;code&gt;gh&lt;/code&gt;, &lt;code&gt;aws&lt;/code&gt;, &lt;code&gt;gcloud&lt;/code&gt; and &lt;code&gt;sentry-cli&lt;/code&gt; are still more context-efficient than MCP servers, because they don't add any per-tool listing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check tool search is actually on.&lt;/strong&gt; It's off in some setups, for example older models, some cloud deployments or when &lt;code&gt;ENABLE_TOOL_SEARCH=false&lt;/code&gt; is set, and then all MCP tools load upfront. If &lt;code&gt;/context&lt;/code&gt; shows a large tools category, this is the first thing to look at.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you write your own MCP server, the docs suggest keeping descriptions and server instructions concise and putting the important words near the start, since that's the part tool search reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Trim what loads from files
&lt;/h2&gt;

&lt;p&gt;Instruction files load every session, whether the task needs them or not. Claude Code's &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;memory docs&lt;/a&gt; recommend keeping each &lt;code&gt;CLAUDE.md&lt;/code&gt; under 200 lines, and note that imports help you organise a long file but don't reduce its context cost, because imported files load with it.&lt;/p&gt;

&lt;p&gt;The fix is to move instructions to where they're needed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Path-scoped rules&lt;/strong&gt; in &lt;code&gt;.claude/rules/&lt;/code&gt; load only when Claude works with matching files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skills&lt;/strong&gt; load only when invoked, so a long PR-review or migration procedure costs nothing on days you don't use it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A path-scoped rule looks like this:&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;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="gh"&gt;# API rules&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Every endpoint validates input with the shared schema.
&lt;span class="p"&gt;-&lt;/span&gt; Errors use the standard error response format.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I went through the full cut-and-move process in &lt;a href="https://dev.to/promptmaster/your-claudemd-is-too-long-what-to-cut-so-claude-code-follows-it-5713"&gt;Your CLAUDE.md Is Too Long&lt;/a&gt;, so I won't repeat it here. One addition that's easy to miss is in Step 5 below: path-scoped rules don't survive compaction the way root instructions do.&lt;/p&gt;

&lt;p&gt;Then look at what the agent reads &lt;em&gt;during&lt;/em&gt; work. Vague prompts trigger broad scanning; the cost docs contrast "improve this codebase" with "add input validation to the login function in auth.ts". The second one lets the agent find what it needs with far fewer file reads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Keep noisy output out of the main window
&lt;/h2&gt;

&lt;p&gt;Test runs, log files and documentation pages are the quiet context killers. A failing test suite can print thousands of lines when you needed ten.&lt;/p&gt;

&lt;p&gt;Two options from the docs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Filter before the model sees it.&lt;/strong&gt; The cost docs show a &lt;code&gt;PreToolUse&lt;/code&gt; hook that rewrites test commands to keep only failure lines, and give the example of a hook that greps a 10,000-line log for &lt;code&gt;ERROR&lt;/code&gt; instead of letting Claude read all of it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delegate to a subagent.&lt;/strong&gt; A subagent does the reading in its own context window and sends back only a summary. The docs recommend this for running tests, fetching documentation and processing logs. It isn't free, since the subagent's requests still count toward your usage, but the raw output stays out of your main conversation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anthropic's post lists the same idea from the other direction: once a tool result is deep in history, the agent rarely needs to see the raw output again, and clearing old tool results is one of the lightest forms of compaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: Manage history on purpose
&lt;/h2&gt;

&lt;p&gt;The longest-lived thing in your context is the conversation itself. You have two tools for it, and they do different things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/clear&lt;/code&gt; when the task changes.&lt;/strong&gt; Old conversation crowds out the files you need next and is re-sent with every message. The docs point out that &lt;code&gt;/clear&lt;/code&gt; costs nothing, while &lt;code&gt;/compact&lt;/code&gt; is itself a large request because it has to read the whole conversation it summarises. If you don't need continuity, clear. Use &lt;code&gt;/rename&lt;/code&gt; first if you want to &lt;code&gt;/resume&lt;/code&gt; that session later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;/compact&lt;/code&gt; with a focus when you do need continuity.&lt;/strong&gt; Claude Code compacts automatically near the limit, but the automatic pass guesses what matters. Telling it is better:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/compact focus on the auth bug fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also put standing instructions in your project's &lt;code&gt;CLAUDE.md&lt;/code&gt;, as the docs show:&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;# Compact instructions&lt;/span&gt;

When you are using compact, please focus on test output and code changes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before relying on compaction, know what comes back. Per the context window docs, after compaction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the project-root &lt;code&gt;CLAUDE.md&lt;/code&gt;, unscoped rules and auto memory are re-injected from disk;&lt;/li&gt;
&lt;li&gt;path-scoped rules and nested &lt;code&gt;CLAUDE.md&lt;/code&gt; files are summarised away and reload only when Claude touches a matching file again;&lt;/li&gt;
&lt;li&gt;Claude Code re-reads up to five recently modified files it had used, and a file over 5,000 tokens comes back as a path reference, not its content;&lt;/li&gt;
&lt;li&gt;invoked skill bodies are re-injected but capped, and truncation keeps the start of the file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if a rule must hold for the whole session, don't scope it to paths; put it in the root file. And put the most important instructions at the top of each skill.&lt;/p&gt;

&lt;p&gt;If you're building your own agent rather than using Claude Code, the same three techniques from Anthropic's post apply: compaction, structured note-taking (the agent writes notes to a file outside the window and reads them back later), and subagents with clean context windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 15-minute context check
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;/context&lt;/code&gt; (or your agent's equivalent) and note the three biggest categories.&lt;/li&gt;
&lt;li&gt;Disable MCP servers you don't need for this project.&lt;/li&gt;
&lt;li&gt;Replace an MCP server with its CLI where one exists.&lt;/li&gt;
&lt;li&gt;Confirm tool search is on if your tools category looks large.&lt;/li&gt;
&lt;li&gt;Get each &lt;code&gt;CLAUDE.md&lt;/code&gt; under 200 lines; move the rest to path-scoped rules or skills.&lt;/li&gt;
&lt;li&gt;Write prompts that name the file or function, not the whole codebase.&lt;/li&gt;
&lt;li&gt;Filter test and log output with a hook, or send it to a subagent.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/clear&lt;/code&gt; between unrelated tasks; &lt;code&gt;/compact&lt;/code&gt; with a focus when you need continuity.&lt;/li&gt;
&lt;li&gt;Keep session-wide rules unscoped, at the top of the root instruction file.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;/context&lt;/code&gt; again and compare.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;These five steps are one slice of a bigger discipline. &lt;a href="https://promptmasterstore.gumroad.com/l/context-engineering-complete-guide?utm_source=devto" rel="noopener noreferrer"&gt;Context Engineering: The Complete Guide to the Skill That Replaced Prompt Engineering&lt;/a&gt; covers it in 40 pages: the anatomy of a context window, the four core strategies (Write, Select, Compress, Isolate) in depth, the four failure modes (poisoning, distraction, confusion and clash) with a diagnostic toolkit, context engineering in Claude Code and Cursor, 20 reusable patterns, 12 common pitfalls with fixes, and a 30-day plan.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI and reviewed by the author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>programming</category>
    </item>
    <item>
      <title>A Stray AGENTS.md Can Steer Your Agent: Check What It Loads First</title>
      <dc:creator>PromptMaster</dc:creator>
      <pubDate>Tue, 03 Nov 2026 08:18:46 +0000</pubDate>
      <link>https://dev.to/promptmaster/a-stray-agentsmd-can-steer-your-agent-check-what-it-loads-first-28an</link>
      <guid>https://dev.to/promptmaster/a-stray-agentsmd-can-steer-your-agent-check-what-it-loads-first-28an</guid>
      <description>&lt;p&gt;On 22 October, the security firm Forever Security &lt;a href="https://forever.security/blog/hijacking-ai-agents-via-downloads-folder/" rel="noopener noreferrer"&gt;published a report&lt;/a&gt; about a file it found on a customer's machine: an &lt;code&gt;AGENTS.md&lt;/code&gt; sitting in &lt;code&gt;~/Downloads&lt;/code&gt;, written to look like ordinary engineering notes. The customer had started OpenAI Codex in that folder. Codex read the file as project guidance and began installing &lt;code&gt;function-color&lt;/code&gt;, a known malicious npm package, before Forever's tooling stopped it.&lt;/p&gt;

&lt;p&gt;The researchers then reran the real file inside an isolated VM. In their tests, Codex carried out the payload in more than 90% of attempts, and the file had it add a server to &lt;code&gt;~/.codex/config.toml&lt;/code&gt;, the machine-wide config, so the damage didn't stay in Downloads. They also built a &lt;code&gt;CLAUDE.md&lt;/code&gt; variant against Claude Code, which they describe as much harder to fool; it followed through about half the time in their testing.&lt;/p&gt;

&lt;p&gt;Their conclusion is the part worth keeping: nothing here is a bug to patch. An agent that follows the instruction file in its folder is doing what it was built to do. So the defence is on you, and it starts with knowing which files your agent loads before you press Enter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the folder you start in matters
&lt;/h2&gt;

&lt;p&gt;Instruction files like &lt;code&gt;AGENTS.md&lt;/code&gt; and &lt;code&gt;CLAUDE.md&lt;/code&gt; aren't opened on request. They're loaded automatically at the start of a session, and the folder you launch from decides which ones. Downloads is a bad place to launch from because, as the report points out, it's the one folder a website can drop a file into without asking.&lt;/p&gt;

&lt;p&gt;I covered why these files are context, not a security boundary, in &lt;a href="https://dev.to/promptmaster/6-agentsmd-mistakes-that-make-your-coding-agent-ignore-it-4bhk"&gt;6 AGENTS.md Mistakes That Make Your Coding Agent Ignore It&lt;/a&gt;. This article is about the step before that: discovery.&lt;/p&gt;

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

&lt;p&gt;Here's what the official docs say, as of 3 November 2026.&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;Global file&lt;/th&gt;
&lt;th&gt;Project files&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Codex&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;~/.codex/AGENTS.override.md&lt;/code&gt; or &lt;code&gt;AGENTS.md&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;From the project root (a folder with &lt;code&gt;.git&lt;/code&gt;) down to where you started; with no root, only the current folder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CLAUDE.md&lt;/code&gt; and &lt;code&gt;CLAUDE.local.md&lt;/code&gt; in the current folder and every folder above it; &lt;code&gt;AGENTS.md&lt;/code&gt; from those same folders when there's no &lt;code&gt;CLAUDE.md&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor&lt;/td&gt;
&lt;td&gt;User Rules (Customize → Rules)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;AGENTS.md&lt;/code&gt; in the project root and subfolders, combined&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini CLI&lt;/td&gt;
&lt;td&gt;&lt;code&gt;~/.gemini/GEMINI.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;GEMINI.md&lt;/code&gt; in workspace folders and their parents&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few details change how you should read that table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Codex&lt;/strong&gt; checks each folder for &lt;code&gt;AGENTS.override.md&lt;/code&gt;, then &lt;code&gt;AGENTS.md&lt;/code&gt;, then any names you added in &lt;code&gt;project_doc_fallback_filenames&lt;/code&gt;, and takes at most one file per folder (&lt;a href="https://learn.chatgpt.com/docs/agent-configuration/agents-md" rel="noopener noreferrer"&gt;Codex AGENTS.md docs&lt;/a&gt;). If it can't find a project root, it only checks the folder you're in. That's exactly the Downloads case: not a Git repo, so the file in Downloads is the project file. Forever also notes that Codex only obeys an &lt;code&gt;AGENTS.md&lt;/code&gt; in a project it trusts, and that once you've worked in Downloads before, it qualifies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code&lt;/strong&gt; walks &lt;em&gt;up&lt;/em&gt;, not just down. Start it in &lt;code&gt;foo/bar/&lt;/code&gt; and it loads &lt;code&gt;foo/bar/CLAUDE.md&lt;/code&gt; and &lt;code&gt;foo/CLAUDE.md&lt;/code&gt; at launch, plus files in subfolders later, once it reads or edits a file there (&lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;memory docs&lt;/a&gt;). Since v2.1.277 it reads &lt;code&gt;AGENTS.md&lt;/code&gt; too, but by default only when there's no &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 your folder or above it. In an interactive session it prints a line like &lt;code&gt;no CLAUDE.md found; AGENTS.md loaded: /home/you/repo/AGENTS.md&lt;/code&gt;, which is worth watching for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor&lt;/strong&gt; reads &lt;code&gt;AGENTS.md&lt;/code&gt; in the project root and in subfolders, and applies a nested file when you work with files in that folder, with more specific instructions taking precedence (&lt;a href="https://cursor.com/docs/rules" rel="noopener noreferrer"&gt;rules docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gemini CLI&lt;/strong&gt; searches for &lt;code&gt;GEMINI.md&lt;/code&gt; in your workspace folders and their parents, and also scans a folder and its ancestors when a tool touches a file there (&lt;a href="https://github.com/google-gemini/gemini-cli/blob/main/docs/cli/gemini-md.md" rel="noopener noreferrer"&gt;GEMINI.md docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The common thread: a file you never opened can end up in the agent's context just because of where it sits relative to where you started.&lt;/p&gt;

&lt;h2&gt;
  
  
  A 5-step check before you run the agent
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Start from the project folder, never Downloads or home
&lt;/h3&gt;

&lt;p&gt;Open a terminal in the repository you mean to work on. Claude Code's own &lt;a href="https://code.claude.com/docs/en/security" rel="noopener noreferrer"&gt;security docs&lt;/a&gt; push in the same direction: if you start it directly in your home directory, trust is held for that session only and the prompt comes back every launch, and the docs suggest starting from a project subfolder instead. For Downloads, Forever's advice is simply not to start any agent there.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. List the instruction files it could load
&lt;/h3&gt;

&lt;p&gt;Before launching, see what's above, around and below you. Here's a small bash sketch that lists the usual filenames; adapt the list to the tools you use:&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;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# List instruction files a coding agent could load if started here.&lt;/span&gt;
&lt;span class="nv"&gt;names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"AGENTS.md AGENTS.override.md CLAUDE.md CLAUDE.local.md GEMINI.md .claude/CLAUDE.md .claude/AGENTS.md"&lt;/span&gt;

&lt;span class="c"&gt;# This folder and every folder above it&lt;/span&gt;
&lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;while&lt;/span&gt; :&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  for &lt;/span&gt;n &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;$names&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"here/above: &lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;$n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;done&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; / &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;break
  dir&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;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$dir&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Global files in your home directory&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;f &lt;span class="k"&gt;in&lt;/span&gt; ~/.codex/AGENTS.md ~/.codex/AGENTS.override.md ~/.claude/CLAUDE.md ~/.gemini/GEMINI.md&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"global: &lt;/span&gt;&lt;span class="nv"&gt;$f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Subfolders (loaded later, when the agent works there)&lt;/span&gt;
find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-mindepth&lt;/span&gt; 2 &lt;span class="nt"&gt;-name&lt;/span&gt; node_modules &lt;span class="nt"&gt;-prune&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="se"&gt;\(&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; AGENTS.md &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; AGENTS.override.md &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; CLAUDE.md &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; CLAUDE.local.md &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nt"&gt;-name&lt;/span&gt; GEMINI.md &lt;span class="se"&gt;\)&lt;/span&gt; &lt;span class="nt"&gt;-print&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^/below: /'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use Codex fallback names, add them to the list. Anything you don't recognise is the thing to read next.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Read the files you didn't write
&lt;/h3&gt;

&lt;p&gt;You don't need to audit every line of your own &lt;code&gt;AGENTS.md&lt;/code&gt;. You do need to read a file that came with a fresh clone, a download or a teammate's branch. The file Forever captured was about 13 KB of bland notes, with the attack in one "Local developer environment" section near the end, so skimming the top isn't enough. A quick search for the risky parts helps:&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;grep&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"mcp_servers|config&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;toml|settings&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="s2"&gt;json|BASE_URL|dangerously|bypass|npx|npm install|curl|launchctl"&lt;/span&gt; AGENTS.md CLAUDE.md 2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of those words is proof of anything. They're the places where an instruction file stops describing your project and starts telling the agent to change its own setup or fetch something.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Confirm what actually loaded
&lt;/h3&gt;

&lt;p&gt;Once you've read the files, check that the agent sees what you expect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Claude Code:&lt;/strong&gt; watch for the &lt;code&gt;AGENTS.md loaded:&lt;/code&gt; line, or run &lt;code&gt;/context&lt;/code&gt; and look under Memory files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex:&lt;/strong&gt; the docs suggest &lt;code&gt;codex --ask-for-approval never "Summarize the current instructions."&lt;/code&gt; from the repo root, or opting into a plaintext log with &lt;code&gt;codex -c log_dir=./.codex-log&lt;/code&gt; and checking &lt;code&gt;codex-tui.log&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treat this as confirmation, not your first line of defence. Asking an agent to summarise its instructions still means starting it with those instructions loaded.&lt;/p&gt;

&lt;p&gt;If ancestor files keep getting in the way, Claude Code's &lt;code&gt;claudeMdExcludes&lt;/code&gt; setting skips specific &lt;code&gt;CLAUDE.md&lt;/code&gt; files by path or glob, and Codex's &lt;code&gt;project_root_markers&lt;/code&gt; controls what counts as a project root.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Keep the brakes on, and check the agent's own config
&lt;/h3&gt;

&lt;p&gt;Leave sandboxes and approvals on. Codex's &lt;a href="https://learn.chatgpt.com/docs/agent-approvals-security" rel="noopener noreferrer"&gt;approvals and security docs&lt;/a&gt; say the CLI and IDE extension default to no network access and writes limited to the active workspace; for chat or planning without changes, switch to read-only with &lt;code&gt;/permissions&lt;/code&gt;. Be very suspicious of any instruction that asks for &lt;code&gt;--dangerously-bypass-approvals-and-sandbox&lt;/code&gt;, which is the flag the captured file had the model use.&lt;/p&gt;

&lt;p&gt;Then look where the attack actually landed. Forever's checklist: an unexpected &lt;code&gt;[mcp_servers.*]&lt;/code&gt; block or a changed &lt;code&gt;chatgpt_base_url&lt;/code&gt; in &lt;code&gt;~/.codex/config.toml&lt;/code&gt;, and an &lt;code&gt;ANTHROPIC_BASE_URL&lt;/code&gt;, proxy or other endpoint you didn't set in a project's &lt;code&gt;.claude/settings.json&lt;/code&gt;. If you find one, they advise assuming the token leaked and rotating it, and treating any machine with &lt;code&gt;function-color&lt;/code&gt; installed as compromised.&lt;/p&gt;

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

&lt;ol&gt;
&lt;li&gt;Don't start any coding agent in &lt;code&gt;~/Downloads&lt;/code&gt; or directly in your home folder.&lt;/li&gt;
&lt;li&gt;Know the discovery rule for each tool you use: Codex goes from the Git root down, Claude Code goes up from where you start.&lt;/li&gt;
&lt;li&gt;List instruction files above, in and below your folder before launching.&lt;/li&gt;
&lt;li&gt;Read every instruction file you didn't write, all the way to the end.&lt;/li&gt;
&lt;li&gt;Search for config edits, base URLs, bypass flags and installs in those files.&lt;/li&gt;
&lt;li&gt;Confirm what loaded with &lt;code&gt;/context&lt;/code&gt; or the Codex log.&lt;/li&gt;
&lt;li&gt;Keep sandbox and approval defaults on.&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;~/.codex/config.toml&lt;/code&gt; and &lt;code&gt;.claude/settings.json&lt;/code&gt; for entries you didn't add.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;If you want to write a good &lt;code&gt;AGENTS.md&lt;/code&gt; of your own, the free &lt;a href="https://promptmasterstore.gumroad.com/l/agents-md-cheat-sheet?utm_source=devto" rel="noopener noreferrer"&gt;AGENTS.md Quick-Start Cheat Sheet&lt;/a&gt; covers what belongs in the file, what to leave out, the one-line test for each line, and cross-tool use including the Claude Code symlink, in 6 pages.&lt;/p&gt;

&lt;p&gt;For the full picture, &lt;a href="https://promptmasterstore.gumroad.com/l/agents-md-guide?utm_source=devto" rel="noopener noreferrer"&gt;AGENTS.md: The Complete Guide to the Cross-Tool Agent Standard&lt;/a&gt; spends 42 pages on how agents find and load the file, the monorepo hierarchy with nearest-file precedence, cross-tool portability, the common mistakes and a 30-day adoption plan.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article was written with the assistance of AI and reviewed by the author.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>codex</category>
      <category>claude</category>
    </item>
  </channel>
</rss>
