<?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: Pawel</title>
    <description>The latest articles on DEV Community by Pawel (@pawel_nowak).</description>
    <link>https://dev.to/pawel_nowak</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%2F4159320%2F2695c3e7-e63c-4b61-a949-c031452769bf.jpg</url>
      <title>DEV Community: Pawel</title>
      <link>https://dev.to/pawel_nowak</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pawel_nowak"/>
    <language>en</language>
    <item>
      <title>5 JsonLogic Mistakes Every AI Coding Agent Makes</title>
      <dc:creator>Pawel</dc:creator>
      <pubDate>Sat, 10 Oct 2026 16:32:57 +0000</pubDate>
      <link>https://dev.to/pawel_nowak/5-jsonlogic-mistakes-every-ai-coding-agent-makes-2p3a</link>
      <guid>https://dev.to/pawel_nowak/5-jsonlogic-mistakes-every-ai-coding-agent-makes-2p3a</guid>
      <description>&lt;p&gt;Ask any LLM to write a JsonLogic rule and it will happily produce something that &lt;em&gt;looks&lt;/em&gt; right. Then you run it and get &lt;code&gt;false&lt;/code&gt; when you expected &lt;code&gt;true&lt;/code&gt;, or an operator your library never implemented. I got tired of debugging hallucinated rules, so I packaged the fix as an open-source agent skill: &lt;a href="https://github.com/valeryia-piatrova/jsonlogic-skill" rel="noopener noreferrer"&gt;jsonlogic-skill&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Drop it into your AI coding assistant and it picks JsonLogic up automatically. You describe the business requirement in plain language, and the assistant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;turns it into a &lt;strong&gt;valid&lt;/strong&gt; JsonLogic rule (checked against the real standard, not vibes),&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;runs&lt;/strong&gt; the rule against your data,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;validates&lt;/strong&gt; whether an existing rule is well-formed,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;explains&lt;/strong&gt; what a rule does, in words,&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;debugs&lt;/strong&gt; rules that return the wrong result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It knows the actual operator set from &lt;a href="https://jsonlogic.com" rel="noopener noreferrer"&gt;jsonlogic.com&lt;/a&gt; and, importantly, checks whether your language's JsonLogic library supports the operators it wants to use. JavaScript, Python, Ruby, PHP, Java: one rule, many runtimes, no surprises.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;It follows the open &lt;a href="https://agentskills.io" rel="noopener noreferrer"&gt;Agent Skills&lt;/a&gt; format, so it works with Claude Code, Cursor, GitHub Copilot, VS Code agent mode, Codex, and Gemini CLI. For Claude Code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/valeryia-piatrova/jsonlogic-skill.git ~/.claude/skills/jsonlogic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart the client and you are done. Every other client has its own one-liner in &lt;a href="https://github.com/valeryia-piatrova/jsonlogic-skill/blob/main/docs/installing.md" rel="noopener noreferrer"&gt;docs/installing.md&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick example
&lt;/h2&gt;

&lt;p&gt;You type:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Give a 20% discount on orders over $200, or 10% over $100, otherwise no discount&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The assistant produces:&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;"if"&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;"&amp;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;"var"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"total"&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="mi"&gt;200&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="mf"&gt;0.20&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;"&amp;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;"var"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"total"&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="mi"&gt;100&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="mf"&gt;0.10&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="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and validates it before showing it to you. No data shape? It asks. Data shape given? Field names come from your JSON, not from the model's imagination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a skill and not a prompt
&lt;/h2&gt;

&lt;p&gt;A prompt tells the model what JsonLogic is. A skill gives it a workflow: gather the data shape, write the rule incrementally, validate it with a real script, then answer. That validate-before-you-answer loop is the whole difference between "looks right" and "runs right."&lt;/p&gt;

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

&lt;p&gt;The repo is MIT licensed and free: &lt;a href="https://github.com/valeryia-piatrova/jsonlogic-skill" rel="noopener noreferrer"&gt;github.com/valeryia-piatrova/jsonlogic-skill&lt;/a&gt;. If it saves you a debugging session, leave a star. And if you hit a JsonLogic edge case it handles badly, open an issue: real-world rules are how it gets better.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>jsonlogic</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Trust Boundary Is Now Whoever Wrote the Last Mod You Installed</title>
      <dc:creator>Pawel</dc:creator>
      <pubDate>Wed, 07 Oct 2026 14:03:49 +0000</pubDate>
      <link>https://dev.to/pawel_nowak/the-trust-boundary-is-now-whoever-wrote-the-last-mod-you-installed-1i06</link>
      <guid>https://dev.to/pawel_nowak/the-trust-boundary-is-now-whoever-wrote-the-last-mod-you-installed-1i06</guid>
      <description>&lt;p&gt;Every extension system for AI coding agents has quietly moved the trust boundary. Nobody announced it. It just happened, one layer at a time, and Claude Code mods are the layer where it finally becomes impossible to ignore.&lt;/p&gt;

&lt;p&gt;Let me trace the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skills were prompts
&lt;/h2&gt;

&lt;p&gt;A skill is a markdown file of instructions the agent reads. The trust question was always about the &lt;em&gt;text&lt;/em&gt;: is this guidance good, is it poisoned, does it tell the model to do something I would not want? The blast radius was bounded by what the model could be talked into. A malicious skill could social-engineer the agent, but it could not &lt;em&gt;do&lt;/em&gt; anything the agent would not do on its own. The boundary was between you and the words.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP servers were services
&lt;/h2&gt;

&lt;p&gt;Then came MCP: external processes that hand the agent new tools. Now the trust question included code, but code running &lt;em&gt;over there&lt;/em&gt;, in its own process, behind a tool interface. An MCP server could exfiltrate data through tool results or lie about what a tool does, and researchers found plenty of both. But there was still a membrane: the agent called the tool, the tool returned, the agent decided. The boundary was between the agent and the service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mods are inside the process
&lt;/h2&gt;

&lt;p&gt;A mod is a module loaded into Claude Code's own runtime. It does not wait to be called. It sees every event as it happens: your prompts, every tool call, every turn, every pixel of the interface as it is drawn. And it can do three things no previous layer could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;See first, answer last.&lt;/strong&gt; Hooks form a chain, and the first mod to load sees each event first and the result last. That includes the events your audit logger was counting on. A mod loaded above your logger can edit what the logger records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rewrite the event mid-flight.&lt;/strong&gt; Not suggest, not return a suspicious result for the agent to interpret. Change the tool call itself before the rest of the chain sees it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Answer instead of the engine.&lt;/strong&gt; Skip the chain entirely and serve the event itself. Refuse the call. Approve the call your hooks blocked. Submit a prompt as if you typed it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The docs say it plainly: "A mod is code that runs with your permissions." It can read and write your files, start processes, make network requests, and read your secrets, including an API key you keep in environment variables or settings files.&lt;/p&gt;

&lt;p&gt;So here is the new boundary, stated without euphemism: &lt;strong&gt;the trust boundary is now whoever wrote the last mod you installed.&lt;/strong&gt; Not the model provider. Not the tool interface. A person, whose code runs inside your agent's process, with your privileges, seeing your prompts before you finish reading them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this feels different from "just install trusted software"
&lt;/h2&gt;

&lt;p&gt;The usual response is: this is just software supply chain, we have always trusted our dependencies. And there is truth in it. You trust your editor's extensions, your npm packages, your shell plugins.&lt;/p&gt;

&lt;p&gt;But there is a difference in &lt;em&gt;position&lt;/em&gt;. Your npm packages run when your code runs them. Your editor extensions run in a host that sandboxes them from each other. A mod runs inside the agent loop itself, at the exact point where your intent becomes action. It is not a dependency of your program. It is a participant in your thinking.&lt;/p&gt;

&lt;p&gt;Consider the permission prompt, the one UI element Anthropic hardened. A mod can restyle almost the entire interface but cannot touch the permission dialog or change what it shows you. That single exception tells you the threat model: the mod is untrusted code with your privileges, and the permission prompt is the last honest surface in the room. They protected the one moment where your eyes are the security control, because everything else is already inside the wire.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we do not have yet
&lt;/h2&gt;

&lt;p&gt;Here is the uncomfortable part: the ecosystem is two days old and the answers do not exist yet.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No capability model.&lt;/strong&gt; A context-bar mod and a security guard request the same &lt;code&gt;$&lt;/code&gt; API. There is no "this mod only draws" permission to grant. &lt;code&gt;claude plugin validate&lt;/code&gt; lists what a mod &lt;em&gt;asks&lt;/em&gt; Claude Code to do, which is transparency, not least privilege.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No signatures, no provenance chain.&lt;/strong&gt; Marketplaces, awesome lists, a community scoreboard grading reach levels, all useful, all informal. (Disclosure: I run one of these directories, &lt;a href="https://aifamily.website" rel="noopener noreferrer"&gt;aifamily.website&lt;/a&gt;, indexing 1,600+ mods with a transparent access profile per mod, because discovery is where trust starts.) Nothing cryptographically binds a mod to its author at install time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No conflict story.&lt;/strong&gt; Two mods hooking the same event: load order decides. Load order is configuration, and configuration is the thing attackers love most.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Anthropic shipped the escape hatches: &lt;code&gt;disableAllHooks&lt;/code&gt;, &lt;code&gt;allowManagedModsOnly&lt;/code&gt; for organizations, &lt;code&gt;--safe-mode&lt;/code&gt; for a session. Those are off switches, not a trust model. The trust model is still "install mods only from authors and marketplaces you trust", which is the same sentence every platform says right before its first big incident.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question I keep coming back to
&lt;/h2&gt;

&lt;p&gt;We built the most privileged extension point in the history of dev tools, code running inside the agent, seeing events before the user, answering instead of the engine, and we called it a "mod", a word borrowed from game skins.&lt;/p&gt;

&lt;p&gt;Maybe that is fine. Maybe the right answer is capability-scoped permissions and signed mods within a year, and this essay ages like every other "the sky is falling" post. Or maybe the first serious incident comes from a 40-line UI mod that nobody audited because it was small, and small felt safe.&lt;/p&gt;

&lt;p&gt;I genuinely do not know which. What I know is that the boundary moved while we were looking at the demo gifs, and it is worth saying out loud where it sits now.&lt;/p&gt;

&lt;p&gt;So: what would it take for you to install a mod from a stranger? A signature? A scoreboard? Or is "trust the author" actually enough, and I am overthinking this?&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>security</category>
      <category>ai</category>
      <category>discuss</category>
    </item>
    <item>
      <title>I Replaced All My Hooks With Mods. Here's What Changed.</title>
      <dc:creator>Pawel</dc:creator>
      <pubDate>Tue, 06 Oct 2026 13:55:45 +0000</pubDate>
      <link>https://dev.to/pawel_nowak/i-replaced-all-my-hooks-with-mods-heres-what-changed-nj5</link>
      <guid>https://dev.to/pawel_nowak/i-replaced-all-my-hooks-with-mods-heres-what-changed-nj5</guid>
      <description>&lt;p&gt;I had five settings hooks. A guard that blocked &lt;code&gt;rm -rf&lt;/code&gt;. A logger that recorded every file edit. A notifier that pinged me when a long turn finished. An auto-approver for safe reads. And a context counter I duct-taped onto the status line.&lt;/p&gt;

&lt;p&gt;Last weekend I migrated all five to Claude Code mods. The honest ledger: three got shorter, one turned out to be impossible as a hook in the first place, and one opened a hole I had not thought about.&lt;/p&gt;

&lt;p&gt;Here is the before and after, with code.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick refresher: what hooks actually are
&lt;/h2&gt;

&lt;p&gt;A settings hook is a shell command that Claude Code runs on a lifecycle event, passing JSON over stdin and stdout. Your script reads the event, and decides: let it through, block it, or tweak it. It is simple, it is battle-tested, and it runs in a subprocess that starts, does its job, and dies.&lt;/p&gt;

&lt;p&gt;A mod is different. It loads once, stays in the session, keeps state, and its hooks are middleware: observe, rewrite, or answer each event in-process. It can also draw UI and call back into Claude Code.&lt;/p&gt;

&lt;p&gt;The docs put it well: if a settings hook, a skill, or an MCP server already does what you need, compare before you write a mod. I did the comparing. Here is what I found.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration 1: the guard (shorter)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before.&lt;/strong&gt; A &lt;code&gt;PreToolUse&lt;/code&gt; hook in &lt;code&gt;settings.json&lt;/code&gt; pointing at a bash script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PreToolUse"&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;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~/.claude/hooks/guard.sh"&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;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;# guard.sh: read the event JSON from stdin, block risky commands&lt;/span&gt;
&lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.tool_input.command // empty'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"rm -rf"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="s2"&gt;"git push --force"&lt;/span&gt;&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Blocked: too risky to run unattended"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
    &lt;span class="nb"&gt;exit &lt;/span&gt;2 &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&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;Two files, a &lt;code&gt;jq&lt;/code&gt; dependency, a subprocess spawned on every single Bash call, and the "UI" is a line of stderr.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After.&lt;/strong&gt; One module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;RISKY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sr"&gt;/rm -rf/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/git push --force/&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool.call&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;RISKY&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cmd&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="na"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Blocked: too risky to run unattended&lt;/span&gt;&lt;span class="dl"&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;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;No subprocess per event. No &lt;code&gt;jq&lt;/code&gt;. The deny path is a first-class return value, not an exit code convention. And because the mod is in-process, the same guard can later grow a UI, a panel with "what would this change" and Proceed/Cancel buttons, like Anthropic's own &lt;code&gt;blast-radius&lt;/code&gt; sample mod. A hook can never do that part.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict: shorter, and upgradeable.&lt;/strong&gt; This is the migration that sells itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration 2: the notifier (impossible as a hook)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before.&lt;/strong&gt; My "tell me when a long turn finishes" hook. The problem: a settings hook can allow, block, or log an event. It cannot draw anything. The closest I got was appending to a log file and having a separate terminal tailing it. It worked, and it was embarrassing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After.&lt;/strong&gt; A mod hooks &lt;code&gt;turn.complete&lt;/code&gt; and draws. The official docs use exactly this as their canonical example: count tool calls and show the count beside the spinner while Claude works, &lt;code&gt;Thinking · tool calls: 3…&lt;/code&gt;, updating live. My notifier became a small band above the prompt showing turn duration and token delta, updating after every turn.&lt;/p&gt;

&lt;p&gt;This one was not a migration. It was a capability that did not exist in my setup before. Hooks observe the world through a keyhole; mods live in the room.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict: impossible before, trivial now.&lt;/strong&gt; If your hook wishlist ever included the words "show me", this is your reason to migrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migration 3: the auto-approver (the hole)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before.&lt;/strong&gt; A hook that approved safe reads. It exited 0 for &lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;ls&lt;/code&gt;, &lt;code&gt;git status&lt;/code&gt;, and let the permission system handle the rest.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After.&lt;/strong&gt; A mod that answers &lt;code&gt;tool.call&lt;/code&gt; events for the same safe list without calling &lt;code&gt;next&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And here is the part I did not expect. While testing, I learned something from the docs that stopped me cold:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A mod that approves tool calls can approve one that an &lt;code&gt;ask&lt;/code&gt; rule would prompt for, or that one of your own PreToolUse hooks blocked.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;My old hook setup had a clear hierarchy: hooks were the bouncer, the permission rules were the policy. A mod does not sit inside that hierarchy. It sits &lt;em&gt;above&lt;/em&gt; it. A mod can approve the exact call my Migration 1 guard denies. The guard and the approver are now peers in a middleware chain, and chain order decides who wins.&lt;/p&gt;

&lt;p&gt;I am not saying "do not migrate your approver". I am saying: when you do, you are moving a security decision from a subprocess the engine invokes into code that runs with your full permissions and can overrule the other guards. Audit the chain order. Write a test that asserts your guard still denies when the approver is loaded. &lt;code&gt;claude plugin test&lt;/code&gt; exists for exactly this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict: migrated, but it changed the trust model.&lt;/strong&gt; This is the one migration I would do differently: I would have read the permissions docs first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrations 4 and 5: the boring ones (kept as hooks, then moved anyway)
&lt;/h2&gt;

&lt;p&gt;The file-edit logger and the context counter. Both were pure "observe and record" hooks with no UI and no decisions. The docs are honest about this case: if a hook already does what you need, a mod is not automatically better.&lt;/p&gt;

&lt;p&gt;I migrated them anyway, for one reason: state. A settings hook is stateless across events; every invocation starts cold. My logger shelled out to append to a JSONL file, and the counter re-read it. The mod versions keep counters in memory (and in &lt;code&gt;$.state&lt;/code&gt;, which survives hot reloads) and share them between hooks, one hook counts, another draws. Less I/O, no temp files, no parsing.&lt;/p&gt;

&lt;p&gt;Were they shorter? Marginally. Was it worth it? For the shared state alone, yes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell you before you start
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Migrate the UI-shaped ones first.&lt;/strong&gt; Anything your hooks do that wishes it could draw, notifiers, dashboards, review views, is where mods win by a mile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the pure block/allow/log hooks if they work.&lt;/strong&gt; A mod is not a moral upgrade. It is a different tool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the chain, not just the mod.&lt;/strong&gt; &lt;code&gt;claude plugin validate&lt;/code&gt; checks one plugin. &lt;code&gt;claude plugin test&lt;/code&gt; exercises hooks against the real engine. Neither tells you what happens when your guard and your approver load together. Write that test yourself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-read the permissions docs after migrating.&lt;/strong&gt; Your mental model of "hooks enforce policy" does not survive contact with mods. The mod is code with your permissions, and it can approve what your hooks blocked.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Three shorter, one newly possible, one that taught me something uncomfortable. Not a bad weekend.&lt;/p&gt;

&lt;p&gt;Which of your hooks is secretly wishing it could draw UI? That is the one to migrate first.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>tutorial</category>
      <category>ai</category>
      <category>devops</category>
    </item>
    <item>
      <title>The Guard I Installed Was Enabled, Running, and Doing Nothing</title>
      <dc:creator>Pawel</dc:creator>
      <pubDate>Mon, 05 Oct 2026 14:02:35 +0000</pubDate>
      <link>https://dev.to/pawel_nowak/the-guard-i-installed-was-enabled-running-and-doing-nothing-589a</link>
      <guid>https://dev.to/pawel_nowak/the-guard-i-installed-was-enabled-running-and-doing-nothing-589a</guid>
      <description>&lt;p&gt;Last week I installed a guard mod. It was supposed to hold risky shell commands, &lt;code&gt;rm -rf&lt;/code&gt;, force pushes, the usual suspects, and show me what they would change before letting them run.&lt;/p&gt;

&lt;p&gt;It was listed in &lt;code&gt;/plugin&lt;/code&gt;. It was enabled. It did absolutely nothing.&lt;/p&gt;

&lt;p&gt;I spent an embarrassing amount of time re-reading my hook code. The code was fine. The harness had quietly discarded my mod before a single event ever reached it, and it never told me in any place I was looking.&lt;/p&gt;

&lt;p&gt;If your mod is "working" but silent, run this checklist first. In order of likelihood, none of these is your code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 1: the project was never trusted
&lt;/h2&gt;

&lt;p&gt;This one got me first. Claude Code only adopts a plugin from a project's &lt;code&gt;.claude/&lt;/code&gt; folder after you accept the trust dialog, the record lives in &lt;code&gt;~/.claude.json&lt;/code&gt; under &lt;code&gt;projects[&amp;lt;path&amp;gt;].hasTrustDialogAccepted&lt;/code&gt;. Until then, an untrusted folder's &lt;code&gt;.claude/&lt;/code&gt; is repository content as far as the engine is concerned. It is not read at all.&lt;/p&gt;

&lt;p&gt;The nasty variant: &lt;code&gt;claude -p&lt;/code&gt; never shows the trust prompt. A headless run in a fresh folder will never load your project mod, and it will not complain about it either.&lt;/p&gt;

&lt;p&gt;Two fixes, pick one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open &lt;code&gt;claude&lt;/code&gt; interactively in the folder and accept the prompt.&lt;/li&gt;
&lt;li&gt;Load it explicitly with &lt;code&gt;--plugin-dir .claude/skills/{name}&lt;/code&gt;, which loads it as &lt;code&gt;{name}@inline&lt;/code&gt; without needing trust.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;User-level plugins under &lt;code&gt;~/.claude/skills/{name}/&lt;/code&gt; always load. If your mod works from the user folder but not from the project folder, trust is your answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 2: I was looking in the wrong place
&lt;/h2&gt;

&lt;p&gt;My mod logged everything through &lt;code&gt;$.ui.log&lt;/code&gt;. In an interactive session, that appends a dim line to the transcript. In &lt;code&gt;claude -p&lt;/code&gt; or the Agent SDK, there is no transcript and no status row, so those lines go exactly one place: the debug log.&lt;/p&gt;

&lt;p&gt;The debug log lives at &lt;code&gt;~/.claude/debug/&amp;lt;session-id&amp;gt;.txt&lt;/code&gt;. Note the extension: &lt;code&gt;.txt&lt;/code&gt;, not &lt;code&gt;.log&lt;/code&gt;. There is a &lt;code&gt;latest&lt;/code&gt; symlink pointing at the newest one, which saves you from guessing session ids.&lt;/p&gt;

&lt;p&gt;The transcript also carries a dim line naming the mod and the reason when something fails to load. And &lt;code&gt;claude --debug&lt;/code&gt; logs the load sequence itself. I now run every mod debugging session with &lt;code&gt;--debug&lt;/code&gt; from the start. It is the difference between guessing and reading.&lt;/p&gt;

&lt;p&gt;Also worth knowing: a mod that draws UI can check which surface it is on. Drawing only happens in the terminal and the Desktop app's Code tab. In the VS Code chat panel, &lt;code&gt;claude -p&lt;/code&gt;, or a cloud session, your hooks run but your panes and bands simply do not exist. If your mod is a UI mod and you tested it headless, "doing nothing" is the correct behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 3: my options were under the wrong key
&lt;/h2&gt;

&lt;p&gt;Plugin options are read from user or managed settings under &lt;code&gt;pluginConfigs&lt;/code&gt;, keyed by the plugin's &lt;strong&gt;full id&lt;/strong&gt;, and the full id depends on how the plugin loaded:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-loaded from &lt;code&gt;.claude/skills/&lt;/code&gt;: the key is &lt;code&gt;"{name}@skills-dir"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Loaded with &lt;code&gt;--plugin-dir&lt;/code&gt;: the key is &lt;code&gt;"{name}"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I had mine under the bare name while the mod loaded as &lt;code&gt;{name}@skills-dir&lt;/code&gt;. Every option silently fell back to its default. No error, no warning in the session. The debug log does say &lt;code&gt;plugin {name}: no pluginConfigs["{name}@skills-dir"].options in user, --settings or managed settings&lt;/code&gt;, which is precise and completely invisible unless you are reading the log (see mistake 2).&lt;/p&gt;

&lt;p&gt;One more trap inside this trap: options are read from user and managed settings, never from project settings. Putting them in the project's &lt;code&gt;.claude/settings.json&lt;/code&gt; is the same as not setting them.&lt;/p&gt;

&lt;p&gt;The fix looks like this in &lt;code&gt;~/.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pluginConfigs"&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;"my-guard@skills-dir"&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;"options"&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;"blockPatterns"&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;"rm -rf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git push --force"&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;Get the key wrong and you get defaults. Get the file wrong and you get defaults. The mod will not tell you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mistake 4: something turned the whole thing off
&lt;/h2&gt;

&lt;p&gt;During early access, mods sat behind a rollout flag, and with the flag off the debug log said &lt;code&gt;installed plugins' hooks modules not loaded: rollout flag is off&lt;/code&gt;. In 2.1.287 mods are on by default, so this specific trap is mostly history. But its modern equivalents are alive and well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;"disableAllHooks": true&lt;/code&gt; in &lt;code&gt;~/.claude/settings.json&lt;/code&gt; stops your mods (and your settings hooks, and your custom status line) in every session.&lt;/li&gt;
&lt;li&gt;An organization's &lt;code&gt;allowManagedModsOnly&lt;/code&gt; stops user-installed mods from loading while the rest of the plugin keeps working. Your skills and commands load fine, your mod does not, and nothing in the session explains the difference.&lt;/li&gt;
&lt;li&gt;Anthropic can also turn off specific built-in pieces remotely; the plugin-authoring skill that lets Claude write mods is subject to that.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cruel detail: built-in mods load regardless of these flags. So &lt;code&gt;/plugin&lt;/code&gt; can show mods active, your session can look completely normal, and your mod is still not among them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checklist, in order
&lt;/h2&gt;

&lt;p&gt;Before you touch your module code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;/plugin&lt;/code&gt; in an interactive session. Look for the dim line: &lt;code&gt;N mod active · name&lt;/code&gt;. If yours is not named there, it never loaded. Stop debugging code.&lt;/li&gt;
&lt;li&gt;Trust the project, or load with &lt;code&gt;--plugin-dir&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Read the debug log at &lt;code&gt;~/.claude/debug/latest&lt;/code&gt; (remember: &lt;code&gt;.txt&lt;/code&gt;). Search for your plugin's name and for &lt;code&gt;pluginConfigs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Check your options key against the full plugin id, in user settings, not project settings.&lt;/li&gt;
&lt;li&gt;Check &lt;code&gt;disableAllHooks&lt;/code&gt; and any managed settings your org deploys.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Four of my last five "broken" mods were fixed by this list without changing a line of module code. The fifth was a typo in &lt;code&gt;hooks.json&lt;/code&gt;, which &lt;code&gt;claude plugin validate&lt;/code&gt; caught in three seconds. Run that too. Always run that.&lt;/p&gt;

&lt;p&gt;What is the most silent failure you have hit with Claude Code mods so far? I have a feeling mistake 3 has bitten more people than will admit it.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>debugging</category>
      <category>tutorial</category>
      <category>ai</category>
    </item>
    <item>
      <title>Claude Code Mods Aren't Sandboxed. Neither Is Your Understanding of "Sandboxed".</title>
      <dc:creator>Pawel</dc:creator>
      <pubDate>Sun, 04 Oct 2026 13:49:49 +0000</pubDate>
      <link>https://dev.to/pawel_nowak/claude-code-mods-arent-sandboxed-neither-is-your-understanding-of-sandboxed-77m</link>
      <guid>https://dev.to/pawel_nowak/claude-code-mods-arent-sandboxed-neither-is-your-understanding-of-sandboxed-77m</guid>
      <description>&lt;p&gt;Two statements about Claude Code mods, both true, both from Anthropic's own docs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The module runs in a sandbox of its own, with no DOM and no Node.&lt;/li&gt;
&lt;li&gt;"Mods aren't sandboxed."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first two days of the mods launch have been one long argument about which of these is the lie. Neither is. The confusion comes from the word "sandbox" doing two different jobs, and once you separate them, the real trust boundary snaps into focus. It is not where most people think it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sandbox #1: the JavaScript runtime
&lt;/h2&gt;

&lt;p&gt;When you write a mod, your module gets a private little world. No &lt;code&gt;document&lt;/code&gt;. No &lt;code&gt;window&lt;/code&gt;. No Node globals. If your code wants to touch anything outside itself, files, processes, the network, the UI, it goes through one object: &lt;code&gt;$&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool.call&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/tmp&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// through $, not fs&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&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;This is sandbox #1. It is a language-level sandbox: it constrains &lt;em&gt;how&lt;/em&gt; your code reaches the outside world, not &lt;em&gt;whether&lt;/em&gt; it can. Every capability lives behind the &lt;code&gt;$&lt;/code&gt; API, which means the engine can see every request, log it, and (in theory) gate it. Think of it like a phone where every app must use the official APIs. The APIs still include the camera.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sandbox #2: the one that does not exist
&lt;/h2&gt;

&lt;p&gt;Here is what the documentation says, and I am quoting because paraphrasing would soften it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A mod is code that runs with your permissions. It can read and write your files, start processes, and make network requests. Install mods only from authors and marketplaces you trust."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Mods aren't sandboxed. If you turn on sandboxing, the sandbox isolates the Bash commands Claude runs, and a process that a mod starts runs outside it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read that second one twice. Claude Code has a sandboxing feature, and it sandboxes &lt;em&gt;Claude's&lt;/em&gt; Bash commands. A mod that starts its own process steps cleanly outside of it. The fence was built around the agent, not around the extension.&lt;/p&gt;

&lt;p&gt;The capability list gets worse the further you read. A mod can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Read your secrets&lt;/strong&gt;: environment variables and settings files, "including an API key you keep in either".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Approve tool calls you blocked&lt;/strong&gt;: a mod that approves tool calls can green-light one "that an ask rule would prompt for, or that one of your own PreToolUse hooks blocked". Your hook said no. The mod says yes. The mod wins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rewrite events before you see them&lt;/strong&gt;: hooks form a chain, and a mod can observe, rewrite, or fully answer any event. Including the ones your audit logger was counting on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So when someone says "mods are sandboxed", they are describing the JS runtime. When Anthropic says "mods aren't sandboxed", they are describing your files, your processes, your network, and your API keys. Both true. The second one is the one that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one hard boundary
&lt;/h2&gt;

&lt;p&gt;There is exactly one thing Anthropic drew a hard line around, and it is telling:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A mod can restyle much of Claude Code's interface, but not the permission prompt. It can't change what a prompt shows you."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A mod can redraw almost the entire UI, panes, bands above the prompt, toasts, but the permission dialog stays Claude Code's own. Whatever else a mod draws, the moment that asks "are you sure?" cannot be faked, restyled, or have its contents swapped.&lt;/p&gt;

&lt;p&gt;Think about why that specific line exists. If a mod could restyle the permission prompt, it could show you "run tests?" while actually approving &lt;code&gt;rm -rf&lt;/code&gt;. Anthropic hardened the one UI element where your eyes are the security control, and left everything else open. That tells you exactly what threat model they designed for: the mod is untrusted code with your privileges, and the permission prompt is the last honest surface in the room.&lt;/p&gt;

&lt;h2&gt;
  
  
  The mental model that actually works
&lt;/h2&gt;

&lt;p&gt;Stop picturing a browser iframe. The right mental model is much older and much simpler:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installing a mod is giving someone your shell.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not a restricted shell. Not a shell with an auditor watching. Your shell, with your env vars, your files, your network, and the ability to overrule the guardrails you built for the agent. The JS-runtime sandbox is real engineering, it keeps modules from stepping on each other and gives the engine a clean interception point, but it was never a security boundary between the mod and your machine.&lt;/p&gt;

&lt;p&gt;Once you have that model, the hygiene checklist writes itself:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;claude plugin validate&lt;/code&gt; before you install, not after.&lt;/strong&gt; It lists the events a mod handles and what it asks Claude Code to do, file reads, network requests, without running any code. Read it like you would read the permissions screen on a phone app. If a context-bar mod wants network access, ask why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Know your off switches.&lt;/strong&gt; Disable one plugin from the Installed tab in &lt;code&gt;/plugin&lt;/code&gt;. Start a session with &lt;code&gt;--safe-mode&lt;/code&gt; to drop all customizations. Set &lt;code&gt;"disableAllHooks": true&lt;/code&gt; in &lt;code&gt;~/.claude/settings.json&lt;/code&gt; for the wide kill switch. Organizations get &lt;code&gt;allowManagedModsOnly&lt;/code&gt;, which stops user-installed mods from loading while skills, commands, and MCP servers keep working.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit the small ones hardest.&lt;/strong&gt; A 40-line UI mod feels safe because it is small. But 40 lines is enough to read &lt;code&gt;~/.claude/settings.json&lt;/code&gt;, exfiltrate an API key, and approve the tool call that covers its tracks. Size is not a security property.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of this means you should not install mods. It means you should install them the way you would hand your laptop to a colleague: only to people you trust, and knowing exactly what they can reach while they have it.&lt;/p&gt;

&lt;p&gt;So here is my question: what does your &lt;code&gt;claude plugin validate&lt;/code&gt; say about the mods you have already installed? And did you read it before, or just now?&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>security</category>
      <category>ai</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>I Built My First Claude Code Mod Without Writing Code: Claude Wrote It, Validated It, and Hot-Reloaded It</title>
      <dc:creator>Pawel</dc:creator>
      <pubDate>Sat, 03 Oct 2026 09:19:53 +0000</pubDate>
      <link>https://dev.to/pawel_nowak/i-built-my-first-claude-code-mod-without-writing-code-claude-wrote-it-validated-it-and-2mh7</link>
      <guid>https://dev.to/pawel_nowak/i-built-my-first-claude-code-mod-without-writing-code-claude-wrote-it-validated-it-and-2mh7</guid>
      <description>&lt;p&gt;I wanted one thing: to see my context window without typing &lt;code&gt;/context&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Not a dashboard. Not a plugin I install and configure for twenty minutes. Just a small bar above the prompt that tells me how full the window is, the way the &lt;code&gt;/context&lt;/code&gt; command does, but always on screen. One sentence of a prompt later, it was there.&lt;/p&gt;

&lt;p&gt;This is the story of the first Claude Code mod I built without writing a single line of code. Claude wrote it, validated it, tested it, and hot-reloaded it into my session. I just watched.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a mod actually is
&lt;/h2&gt;

&lt;p&gt;Claude Code mods (shipped in v2.1.287, on by default) are small JavaScript or TypeScript modules that live inside a plugin. They see every event in your session as it happens: tool calls, the prompt you submit, turns starting and ending, and every piece of the interface as it is drawn.&lt;/p&gt;

&lt;p&gt;That last part is the new bit. Mods can rewrite what Claude Code does and draw their own UI: a pane, a band above the prompt, a status line entry, a toast. Everything the engine draws is a component you can hook into.&lt;/p&gt;

&lt;p&gt;And here is the part that still feels strange: you do not need to learn the API to try one. You describe the mod you want, and Claude Code builds it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: ask for it
&lt;/h2&gt;

&lt;p&gt;Open any project, run &lt;code&gt;claude&lt;/code&gt;, and paste a prompt like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create a mod that draws my context window as a stacked bar above the prompt, one colour per category like /context, toggled with /context-bar.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude Code loads its own plugin-authoring skill, reads the engine's type declarations, and writes three or four files into a session folder under &lt;code&gt;~/.claude/dev-mods/&lt;/code&gt;. Then it asks one question: allow hot reloading for this session?&lt;/p&gt;

&lt;p&gt;Say yes. When the turn ends, the bar appears above your prompt.&lt;/p&gt;

&lt;p&gt;That is the moment that sold me. I did not open an editor. I did not read the docs. I described a UI in plain English, and it showed up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: make Claude validate its own homework
&lt;/h2&gt;

&lt;p&gt;Before I even looked at the code, I asked for two things while the session was still warm:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Run &lt;code&gt;claude plugin validate&lt;/code&gt; on the folder.&lt;/strong&gt; The validator reads the module the way the engine will and names anything the engine would refuse. Think of it as a compiler for mods: non-negotiable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write a &lt;code&gt;tests/&lt;/code&gt; file and run &lt;code&gt;claude plugin test&lt;/code&gt;.&lt;/strong&gt; That exercises the hooks against the real engine. No TypeScript toolchain needed on your machine.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the difference between "Claude wrote something plausible" and "the engine will actually load this". An LLM will happily produce a mod that looks right and fails at load time. Validate and test are what separate the magic from the garbage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: peek under the hood (optional, but worth it)
&lt;/h2&gt;

&lt;p&gt;Curiosity won, so I read the code. A mod is one module exporting &lt;code&gt;register&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ui.render&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;AbovePrompt&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ui&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;paddingX&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="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hello from my mod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})],&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The shape is middleware. Your hook runs, then &lt;code&gt;next(e)&lt;/code&gt; hands the event to the next plugin in the chain, then to Claude Code itself. Every hook does one of three moves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Observe:&lt;/strong&gt; call &lt;code&gt;next(e)&lt;/code&gt;, then look at the result. Record every file edit, take a reading after each turn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rewrite:&lt;/strong&gt; call &lt;code&gt;next&lt;/code&gt; with a modified event. Change what the rest of the chain sees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Answer:&lt;/strong&gt; skip &lt;code&gt;next&lt;/code&gt; entirely and serve the event yourself. Refuse a tool call, handle a slash command.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One detail I stole from Addy Osmani's guide: do not keep history in a module-level &lt;code&gt;let&lt;/code&gt;. Hot reload is a fresh load, &lt;code&gt;register&lt;/code&gt; runs again, and module variables start over. Keep readings in &lt;code&gt;$.state&lt;/code&gt;, declared in a small types contract, and they survive reloads. If you skip the contract, &lt;code&gt;claude plugin validate&lt;/code&gt; stops you with an error that names the fix. The tooling here is genuinely good.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: make it permanent
&lt;/h2&gt;

&lt;p&gt;The session folder dies with the session. Copy the mod somewhere stable:&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;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.claude/mods
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; ~/.claude/dev-mods/&amp;lt;session-id&amp;gt;/context-bar ~/.claude/mods/context-bar
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the manifest in &lt;code&gt;.claude-plugin/plugin.json&lt;/code&gt;, the &lt;code&gt;hooks/&lt;/code&gt; folder with its module, and the &lt;code&gt;types/&lt;/code&gt; contract if the mod keeps state. Copy &lt;code&gt;tests/&lt;/code&gt; too, so you can rerun the tests after an update.&lt;/p&gt;

&lt;p&gt;Then tell Claude Code where it lives. The one place that reaches every session, including the ones the desktop app starts, is the &lt;code&gt;env&lt;/code&gt; block of your user settings file. Open &lt;code&gt;~/.claude/settings.json&lt;/code&gt; and add:&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;"env"&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;"CLAUDE_CODE_PLUGIN_DIRS"&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/you/.claude/mods/context-bar"&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;Use the full path, not &lt;code&gt;~&lt;/code&gt;. Several mods are separated with a colon. For a one-off terminal session, &lt;code&gt;claude --plugin-dir ~/.claude/mods/context-bar&lt;/code&gt; does the same without touching settings.&lt;/p&gt;

&lt;p&gt;Open a new session in any project. The bar is above the prompt before you type anything, and &lt;code&gt;/context-bar&lt;/code&gt; hides it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things that bit me
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Early access means churn.&lt;/strong&gt; The API can change between releases, so a mod written today may need a touch-up after an update. Rerun &lt;code&gt;claude plugin validate&lt;/code&gt; after every upgrade. It is cheap insurance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check which &lt;code&gt;claude&lt;/code&gt; you are running.&lt;/strong&gt; The desktop app bundles its own engine, and the one on your PATH may be older. Mine was, and its validator did not recognize the hooks module at all. Validate with the app's bundled binary, or update the CLI first. This cost me twenty confused minutes staring at an error that was not mine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this feels different
&lt;/h2&gt;

&lt;p&gt;I have used hooks, slash commands, and skills. A settings hook runs a shell command per event and passes JSON over stdin and stdout. A mod loads once, keeps state, draws UI that updates as events happen, and can call back into Claude Code: open a pane, run a process, register a slash command, register a tool the model can call. Some of Claude Code's own features are built as mods, including AGENTS.md support and the &lt;code&gt;/diff&lt;/code&gt; pane. Their source is public, so you can read how the team builds them.&lt;/p&gt;

&lt;p&gt;But the real shift is the loop. The skill for writing mods ships inside Claude Code, so the agent modifies its own runtime, in the same session, and hot reload shows you the result on the next turn. I kept asking for tweaks ("add a legend line with per-category token counts", "show a toast when context crosses 85%") and watched the bar change. It felt less like configuring a tool and more like pair programming with the tool itself.&lt;/p&gt;

&lt;p&gt;If you could describe a mod in one sentence, what would it do?&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
