<?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: Chudi Nnorukam</title>
    <description>The latest articles on DEV Community by Chudi Nnorukam (@chudi_nnorukam).</description>
    <link>https://dev.to/chudi_nnorukam</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3677297%2Fc88a47b0-5b3a-4ac1-950e-8c51b6a2c238.jpg</url>
      <title>DEV Community: Chudi Nnorukam</title>
      <link>https://dev.to/chudi_nnorukam</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/chudi_nnorukam"/>
    <language>en</language>
    <item>
      <title>How I Use AI as an Executive Function Prosthetic</title>
      <dc:creator>Chudi Nnorukam</dc:creator>
      <pubDate>Tue, 16 Jun 2026 00:36:41 +0000</pubDate>
      <link>https://dev.to/chudi_nnorukam/how-i-use-ai-as-an-executive-function-prosthetic-fe4</link>
      <guid>https://dev.to/chudi_nnorukam/how-i-use-ai-as-an-executive-function-prosthetic-fe4</guid>
      <description>&lt;p&gt;For years I thought I had a discipline problem. I had shipped code, finished a degree, built things, and still the dominant private feeling was that I was getting away with something, that the gap between what I could do on a good day and what I could do on a normal one was a character flaw I was hiding. The reframe that changed everything was clinical, not motivational: I do not have a discipline problem. I have an executive function problem. And executive function, unlike character, can be supported from the outside.&lt;/p&gt;

&lt;p&gt;This is the most personal of the three posts in this cluster. The other two are practical: the &lt;a href="https://chudi.dev/blog/adhd-developers-guide-claude-md" rel="noopener noreferrer"&gt;CLAUDE.md guide&lt;/a&gt; and the &lt;a href="https://chudi.dev/blog/claude-code-skills-adhd-developers" rel="noopener noreferrer"&gt;five skills&lt;/a&gt;. This one is the why underneath both.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Executive Function, Actually?
&lt;/h2&gt;

&lt;p&gt;Executive function is the brain's management layer. It is not intelligence, and it is not knowledge. It is the set of processes that turn knowing-what-to-do into actually-doing-it. The &lt;a href="https://www.nimh.nih.gov/health/topics/attention-deficit-hyperactivity-disorder-adhd" rel="noopener noreferrer"&gt;National Institute of Mental Health describes ADHD&lt;/a&gt; as fundamentally a disorder of these self-management processes rather than of attention alone.&lt;/p&gt;

&lt;p&gt;It is not one function. For the purposes of getting work done, it is at least four distinct ones, and ADHD disrupts each of them in a different way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Working memory&lt;/strong&gt;, the mental scratchpad holding what you are doing right now.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task initiation&lt;/strong&gt;, the ability to start, to cross the gap from intention to action.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context switching&lt;/strong&gt;, the ability to drop one task, pick up another, and come back without losing the first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time perception&lt;/strong&gt;, the internal sense of duration that lets you pace and estimate.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Calling them out separately matters, because "I struggle with executive function" is too vague to act on. Each of the four breaks differently and each one needs a different prosthetic. Lumping them together is how you end up trying to fix a time-perception problem with a task-initiation strategy and concluding you are just broken.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is an Executive Function Prosthetic?
&lt;/h2&gt;

&lt;p&gt;A prosthetic does not heal. It compensates. Glasses do not repair your eyes, they do the focusing your eyes cannot, and the result is that you see. Nobody calls glasses a moral failure. Nobody tells a nearsighted person to try harder at focusing.&lt;/p&gt;

&lt;p&gt;An executive function prosthetic is the same idea pointed at the management layer. It is an external system that performs the executive work your brain struggles to perform, so the outcome is the same as if the function worked. A calendar is a prosthetic for time perception. A checklist is a prosthetic for working memory. The framing I want you to take from this post is that you are allowed to build these, deliberately, for the specific parts of you that do not work, and that doing so is engineering, not cheating.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The crutch objection answers itself. Yes, it is a crutch. Crutches let people walk who otherwise could not. The point of an assistive tool was never to prove you can do it the hard way. It was the walking.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I use &lt;a href="https://docs.anthropic.com/en/docs/claude-code/overview" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; as a prosthetic for all four functions. Here is the map.&lt;/p&gt;

&lt;h2&gt;
  
  
  How ADHD Disrupts Each Function, and How AI Compensates
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Working Memory
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How ADHD breaks it:&lt;/strong&gt; the scratchpad is smaller and leakier. You hold fewer items and they fall off faster, especially across interruptions. Close a project, come back, and the model of what you were doing is partly or wholly gone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prosthetic:&lt;/strong&gt; I move the scratchpad out of my head into a file Claude reads automatically. My conventions, my stack, my current task, all of it lives in CLAUDE.md, so the cost of dropping it from memory goes to zero. The file remembers, so I do not have to. The first time I reopened a project and Claude told me what I was doing before I said a word, the relief was physical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task Initiation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How ADHD breaks it:&lt;/strong&gt; there is a real barrier between planning and starting. It is not laziness, it is a failure of the initiation circuit to fire, and a blank editor is the worst possible trigger because it offers nothing to grab. You can know exactly what to do and still be unable to begin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prosthetic:&lt;/strong&gt; I never start from a blank slate. I describe the goal and Claude decomposes it into three to five concrete tasks, and I pick one. Picking one item off a list is a completely different cognitive act than generating the first move from nothing, and the initiation circuit, which could not fire on "build the feature," fires easily on "do this one named thing." The decomposition is the prosthetic. The list is the ramp.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context Switching
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How ADHD breaks it:&lt;/strong&gt; every switch tears down the mental model, and rebuilding it costs time. The &lt;a href="https://www.apa.org/topics/research/multitasking" rel="noopener noreferrer"&gt;American Psychological Association measures the typical recovery at about 23 minutes&lt;/a&gt; per switch. ADHD switches more often, involuntarily, and rebuilds slower. Ten interruptions is not ten minutes lost, it is most of a working day bled out in reconstruction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prosthetic:&lt;/strong&gt; the reconstruction gets done by the tool, not by me. At session start, Claude reads the checkpoint and summarizes the open state, what was in flight, what was blocked, before I write a line. The cold start becomes a warm one. The 23 minutes I would have spent opening files semi-randomly trying to jog the memory get spent on the actual work instead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Time Perception
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;How ADHD breaks it:&lt;/strong&gt; you cannot feel time passing. "One quick fix" becomes four hours. A two-hour task gets estimated at twenty minutes. You ship on deadline panic because there is no internal clock to pace against, and the absence is invisible from the inside, which is what makes it so hard to fix by awareness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The prosthetic:&lt;/strong&gt; external time markers, generated by the tool, surfaced at the moment of drift. Checkpoints at real intervals that interrupt and ask "you set 45 minutes, you are at 60, still the right task?" The brain that cannot generate the time signal gets handed one, while the drift is happening rather than after. The clock lives outside me because inside me it does not run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The One That Is Not on the Standard List: Discounting the Positive
&lt;/h2&gt;

&lt;p&gt;There is a fifth gap that is not technically executive function, but it sabotages all four, so I treat it as part of the same system.&lt;/p&gt;

&lt;p&gt;"Discounting the positive" is a cognitive distortion from CBT: you mentally erase your own wins, so only the failures register. You shipped three features and your brain renders the one bug. ADHD ships with this so reliably that it stops feeling like a distortion and starts feeling like accuracy. It is not accuracy. It is a measurement error, and it is expensive, because the ADHD reward system is already under-fueled, and a brain that cannot see its own progress gets none of the dopamine that progress is supposed to pay.&lt;/p&gt;

&lt;p&gt;This is where my &lt;code&gt;mirror&lt;/code&gt; skill came from. It is the most personal thing I have built. At the end of a session, it reflects back what I actually did, grounded in evidence: the commits, the closed tasks, the things that shipped. Not encouragement, receipts. When I tell myself "good job," my brain discounts it in real time. When a tool shows me the commit log of what I closed today, the distortion has nothing to argue with. The win gets to count.&lt;/p&gt;

&lt;p&gt;That last clause is the whole reason this matters. For an under-fueled reward system, letting the win count is not a nice-to-have. It is the fuel that makes tomorrow's initiation possible. Externalizing the scorekeeping is how I keep the distortion from quietly stealing the dopamine I need to keep going.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Looks Like in Practice
&lt;/h2&gt;

&lt;p&gt;A single feature, start to finish, mapped to the four functions plus the fifth:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Intention: "Add rate limiting to the API."

[Task initiation]   I do not stare at a blank editor. I hand Claude the goal.
                    It returns: 1) define the limiter, 2) wire the middleware,
                    3) add the headers, 4) write the tests. I pick #1.

[Working memory]    Everything I decide (Redis-backed, 100 req/min, per-key)
                    goes straight into CLAUDE.md. I do not hold it. It holds it.

[Context switch]    Slack pulls me out for 40 minutes. I come back cold.
                    Claude reads the checkpoint: "limiter defined, middleware
                    next." No 23-minute rebuild. Warm start.

[Time perception]   A 45-minute checkpoint pings at 60. "Still on the limiter?"
                    I had drifted into refactoring an unrelated helper.
                    Caught it now instead of two hours from now.

[Discount-positive] End of session my brain says "you barely did anything."
                    /mirror: "Shipped rate limiting, 4 tasks closed, tests
                    green. Commits attached." The win counts. Fuel for tomorrow.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No single piece of that is heroic. The system is the point. Each prosthetic is dull on its own. Together they let a brain that cannot reliably do any of the four functions produce the same output as one that can.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Limits
&lt;/h2&gt;

&lt;p&gt;This is environmental support, not treatment. It does not replace medication, therapy, or a clinician, and I would not want anyone to read a blog post and substitute it for care. The prosthetic frame is a way to design your environment, not a way to fix your neurology.&lt;/p&gt;

&lt;p&gt;It also has failure modes. A prosthetic that lies is worse than none: stale context in CLAUDE.md will make Claude reason confidently from a model that no longer matches reality, and you have to maintain the aid with the same discipline you lack, which is its own quiet joke. And there is a real risk of outsourcing understanding, of letting the tool carry so much that you stop being able to hold any of it. The guard there is that I still have to understand what ships. The prosthetic carries the management layer. It does not get to carry the thinking.&lt;/p&gt;

&lt;p&gt;But the reframe stands, and it is the thing I most want you to take. I am not undisciplined. My executive function works differently, and the parts that do not work can be supported from the outside, deliberately, without shame. I stopped trying to white-knuckle my way into a neurotypical brain. I started building prosthetics for the specific functions mine does not have. The work got done. So did I.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is an executive function prosthetic?&lt;/strong&gt;&lt;br&gt;
An external system that performs the executive-function work your brain struggles to do, the way glasses do the focusing your eyes cannot. It does not heal the underlying difference. It compensates so the outcome is the same as if the function worked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is using AI for executive function just a crutch?&lt;/strong&gt;&lt;br&gt;
Yes, and that is not an insult. Crutches let people walk who otherwise could not. The goal of an assistive tool is the outcome, not proving you can do it the hard way. A calendar is a crutch. So are glasses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is "discounting the positive"?&lt;/strong&gt;&lt;br&gt;
A cognitive distortion from CBT where you mentally erase your own wins so only the failures register. It is common with ADHD and it starves an already under-fueled reward system, which is why externalizing your own scorekeeping helps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this replace ADHD medication or therapy?&lt;/strong&gt;&lt;br&gt;
No. It is environmental support, not treatment. It works alongside medication, therapy, and other supports, never instead of them. Talk to a clinician about treatment and use tools like this to design your environment.&lt;/p&gt;

&lt;p&gt;If this mapped onto your experience, the two practical companions are the &lt;a href="https://chudi.dev/blog/adhd-developers-guide-claude-md" rel="noopener noreferrer"&gt;ADHD developer's guide to CLAUDE.md&lt;/a&gt; for the working-memory layer, the &lt;a href="https://chudi.dev/blog/claude-code-skills-adhd-developers" rel="noopener noreferrer"&gt;five Claude Code skills&lt;/a&gt; for the rest of the executive-function gaps, and the &lt;a href="https://chudi.dev/blog/claude-code-adhd-workflows" rel="noopener noreferrer"&gt;original ADHD coding workflow&lt;/a&gt; that started this whole cluster.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://chudi.dev/blog/ai-executive-function-prosthetic-adhd" rel="noopener noreferrer"&gt;chudi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>adhd</category>
      <category>executivefunction</category>
      <category>ai</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>5 Claude Code Skills Every ADHD Developer Needs</title>
      <dc:creator>Chudi Nnorukam</dc:creator>
      <pubDate>Tue, 16 Jun 2026 00:35:48 +0000</pubDate>
      <link>https://dev.to/chudi_nnorukam/5-claude-code-skills-every-adhd-developer-needs-5e74</link>
      <guid>https://dev.to/chudi_nnorukam/5-claude-code-skills-every-adhd-developer-needs-5e74</guid>
      <description>&lt;p&gt;I have built 114 Claude Code skills. Most of them are engineering plumbing. But five of them exist for one reason only: my executive function has specific, repeatable holes, and I got tired of falling into the same ones. These five are not productivity hacks. Each one maps to a named ADHD deficit, and each one fills it the same way every time so I do not have to re-improvise around my own brain at 2pm.&lt;/p&gt;

&lt;p&gt;If you want the broader system this sits inside, start with my &lt;a href="https://chudi.dev/blog/claude-code-adhd-workflows" rel="noopener noreferrer"&gt;Claude Code ADHD workflow&lt;/a&gt; and the &lt;a href="https://chudi.dev/blog/adhd-developers-guide-claude-md" rel="noopener noreferrer"&gt;CLAUDE.md guide&lt;/a&gt;. This post is the skills layer specifically.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Claude Code Skill?
&lt;/h2&gt;

&lt;p&gt;A skill is a named, repeatable workflow you invoke with a slash command. Instead of re-prompting &lt;a href="https://docs.anthropic.com/en/docs/claude-code/overview" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; from a blank slate every time ("okay, help me figure out what to work on, here is my situation again..."), you type &lt;code&gt;/adhd-task-triage&lt;/code&gt; and it runs the same defined steps it ran yesterday. For an ADHD brain, that determinism is the feature. The skill does not depend on me remembering how to drive it. It just runs.&lt;/p&gt;

&lt;p&gt;Custom skills live in a &lt;code&gt;.claude/skills/&amp;lt;name&amp;gt;/SKILL.md&lt;/code&gt; file that describes what the skill does and when it should fire. You can build one for any gap you fall into more than twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. adhd-task-triage: Energy-Based Prioritization
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The gap it fills: task initiation paralysis.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Standard task managers sort by priority or deadline. That assumes you can act on the top item by willpower. ADHD does not work that way. The top-priority task and the task you can actually start right now are often different tasks, and trying to force the high-priority one when your initiation circuit is offline produces zero output and a guilt spiral.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;adhd-task-triage&lt;/code&gt; sorts by &lt;strong&gt;available energy&lt;/strong&gt;, not importance. You tell it where you are (wired, foggy, depleted), it looks at the work in front of you, and it hands back the task that matches the state you are actually in, not the one you wish you were in.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/adhd-task-triage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why it helps specifically: it removes the moral framing. The question stops being "why can't you just do the important one" and becomes "what can this brain, in this state, actually initiate." Matching the task to the energy is how you get momentum, and momentum is the thing ADHD brains can ride once it exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. mirror: The Counter to Discounting the Positive
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The gap it fills: cognitive distortion, specifically discounting the positive.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Discounting the positive" is a CBT term for a thinking pattern where you mentally delete your own wins. You shipped three features, but your brain only renders the one bug. ADHD comes bundled with this so often that it feels like just being realistic. It is not realistic. It is a measurement error, and it is corrosive, because a brain that cannot see its own progress loses the dopamine that progress is supposed to pay out.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mirror&lt;/code&gt; reflects back what I actually did, grounded in evidence, not vibes. It reads the session, the commits, the closed tasks, and tells me plainly: here is what shipped. Not cheerleading. Receipts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/mirror
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why it helps specifically: ADHD reward systems are under-fueled, and discounting the positive starves them further. When the skill says "you closed four tasks and shipped the auth flow," and points at the commits, the distortion has nothing to push against. I cannot argue with the log. The win gets to count, which is the entire point.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The reason this works is that it is external and evidence-based. If I tell myself "good job," my brain discounts it instantly. If a tool shows me the commit history of what I closed today, the distortion has no grip. Externalize the scorekeeping and the distortion loses.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. pickup: Session Resume for Context-Switching
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The gap it fills: context-switch recovery cost.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every interruption tears down the mental model of what I was doing. Rebuilding it is the &lt;a href="https://www.apa.org/topics/research/multitasking" rel="noopener noreferrer"&gt;23-minute reconstruction tax&lt;/a&gt; that, for an ADHD brain, runs longer and hits more often. The worst version is the overnight gap: I close the laptop mid-thought and the next morning the entire working model is just gone.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pickup&lt;/code&gt; reconstructs the session for me. It reads where I left off, what was in flight, what was blocked, and summarizes the open state before I write a single line.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/pickup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why it helps specifically: it converts a cold start into a warm one. The reconstruction that used to eat the first half hour of every session, the part where I open files semi-randomly trying to jog the memory, gets done by the skill in seconds. I am not rebuilding the model. I am reading it back.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. schedule: A Patch for Time Blindness
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The gap it fills: time perception distortion.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Time blindness is an ADHD hallmark: you cannot feel time passing, so "I'll just fix one thing" becomes a four-hour rabbit hole, and a two-hour task gets estimated at twenty minutes. You ship on deadline panic because your brain has no internal clock to pace against.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;schedule&lt;/code&gt; puts external markers on the work: it sets up recurring checkpoints and time-boxed runs so the passage of time becomes visible instead of imagined. It is the external clock my brain does not have.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/schedule
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why it helps specifically: ADHD does not respond well to "be more aware of time," because the awareness machinery is the thing that does not work. It responds to external structure. A skill that interrupts at a real interval and asks "you set 45 minutes for this, you are at 60, still the right task?" gives me the time signal my brain cannot generate, at the moment the drift is happening rather than after.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. librarian: Reducing the Cognitive Load of Navigation
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The gap it fills: working-memory overload from holding a whole codebase in your head.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Navigating a large codebase means holding a map of it in working memory: where things are, what calls what, which file owns which concern. ADHD working memory cannot hold that map, so every navigation becomes a fresh, expensive search, and the expense itself becomes a reason to avoid touching unfamiliar parts of the code.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;librarian&lt;/code&gt; walks the knowledge layer for me. Instead of me grepping around trying to reconstruct where a behavior lives, I describe what I am after and it returns the relevant pieces and how they connect. It holds the map so I do not have to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;/librarian
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why it helps specifically: it collapses the navigation tax. The mental energy I would spend reconstructing "where does this live and what touches it" is exactly the executive-function budget I do not have to spare. Offloading the map means I can spend that budget on the actual change instead of on finding the place to make it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Looks Like in Practice
&lt;/h2&gt;

&lt;p&gt;A real morning, lightly compressed, showing the five working as a chain rather than in isolation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;9:10  Sit down. No memory of yesterday's state.
      /pickup  -&amp;gt;  "You were mid-refactor on the signal loop, JWT
                    middleware blocked on a cookie setting. 2 tasks open."
      Cold start avoided. ~20 min reconstruction skipped.

9:14  Brain is foggy, not wired.
      /adhd-task-triage  -&amp;gt;  hands me the low-initiation-cost task
                            (write the middleware test), not the hard one.
      Momentum started instead of staring at the blank editor.

9:20  Need to change the auth schema but forget where it lives.
      /librarian  -&amp;gt;  returns the schema file + everything that touches it.
      Navigation tax collapsed. No grep spiral.

9:25  /schedule already running a 45-min checkpoint in the background.
      At 10:10 it pings: "60 min on a 45 task, still the right one?"
      Caught a hyperfocus drift before it ate the morning.

11:30 Brain says "you got nothing done."
      /mirror  -&amp;gt;  "3 tasks closed, middleware shipped, schema migrated.
                    Here are the commits."
      Distortion loses. The win counts.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;None of these is doing anything a disciplined neurotypical developer could not do by hand. That is the point. They do it &lt;em&gt;for&lt;/em&gt; me, the same way every time, so the doing does not depend on executive function I cannot summon on command.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Start Building Your Own?
&lt;/h2&gt;

&lt;p&gt;Do not install five skills today. Pick the one deficit that costs you the most, mine was the cold-start reconstruction, and build or invoke the one skill that targets it. A skill is a &lt;code&gt;SKILL.md&lt;/code&gt; file in &lt;code&gt;.claude/skills/&lt;/code&gt; that names what it does and when it triggers. Start there, use it for a week, and let the next-most-expensive gap tell you what to build next.&lt;/p&gt;

&lt;p&gt;The pattern underneath all five is the same: name the executive-function gap precisely, then build a repeatable thing that fills it so you stop re-improvising around your own brain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is a Claude Code skill?&lt;/strong&gt;&lt;br&gt;
A packaged capability you invoke by name (like &lt;code&gt;/pickup&lt;/code&gt;) that runs a defined, repeatable workflow. Instead of re-prompting Claude from scratch each time, you call the skill and it runs the same proven steps every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Are these built into Claude Code?&lt;/strong&gt;&lt;br&gt;
Claude Code ships with a skill system and some bundled skills. Several here (like &lt;code&gt;adhd-task-triage&lt;/code&gt; and &lt;code&gt;mirror&lt;/code&gt;) are custom skills I built for my own setup. The transferable thing is the pattern: build a skill for any recurring executive-function gap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need ADHD for these to help?&lt;/strong&gt;&lt;br&gt;
No. They help anyone who context-switches a lot, undersells their progress, or loses the thread between sessions. ADHD just makes the gaps sharper, so the payoff comes faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I invoke a skill?&lt;/strong&gt;&lt;br&gt;
Type its slash command (for example &lt;code&gt;/pickup&lt;/code&gt;) in Claude Code. Custom skills live in &lt;code&gt;.claude/skills/&lt;/code&gt; as a &lt;code&gt;SKILL.md&lt;/code&gt; file that defines what the skill does and when it fires.&lt;/p&gt;

&lt;p&gt;These skills are the executive-function layer on top of the working-memory layer from the &lt;a href="https://chudi.dev/blog/adhd-developers-guide-claude-md" rel="noopener noreferrer"&gt;CLAUDE.md guide&lt;/a&gt;. For the clinical picture of why each gap exists and how AI compensates, read &lt;a href="https://chudi.dev/blog/ai-executive-function-prosthetic-adhd" rel="noopener noreferrer"&gt;how I use AI as an executive function prosthetic&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://chudi.dev/blog/claude-code-skills-adhd-developers" rel="noopener noreferrer"&gt;chudi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>adhd</category>
      <category>skills</category>
      <category>neurodivergent</category>
    </item>
    <item>
      <title>The ADHD Developer's Guide to CLAUDE.md</title>
      <dc:creator>Chudi Nnorukam</dc:creator>
      <pubDate>Tue, 16 Jun 2026 00:35:39 +0000</pubDate>
      <link>https://dev.to/chudi_nnorukam/the-adhd-developers-guide-to-claudemd-2o8</link>
      <guid>https://dev.to/chudi_nnorukam/the-adhd-developers-guide-to-claudemd-2o8</guid>
      <description>&lt;p&gt;I reopened a file I had already fixed that morning. Not metaphorically. I literally re-fixed a bug I had closed four hours earlier, because between the fix and the reopen, my brain had quietly deleted the entire afternoon. That is the ADHD tax most productivity advice never names: it is not that you cannot focus, it is that the working model of what you were doing does not survive the gap between sessions.&lt;/p&gt;

&lt;p&gt;CLAUDE.md is the cheapest fix I have found for that specific failure. This is the companion to my &lt;a href="https://chudi.dev/blog/claude-code-adhd-workflows" rel="noopener noreferrer"&gt;Claude Code ADHD workflow&lt;/a&gt;; that post is the full system, this one zooms all the way in on the single file doing most of the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is CLAUDE.md, Actually?
&lt;/h2&gt;

&lt;p&gt;CLAUDE.md is a Markdown file that &lt;a href="https://docs.anthropic.com/en/docs/claude-code/overview" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; reads automatically at the start of every session. You do not paste it. You do not remind Claude it exists. It just gets read, every time, before the first line of work.&lt;/p&gt;

&lt;p&gt;There are two places it lives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;./CLAUDE.md&lt;/code&gt;&lt;/strong&gt; at a project root holds rules for that project: the tech stack, the conventions, the gotchas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;&lt;/strong&gt; holds your global rules: things true across everything you build (your voice, your defaults, the things you never want re-litigated).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a neurotypical developer this is a convenience. For an ADHD developer it is a prosthetic. The difference is what the file is replacing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why CLAUDE.md Is External Working Memory for ADHD Brains
&lt;/h2&gt;

&lt;p&gt;Working memory is the mental scratchpad that holds "what I am doing right now and the three things I just decided about it." ADHD shrinks that scratchpad and makes it leaky. Every interruption, a Slack ping, a stray thought, a context-switch to email, knocks items off it. When you return, the scratchpad is blank and you rebuild it from scratch.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://www.apa.org/topics/research/multitasking" rel="noopener noreferrer"&gt;American Psychological Association puts the rebuild cost at roughly 23 minutes&lt;/a&gt; per context switch for a typical brain. For an ADHD brain that involuntarily switches more often and rebuilds slower, the real cost is higher and it compounds. Ten switches a day is not ten minutes lost, it is most of a morning.&lt;/p&gt;

&lt;p&gt;CLAUDE.md moves the scratchpad out of your head and into a file. The conventions you would otherwise have to remember, hold, and re-explain now live somewhere that does not leak:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It remembers your &lt;strong&gt;tech stack&lt;/strong&gt;, so you never re-explain "we use SvelteKit, not Next."&lt;/li&gt;
&lt;li&gt;It remembers your &lt;strong&gt;conventions&lt;/strong&gt;, so you stop re-deciding the same naming question.&lt;/li&gt;
&lt;li&gt;It remembers your &lt;strong&gt;voice&lt;/strong&gt;, so output comes back sounding like you without a paragraph of instructions.&lt;/li&gt;
&lt;li&gt;It remembers the &lt;strong&gt;current task&lt;/strong&gt;, so reopening the project does not start from "wait, what was I building?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You are not asking Claude to remember for you. You are writing down what you would otherwise have to hold, once, so that the cost of dropping it goes to zero.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The mental reframe that made this click for me: CLAUDE.md is not documentation you write for other people. It is a note you write to your own future self, who will have no memory of today. Write it for the version of you that comes back tomorrow with the scratchpad wiped.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What This Looks Like in Practice
&lt;/h2&gt;

&lt;p&gt;Here is a real, trimmed version of a project CLAUDE.md from my setup. Nothing exotic, that is the point. It is boring, and boring is what survives.&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;# Project: chudi.dev blog&lt;/span&gt;

&lt;span class="gu"&gt;## Stack (do not re-ask)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; SvelteKit + Svelte 5 runes ONLY ($state, $props, $derived). No legacy &lt;span class="sb"&gt;`let`&lt;/span&gt;/&lt;span class="sb"&gt;`$:`&lt;/span&gt;.
&lt;span class="p"&gt;-&lt;/span&gt; Tailwind v4 + CSS custom properties in src/app.css. Never hardcode hex in components.
&lt;span class="p"&gt;-&lt;/span&gt; Content: Markdown in content/posts/. Frontmatter required: title, date, description, tags.

&lt;span class="gu"&gt;## Voice (apply to all writing)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; First person, conversational, backed by real numbers.
&lt;span class="p"&gt;-&lt;/span&gt; NEVER use em-dashes. Use commas, periods, parentheses.
&lt;span class="p"&gt;-&lt;/span&gt; Lead with the failure, then the fix. No marketing tone.

&lt;span class="gu"&gt;## Gotchas (learned the hard way)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Never use the $lib alias in any file reachable from a prebuild script. tsx
  on Vercel will not resolve it. Use relative imports. (Cost me a 3-hour outage.)
&lt;span class="p"&gt;-&lt;/span&gt; Reading time is auto-calculated. Do not hardcode it.

&lt;span class="gu"&gt;## Current checkpoint&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Last task: shipped citability CTA on 3 top-traffic posts.
&lt;span class="p"&gt;-&lt;/span&gt; Next task: write the 3-post ADHD cluster around claude-code-adhd-workflows.
&lt;span class="p"&gt;-&lt;/span&gt; Blocked by: nothing.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The four sections map directly onto the four things my working memory cannot be trusted to hold: the stack, the voice, the landmines, and the bookmark.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Current checkpoint&lt;/strong&gt; block is the one that matters most for ADHD. Before I had it, every session opened with a flailing minute or two of "okay where was I." Now the first thing Claude tells me is what I was doing and what is next, because it read the checkpoint before I said a word. The flail is gone.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Global File Does the Re-Decided Stuff
&lt;/h3&gt;

&lt;p&gt;The project file handles per-project facts. The global &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt; handles the things I was tired of re-deciding everywhere:&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;## Defaults (do not ask, just do)&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Verify before claiming done. Show the build output, do not say "should work."
&lt;span class="p"&gt;-&lt;/span&gt; Break vague goals into 3-5 atomic tasks and let me pick one. Never hand me a blank slate.
&lt;span class="p"&gt;-&lt;/span&gt; One question at a time. If you have three, ask the first and wait.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last rule is pure ADHD accommodation. A wall of "do you want A or B, and also C or D, and what about E" is a context-switch grenade. "Ask the first, wait" keeps me in one decision at a time.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Does CLAUDE.md Compare Before and After?
&lt;/h2&gt;

&lt;p&gt;I tracked my own sessions loosely for two weeks before writing a real CLAUDE.md and two weeks after. This is self-reported and n-of-one, so read it as a direction, not a benchmark. But the direction was not subtle.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Before CLAUDE.md&lt;/th&gt;
&lt;th&gt;After CLAUDE.md&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time to first useful action after reopening a project&lt;/td&gt;
&lt;td&gt;5 to 15 minutes of "where was I"&lt;/td&gt;
&lt;td&gt;Under 1 minute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Re-explaining stack/conventions per session&lt;/td&gt;
&lt;td&gt;Almost every session&lt;/td&gt;
&lt;td&gt;Effectively never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Re-doing work I had already finished&lt;/td&gt;
&lt;td&gt;A few times a week&lt;/td&gt;
&lt;td&gt;Rare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sessions that ended without me knowing what was next&lt;/td&gt;
&lt;td&gt;Most of them&lt;/td&gt;
&lt;td&gt;Almost none (the checkpoint forces it)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The biggest win is not on that table because it is hard to measure: the dread went down. Reopening a half-built project used to carry a little spike of "ugh, I have to reload all of this." When the file reloads it for me, that spike mostly disappears, and the lowered friction is what actually gets me back into the chair.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Breaks This System
&lt;/h2&gt;

&lt;p&gt;CLAUDE.md is not free and it is not foolproof. Two failure modes, both real, both mine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stale context is worse than no context.&lt;/strong&gt; I once let a project's CLAUDE.md describe an architecture I had refactored away three weeks earlier. Claude reasoned confidently from a model that no longer matched the code, and I almost shipped it. The fix is discipline: when you change something structural, update the file in the same breath. An out-of-date CLAUDE.md does not just fail to help, it actively lies to you with full confidence.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The unwritten checkpoint.&lt;/strong&gt; The checkpoint only works if you write it before you close. The times I closed mid-thought and skipped it, the next session opened cold, exactly the problem the file was supposed to solve. I eventually added a hook that nudges me to update the checkpoint before the session ends, because relying on ADHD memory to maintain the ADHD memory aid is, predictably, a bad plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do You Start?
&lt;/h2&gt;

&lt;p&gt;Do not engineer the perfect file. Open the project, create &lt;code&gt;CLAUDE.md&lt;/code&gt; at the root, and write four headers: Stack, Voice, Gotchas, Current checkpoint. Fill in what you know right now. Leave the rest blank. The file gets better every time Claude asks you something you wish it already knew, because that question is the exact thing you should write down.&lt;/p&gt;

&lt;p&gt;Your working memory is not a character flaw to push through. It is a constraint to design around. CLAUDE.md is the cheapest way I know to design around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is CLAUDE.md and where does it live?&lt;/strong&gt;&lt;br&gt;
It is a plain Markdown file Claude Code reads automatically at the start of every session. Project rules go in &lt;code&gt;./CLAUDE.md&lt;/code&gt; at the project root; global rules go in &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;. You never have to tell Claude to read it, it just does, every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How is it different from explaining context in chat?&lt;/strong&gt;&lt;br&gt;
Chat context dies when the session ends. CLAUDE.md persists across sessions. For an ADHD brain that loses the mental model across interruptions, that persistence is the entire value: the file holds your conventions so your working memory does not have to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Will CLAUDE.md fix my ADHD?&lt;/strong&gt;&lt;br&gt;
No. It externalizes one slice of executive function, working memory, so the cost of switching back into a project drops from minutes to seconds. It is a prosthetic, not a cure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How long should it be?&lt;/strong&gt;&lt;br&gt;
Short enough that you would actually read it, long enough that Claude stops asking you things you already decided. Mine run 40 to 120 lines per project. Prune it when it drifts out of date, because stale context is worse than none.&lt;/p&gt;

&lt;p&gt;If CLAUDE.md is the working-memory layer, the next layer is the executive-function gaps it does not cover. I wrote about the specific &lt;a href="https://chudi.dev/blog/claude-code-skills-adhd-developers" rel="noopener noreferrer"&gt;Claude Code skills every ADHD developer needs&lt;/a&gt; for that, and the bigger clinical picture in &lt;a href="https://chudi.dev/blog/ai-executive-function-prosthetic-adhd" rel="noopener noreferrer"&gt;how I use AI as an executive function prosthetic&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://chudi.dev/blog/adhd-developers-guide-claude-md" rel="noopener noreferrer"&gt;chudi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>adhd</category>
      <category>claudemd</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Entity Optimization for Brands in AI Search</title>
      <dc:creator>Chudi Nnorukam</dc:creator>
      <pubDate>Wed, 22 Apr 2026 22:13:03 +0000</pubDate>
      <link>https://dev.to/chudi_nnorukam/entity-optimization-for-brands-in-ai-search-hbn</link>
      <guid>https://dev.to/chudi_nnorukam/entity-optimization-for-brands-in-ai-search-hbn</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://chudi.dev/blog/entity-optimization-brands-ai-search" rel="noopener noreferrer"&gt;chudi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;AI search engines do not rank pages. They score entities, then quote the entity that best matches the query. For a sub-DR-20 brand, this is the good news. You cannot outspend enterprise SEO teams on backlinks. You can out-engineer them on entity coherence.&lt;/p&gt;

&lt;p&gt;This post is the engineering playbook for that work. It covers the three schemas that anchor the graph, the sameAs surface where most brands leak trust, the &lt;code&gt;knowsAbout&lt;/code&gt; field that tells engines what you are an authority on, and the measurement loop that tells you when it is working. The reference model throughout is chudi.dev (the Person brand) and citability.dev (the product brand): a deliberate two-node graph that synergizes without cannibalizing.&lt;/p&gt;

&lt;h2&gt;
  
  
  §1 — Why AI citations resolve to entities, not URLs
&lt;/h2&gt;

&lt;p&gt;Classical SEO optimized for pages. Answer engines optimize for the entity behind the page. If two sites say the same thing and one site has a coherent Person + Organization + sameAs graph, the engine quotes the coherent site even when the other page ranks higher in traditional SERPs. Cite the entity, not the URL. That is the mental model shift this cluster pivots on.&lt;/p&gt;

&lt;h2&gt;
  
  
  §2 — The three anchor schemas
&lt;/h2&gt;

&lt;p&gt;Every resilient entity graph has three anchor schemas. Person. Organization. SoftwareApplication. They cross-reference through &lt;code&gt;creator&lt;/code&gt;, &lt;code&gt;publisher&lt;/code&gt;, and &lt;code&gt;about&lt;/code&gt; fields. Miss one anchor and the graph reads as a disconnected individual, a disconnected company, or a disconnected tool. Engines pick the version of your story they find easiest to summarize, which may not be the version you want quoted.&lt;/p&gt;

&lt;h2&gt;
  
  
  §3 — sameAs coherence across platforms
&lt;/h2&gt;

&lt;p&gt;The sameAs array is the single most valuable field in the Person schema for sub-DR-20 brands. It is also the field most brands let drift. Six platforms. Six subtly different job titles. Six subtly different descriptions. Engines treat this as ambiguity and choose a canonical, usually the platform with the highest authority, not the one you care about.&lt;/p&gt;

&lt;h2&gt;
  
  
  §4 — The knowsAbout surface nobody uses
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;knowsAbout&lt;/code&gt; is the declarative authority claim. Use it to stake your territory. For chudi.dev, the stake is AI Visibility Engineering, Generative Engine Optimization, Entity Graph Architecture, and Sub-DR-20 SEO. Four claims. Four phrases an engine can match against a query.&lt;/p&gt;

&lt;h2&gt;
  
  
  §5 — The citation flow
&lt;/h2&gt;

&lt;p&gt;When a query arrives, an AI engine runs a pipeline. Retrieve candidate entities. Score coherence. Gate. Cite. Optimizing for citation means shortening the distance between query and gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  §6 — Measurement: how you know it is working
&lt;/h2&gt;

&lt;p&gt;The answer engines that matter (Perplexity, ChatGPT with search, Google AI Overviews) do not expose rank. They expose citations. Measurement is a different instrument than Google Search Console. Track citation count per engine, quote length per citation, and coherence-check failures across your sameAs graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bridge — from thinking to measurement
&lt;/h2&gt;

&lt;p&gt;This post describes the thinking. &lt;a href="https://citability.dev" rel="noopener noreferrer"&gt;citability.dev&lt;/a&gt; is the instrumentation. Run the citability scorer on any chudi.dev post to see what the engine actually extracts, which entity it resolves to, and where the coherence gates are failing.&lt;/p&gt;

</description>
      <category>aivisibility</category>
      <category>entitygraph</category>
      <category>schemaorg</category>
      <category>geo</category>
    </item>
    <item>
      <title>ADHD Remote Work for Developers: Build for Context Decay, Not Perfect Focus</title>
      <dc:creator>Chudi Nnorukam</dc:creator>
      <pubDate>Fri, 10 Apr 2026 23:55:49 +0000</pubDate>
      <link>https://dev.to/chudi_nnorukam/adhd-remote-work-for-developers-build-for-context-decay-not-perfect-focus-45gp</link>
      <guid>https://dev.to/chudi_nnorukam/adhd-remote-work-for-developers-build-for-context-decay-not-perfect-focus-45gp</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://chudi.dev/blog/adhd-remote-work-developer-systems" rel="noopener noreferrer"&gt;chudi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I worked from home for eight months before I figured out I was failing at remote work specifically because of ADHD, not despite doing everything "right."&lt;/p&gt;

&lt;p&gt;I had a desk. A monitor. A to-do list. I woke up at the same time every day. And still, whole afternoons would vanish. I'd look up at 5pm, realize I'd been deep in a rabbit hole since 11am, and have nothing to show for it that anyone would call work.&lt;/p&gt;

&lt;p&gt;The problem wasn't focus. ADHD gets described as a focus problem, but that's wrong. My focus was fine. It was just pointed at the wrong things for hours at a time, with no external force to redirect it. That's what remote work does to ADHD brains: it removes every environmental cue that normally handles redirection for you.&lt;/p&gt;

&lt;p&gt;Here's what I replaced those cues with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why remote work hits ADHD harder than most people realize
&lt;/h2&gt;

&lt;p&gt;An office is full of behavioral scaffolding you don't notice until it's gone.&lt;/p&gt;

&lt;p&gt;The commute is a transition ritual. It physically separates "home brain" from "work brain." Without it, you start working and your nervous system never quite shifts modes.&lt;/p&gt;

&lt;p&gt;Other people visibly working is a constant time cue. You glance up and see colleagues at their desks, it's 2pm, you haven't eaten. That passive awareness regulates your own sense of time. Without it, time blindness kicks in fully.&lt;/p&gt;

&lt;p&gt;Scheduled meetings are anchors. Even if you hate meetings, they force context switching at predictable intervals. They're external interrupts the ADHD brain doesn't have to generate internally.&lt;/p&gt;

&lt;p&gt;Remote work strips all three. What you're left with is a quiet room, unlimited flexibility, and a brain that cannot generate external structure from nothing.&lt;/p&gt;

&lt;p&gt;Flexibility, for ADHD, is not a feature. It's a trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Environment design: make your space do the transition work
&lt;/h2&gt;

&lt;p&gt;The most reliable fix I found is physical environment separation. Not because it's psychologically important in some abstract way, but because ADHD brains form strong context associations. If you sleep, relax, and work in the same chair, your brain genuinely cannot tell which mode to be in.&lt;/p&gt;

&lt;p&gt;If you have a separate room, use it only for work. Close the door when you're done. The physical act of closing the door is the end-of-day ritual. Open it again when starting. That's the whole commute.&lt;/p&gt;

&lt;p&gt;If you don't have a separate room (I didn't for two years), use environmental cues instead:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A specific chair at a specific surface.&lt;/strong&gt; Not the couch. Not the bed. One chair that means work. Sit in it, you're working. Leave it, you're not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distinct lighting.&lt;/strong&gt; I use a desk lamp on a smart plug scheduled to turn on at 9am and off at 6pm. When it turns on, that's the ambient "it's work time" signal. This sounds absurd. It works.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A start ritual that's identical every day.&lt;/strong&gt; Mine is: make coffee, carry it to the desk, open a specific playlist. Three steps, always the same order. The ritual triggers the context switch. Willpower doesn't have to.&lt;/p&gt;

&lt;p&gt;The goal isn't a beautiful home office. It's tricking your nervous system into associating a location with a mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time anchors: replace meetings with artificial deadlines
&lt;/h2&gt;

&lt;p&gt;Time blindness is the ADHD symptom that remote work makes worst. In an office, you feel time passing through social cues. At home, four hours can feel like forty minutes.&lt;/p&gt;

&lt;p&gt;The fix is time anchors, and the most effective one I've found is a scheduled video call. It doesn't matter what the call is about. It just has to happen at a predictable time, because your brain will orient itself around it. "I have a call at 2pm" creates a before-the-call and an after-the-call. That's two time blocks with structure, instead of one infinite formless day.&lt;/p&gt;

&lt;p&gt;If you don't have regular calls, manufacture them:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Daily standup with yourself.&lt;/strong&gt; Five minutes on video, speaking out loud, covering what you did yesterday and what you're doing today. The video part matters. Talking to a camera activates social awareness in a way typing doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focusmate sessions.&lt;/strong&gt; Two people, 50-minute video session, each says what they're working on, then works silently, then checks back in. It's structured, it ends, and the commitment to another person is enough accountability to keep most ADHD brains on task.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;90-minute alarms.&lt;/strong&gt; Not as a task timer. As a "what are you doing right now" interrupt. When it goes off, write one sentence in a running doc: what you're doing, whether it's the right thing. That's it. The awareness loop is enough to reduce hyperfocus drift.&lt;/p&gt;

&lt;h2&gt;
  
  
  Async communication: close every loop explicitly
&lt;/h2&gt;

&lt;p&gt;Open communication loops are ADHD kryptonite at home.&lt;/p&gt;

&lt;p&gt;In an office, you send a Slack message and you can see the person at their desk. You know they're alive. You have some sense of whether they saw it. At home, you send something and then... nothing. That uncertainty sits in your brain as an open tab, consuming attention.&lt;/p&gt;

&lt;p&gt;The fix is aggressive loop-closing, on both ends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Send explicit "done" messages.&lt;/strong&gt; When you finish a task that someone asked for, message them directly: "Done, PR is up." Don't assume they'll see the PR notification. Close the loop yourself. This sounds like extra work but it's actually cognitive offloading — you're moving the "did they see it" question out of your head and into their inbox.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Batch your Slack/email.&lt;/strong&gt; Check twice a day: 9:30am and 3:30pm. Outside those windows, quit the app. I mean actually quit it, not just minimize it. The notification bubble sitting in your dock is a distraction even when you're not looking at it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use Loom instead of "let's hop on a call."&lt;/strong&gt; When you need to explain something complex, record a 3-minute video. It's faster to make than scheduling a call, the other person can watch it at their pace, and you don't have to hold the context of what you were going to say while waiting for a meeting slot to open up.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set explicit Slack statuses.&lt;/strong&gt; "Deep work until 2pm" tells your team not to expect synchronous response. It also reminds you what you're supposed to be doing when you look at your own status and see it staring back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Body doubling: the remote version
&lt;/h2&gt;

&lt;p&gt;Body doubling is working in the presence of another person. It activates the social engagement system, which provides the kind of external accountability ADHD brains need to sustain attention on boring tasks. It sounds like it shouldn't work. It works.&lt;/p&gt;

&lt;p&gt;The office version is automatic. The remote version requires effort to set up, but it's worth it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focusmate&lt;/strong&gt; (focusmate.com) is purpose-built for this. You book 50-minute sessions with a random partner, you both say what you're working on, you work silently, you check back in at the end. It's free for a few sessions per week, paid for unlimited. I've used it to get through tax returns, documentation, and every other task that normally gets avoided indefinitely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Discord co-working servers.&lt;/strong&gt; There are ADHD-specific ones with always-on voice channels where people work silently together. You join, unmute when you want company, mute when you need quiet. The ambient presence of other people is enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A recurring call you never close.&lt;/strong&gt; I have a weekly video call with a friend who also works remotely. We don't talk during it. We just exist on the same screen, working. When one of us needs to say something, we say it. This is body doubling. It costs nothing and it's sustainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managing your manager remotely with ADHD
&lt;/h2&gt;

&lt;p&gt;The part nobody talks about: ADHD makes remote work harder because managers can't see you working. In an office, your presence is visible evidence of effort. At home, you have to manufacture that evidence.&lt;/p&gt;

&lt;p&gt;This isn't about performing productivity. It's about removing the anxiety of wondering whether your manager thinks you've disappeared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Daily end-of-day summary, one sentence.&lt;/strong&gt; "Finished the auth PR, reviewed two tickets, starting the cache refactor tomorrow." Send it every day, at the same time, without being asked. It closes the loop for your manager and it forces you to acknowledge what you actually did, which matters for your own ADHD sense of accomplishment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Overcommunicate blockers.&lt;/strong&gt; When you're stuck, say so immediately. ADHD means stuck can turn into three hours of silent spiraling without warning. Saying "I'm blocked on X, any context?" gets you unblocked faster and signals that you're engaged, not absent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use async video for updates that would otherwise be a meeting.&lt;/strong&gt; A 2-minute Loom is easier for everyone than a 30-minute call. It respects your manager's time, it documents the update, and it proves you exist and are thinking.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I stopped doing
&lt;/h2&gt;

&lt;p&gt;I stopped trying to simulate the office at home. I don't have scheduled "office hours" where I sit at my desk whether or not I have anything to do. I don't use time-blocking as a rigid constraint because rigid constraints with ADHD become guilt traps when they break, which they always do.&lt;/p&gt;

&lt;p&gt;Instead, I work with the parts of ADHD that actually help remote work. Hyperfocus is an asset when pointed at the right thing, so I protect large uninterrupted blocks in the morning. The lack of commute means I can start working when my brain is actually ready, not when a train schedule says I should be at a desk.&lt;/p&gt;

&lt;p&gt;Remote work with ADHD doesn't require suppressing the ADHD. It requires replacing the scaffolding the office provided with intentional, homemade versions of the same cues.&lt;/p&gt;

&lt;p&gt;The cues work. The willpower approach doesn't. That's the whole thing.&lt;/p&gt;




&lt;p&gt;If you want to compare notes on what's working, the contact is at the bottom of the page. Remote ADHD work is still being figured out by most of us in real time.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.additudemag.com/time-blindness-adhd/" rel="noopener noreferrer"&gt;ADHD and Time Blindness — ADDitude Magazine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://chadd.org/adhd-weekly/body-doubling-why-does-working-with-someone-else-in-the-room-make-you-more-productive/" rel="noopener noreferrer"&gt;Body Doubling for ADHD — CHADD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.apa.org/monitor/2021/04/ce-remote-workers" rel="noopener noreferrer"&gt;Remote Work and Mental Health — APA&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>adhd</category>
      <category>neurodivergent</category>
      <category>productivity</category>
      <category>workingmemory</category>
    </item>
    <item>
      <title>Self-Improving RAG: Teaching Claude Code to Learn From Errors</title>
      <dc:creator>Chudi Nnorukam</dc:creator>
      <pubDate>Fri, 10 Apr 2026 23:55:21 +0000</pubDate>
      <link>https://dev.to/chudi_nnorukam/i-built-a-self-improving-rag-system-for-claude-code-here-is-what-it-learned-5a3b</link>
      <guid>https://dev.to/chudi_nnorukam/i-built-a-self-improving-rag-system-for-claude-code-here-is-what-it-learned-5a3b</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://chudi.dev/blog/self-improving-rag-claude-code" rel="noopener noreferrer"&gt;chudi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I was debugging the same authentication error for the third time this month.&lt;/p&gt;

&lt;p&gt;Same error. Same root cause. Same fix.&lt;/p&gt;

&lt;p&gt;Claude Code had solved this exact problem two weeks ago—but it didn't remember. Each session starts fresh. No memory of what worked, what failed, or what patterns emerged.&lt;/p&gt;

&lt;p&gt;That's a massive waste of debugging time.&lt;/p&gt;

&lt;p&gt;So I built a system to fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem With Stateless AI
&lt;/h2&gt;

&lt;p&gt;Claude Code is powerful, but it has a fundamental limitation: &lt;strong&gt;every session starts from zero.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same mistakes repeated across sessions&lt;/li&gt;
&lt;li&gt;No accumulation of project-specific knowledge&lt;/li&gt;
&lt;li&gt;Debugging loops that should take minutes take hours&lt;/li&gt;
&lt;li&gt;Learnings trapped in conversation history, never extracted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The irony? Claude Code can solve complex problems. It just can't remember that it already solved them. This is where building a &lt;a href="https://chudi.dev/blog/self-improving-rag-claude-code" rel="noopener noreferrer"&gt;self-improving RAG system&lt;/a&gt; becomes transformative.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing the Self-Improving RAG System
&lt;/h2&gt;

&lt;p&gt;I built a configuration that makes Claude Code learn from every debugging session.&lt;/p&gt;

&lt;p&gt;The core idea: &lt;strong&gt;automatic capture, structured storage, intelligent retrieval.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When something breaks, the system captures it. When something works, the system remembers it. When a session ends, the system reflects on what happened.&lt;/p&gt;

&lt;p&gt;No manual logging. No /learn commands for every insight. The system watches, learns, and improves. This is built on the principles of &lt;a href="https://chudi.dev/blog/what-is-rag" rel="noopener noreferrer"&gt;Retrieval-Augmented Generation (RAG)&lt;/a&gt;—using external knowledge to enhance AI responses—combined with Claude Code's development capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture: Three Memory Layers
&lt;/h2&gt;

&lt;p&gt;The system uses three complementary memory approaches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────────┐
│                     Knowledge Layer                              │
│                                                                  │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐          │
│  │ ChromaDB     │  │ Graph Memory │  │ CLAUDE.md    │          │
│  │ (Vectors)    │  │ (Relations)  │  │ (File)       │          │
│  └──────────────┘  └──────────────┘  └──────────────┘          │
│                                                                  │
│  Collections:          Relationships:     Sections:             │
│  • error_patterns      • error→file       • Known Pitfalls      │
│  • successful_patterns • error→fix        • Successful Patterns │
│  • project_learnings   • fix→file         • Error History       │
│  • meta_learnings      • decision→outcome                       │
└─────────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Layer 1: ChromaDB (Semantic Search)
&lt;/h3&gt;

&lt;p&gt;Vector embeddings enable semantic search across all captured knowledge.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collections:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;error_patterns&lt;/code&gt;: Build failures, type errors, runtime exceptions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;successful_patterns&lt;/code&gt;: What worked—deployment patterns, architecture decisions&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;project_learnings&lt;/code&gt;: Project-specific insights&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;meta_learnings&lt;/code&gt;: Process improvements from self-reflection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I search "authentication errors," I get relevant results even if the exact phrase wasn't used.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: Graph Memory (Relationships)
&lt;/h3&gt;

&lt;p&gt;ChromaDB stores flat documents. But knowledge has structure.&lt;/p&gt;

&lt;p&gt;Graph memory tracks relationships:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error ──occurred_in──→ File
  │
  └──fixed_by──→ Fix ──applied_to──→ File

Decision ──led_to──→ Outcome
                        │
Learning ←──learned_from─┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables queries like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"What errors have occurred in &lt;code&gt;auth.ts&lt;/code&gt;?"&lt;/li&gt;
&lt;li&gt;"What fixes have been applied to the API module?"&lt;/li&gt;
&lt;li&gt;"What decisions led to successful deployments?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Relationships reveal patterns that flat search misses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3: CLAUDE.md (Project Memory)
&lt;/h3&gt;

&lt;p&gt;Each project maintains a &lt;code&gt;CLAUDE.md&lt;/code&gt; file—a living document that Claude reads at session start.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sections:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Known Pitfalls&lt;/strong&gt;: Project-specific gotchas (auto-populated by hooks)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Successful Patterns&lt;/strong&gt;: Code patterns that have worked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error History&lt;/strong&gt;: Recent errors with resolutions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This provides immediate context without database queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatic Learning Capture
&lt;/h2&gt;

&lt;p&gt;The magic is in the hooks—scripts that intercept Claude Code events.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hook: Capture Failures
&lt;/h3&gt;

&lt;p&gt;When a build or test fails:&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;# capture_failure.py (PostToolUse hook)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;capture_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_result&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;tool_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exit_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;store_to_chromadb&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&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;error_pattern&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;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tool_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="nf"&gt;update_graph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;occurred_in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No manual intervention. Failures get captured automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hook: Track File Edits
&lt;/h3&gt;

&lt;p&gt;When files are modified:&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;# log_edit.py (PostToolUse hook)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;log_edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_result&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;tool_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Edit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;update_graph&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&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;applied_to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This builds the error→fix→file relationship over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hook: Session Summary
&lt;/h3&gt;

&lt;p&gt;When a session ends:&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;# session_summary.py (Stop hook)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;session_summary&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;learnings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_learnings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session_history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;update_claude_md&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;learnings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;store_to_chromadb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;learnings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system extracts what was learned and persists it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-Reflection: Meta-Learning
&lt;/h2&gt;

&lt;p&gt;Beyond capturing individual learnings, the system reflects on patterns.&lt;/p&gt;

&lt;p&gt;At session end, a self-reflection agent analyzes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What approaches were effective&lt;/li&gt;
&lt;li&gt;What inefficiencies occurred&lt;/li&gt;
&lt;li&gt;What patterns emerged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These &lt;strong&gt;meta-learnings&lt;/strong&gt; go into a separate collection—insights about the development process itself, not just specific bugs.&lt;/p&gt;

&lt;p&gt;Example meta-learning:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"When debugging TypeScript type errors, checking the imported types first resolves 70% of issues faster than tracing through the codebase."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is knowledge about &lt;em&gt;how&lt;/em&gt; to debug, not just &lt;em&gt;what&lt;/em&gt; broke.&lt;/p&gt;

&lt;h2&gt;
  
  
  Memory Decay: Keeping Knowledge Fresh
&lt;/h2&gt;

&lt;p&gt;Old knowledge becomes stale. A fix that worked six months ago might not apply to the current architecture.&lt;/p&gt;

&lt;p&gt;Memory decay solves this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Half-life&lt;/strong&gt;: 90 days (configurable)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimum weight&lt;/strong&gt;: 0.1 (never fully forgotten)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access boost&lt;/strong&gt;: Recently used memories get +20% weight&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result: Claude prioritizes recent, actively-used knowledge while maintaining a long-term memory of rare but important patterns. This is similar to the token optimization strategies I've outlined for &lt;a href="https://chudi.dev/blog/reduce-ai-token-usage-progressive-disclosure" rel="noopener noreferrer"&gt;reducing AI token usage&lt;/a&gt;, where selective information display keeps context efficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom Commands
&lt;/h2&gt;

&lt;p&gt;The system adds slash commands for manual interaction:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/learn&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Manually capture a learning from the current session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/search-knowledge "query"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Search all memory layers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/review-plan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Validate a plan against past learnings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/reflect&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Trigger self-reflection analysis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/memory-stats&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Show knowledge base statistics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/memory-maintain&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Run decay, merge duplicates, archive old memories&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Most learning happens automatically. These commands are for when you want manual control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Effort-Based Routing
&lt;/h2&gt;

&lt;p&gt;Not every task needs maximum AI capability. The system classifies prompts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Token Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;low&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"What's the syntax for..."&lt;/td&gt;
&lt;td&gt;Fastest, cheapest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;medium&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Implement this feature"&lt;/td&gt;
&lt;td&gt;Balanced&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;high&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Architect the auth system"&lt;/td&gt;
&lt;td&gt;Maximum capability&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Simple questions get fast answers. Complex problems get deep thinking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Results
&lt;/h2&gt;

&lt;p&gt;After two months of use:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before (vanilla Claude Code):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same auth bug: 45 minutes to debug (third time)&lt;/li&gt;
&lt;li&gt;Build failures: Manual pattern recognition&lt;/li&gt;
&lt;li&gt;Session knowledge: Lost after conversation ends&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After (self-improving RAG):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same auth bug: &lt;code&gt;/search-knowledge "auth middleware"&lt;/code&gt; → fix in 2 minutes&lt;/li&gt;
&lt;li&gt;Build failures: Automatic capture, searchable history&lt;/li&gt;
&lt;li&gt;Session knowledge: Persisted, searchable, improving&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The system has captured 150+ error patterns, 45 successful patterns, and 80 meta-learnings across my projects. For more on Claude Code workflows and best practices, see my &lt;a href="https://chudi.dev/blog/claude-code-complete-guide" rel="noopener noreferrer"&gt;comprehensive Claude Code guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The system is available as a configuration you can install:&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;cd&lt;/span&gt; ~/Projects/active/claude-rag-config
./setup.sh

&lt;span class="c"&gt;# Then in any project:&lt;/span&gt;
claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setup installs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hooks for automatic capture&lt;/li&gt;
&lt;li&gt;Custom commands for manual control&lt;/li&gt;
&lt;li&gt;ChromaDB for vector storage&lt;/li&gt;
&lt;li&gt;Graph memory database&lt;/li&gt;
&lt;li&gt;CLAUDE.md template&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Requirements&lt;/strong&gt;: Python 3.9+, Node 18+, ChromaDB (&lt;code&gt;pip install chromadb&lt;/code&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Getting Started: The Minimum Viable RAG Setup
&lt;/h2&gt;

&lt;p&gt;You don't need the full system on day one. Here's the smallest version that actually makes a difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Install ChromaDB&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;chromadb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's your vector store. One command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Create a capture hook&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create a file at &lt;code&gt;~/.claude/hooks/post_tool_use.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chromadb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&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;datetime&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool&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;Bash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exit_code&lt;/span&gt;&lt;span class="sh"&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="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chromadb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PersistentClient&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;~/.claude/memory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_or_create_collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error_patterns&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;error_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;doc_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;md5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error_text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()).&lt;/span&gt;&lt;span class="nf"&gt;hexdigest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;error_text&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;doc_id&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;metadatas&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;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&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="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;continue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This captures every failed Bash command into ChromaDB. No manual intervention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Add a search command&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add this to your CLAUDE.md:&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;## /search-errors command&lt;/span&gt;
When user types /search-errors "query":
&lt;span class="p"&gt;1.&lt;/span&gt; Query ChromaDB error_patterns collection
&lt;span class="p"&gt;2.&lt;/span&gt; Return top 3 similar past errors and their context
&lt;span class="p"&gt;3.&lt;/span&gt; Suggest fixes based on patterns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4: Add a project-specific CLAUDE.md section&lt;/strong&gt;&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;## Known Pitfalls (auto-updated)&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- This section gets updated by the session summary hook --&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the minimum viable setup. Four steps, maybe 20 minutes. You won't have graph memory or self-reflection yet--but you'll have semantic search over your past errors, which is where most of the day-to-day value comes from.&lt;/p&gt;

&lt;p&gt;The auth bug I mentioned at the top of this post? The minimum viable version would have caught it. The error was in the database. The fix was two queries away.&lt;/p&gt;

&lt;p&gt;Build the full system when the minimum version proves itself. For me, that took about 3 weeks.&lt;/p&gt;

&lt;p&gt;The minimum version will change how you think about debugging. Instead of starting from scratch each session, you'll start with a search. That shift alone is worth the 20-minute setup cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;The system keeps improving. Planned additions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cross-project learning&lt;/strong&gt;: Patterns that work in one project suggested in others&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confidence scoring&lt;/strong&gt;: How reliable is this learning based on how often it's worked?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team memory&lt;/strong&gt;: Shared knowledge base across collaborators&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bigger Picture
&lt;/h2&gt;

&lt;p&gt;This isn't just about remembering bugs.&lt;/p&gt;

&lt;p&gt;It's about &lt;strong&gt;accumulating developer judgment&lt;/strong&gt; in a searchable, queryable format.&lt;/p&gt;

&lt;p&gt;Every debugging session teaches something. Without capture, those lessons evaporate. With this system, they compound.&lt;/p&gt;

&lt;p&gt;Claude Code doesn't just help you code. It becomes a repository of everything you've learned about your codebase—and it gets smarter every session.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Questions about implementing this in your workflow? &lt;a href="https://linkedin.com/in/chudinnorukam" rel="noopener noreferrer"&gt;Reach out on LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/engineering/claude-code-best-practices" rel="noopener noreferrer"&gt;Claude Code Best Practices&lt;/a&gt; (Anthropic)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.anthropic.com/en/docs/claude-code/hooks" rel="noopener noreferrer"&gt;Claude Code Hooks Documentation&lt;/a&gt; (Anthropic)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.trychroma.com/" rel="noopener noreferrer"&gt;ChromaDB Documentation&lt;/a&gt; (Chroma)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>claudecode</category>
      <category>rag</category>
      <category>automation</category>
    </item>
    <item>
      <title>Bug Bounty Automation: Building Security Workflows That Scale</title>
      <dc:creator>Chudi Nnorukam</dc:creator>
      <pubDate>Fri, 10 Apr 2026 23:54:50 +0000</pubDate>
      <link>https://dev.to/chudi_nnorukam/i-built-an-ai-workflow-for-bug-bounty-automation-here-is-what-worked-18ge</link>
      <guid>https://dev.to/chudi_nnorukam/i-built-an-ai-workflow-for-bug-bounty-automation-here-is-what-worked-18ge</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://chudi.dev/blog/bug-bounty-automation" rel="noopener noreferrer"&gt;chudi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;My first automated bug bounty scan found 47 "critical" vulnerabilities.&lt;/p&gt;

&lt;p&gt;I submitted 12 reports. Every single one was a false positive.&lt;/p&gt;

&lt;p&gt;The program I targeted now knows my name. Not in a good way.&lt;/p&gt;

&lt;p&gt;That specific embarrassment is what made me rebuild everything from scratch. Not a faster scanner. Not a better scanner. A fundamentally different approach to what automation should and shouldn't do in security research.&lt;/p&gt;

&lt;p&gt;This guide is the result: a complete system for bug bounty automation that actually works in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Bug Bounty Automation Actually Is (and Isn't)
&lt;/h2&gt;

&lt;p&gt;Bug bounty automation is not a script that finds vulnerabilities for you.&lt;/p&gt;

&lt;p&gt;That framing leads directly to 47 false positive submissions and a wrecked reputation.&lt;/p&gt;

&lt;p&gt;What it actually is: a system that handles the mechanical parts of security research — reconnaissance, asset discovery, initial scanning — while keeping humans in control of the decision that matters most: what to submit.&lt;/p&gt;

&lt;p&gt;The best automation makes you a more effective researcher. It doesn't replace your judgment. It amplifies it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What automation handles well:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Subdomain enumeration across certificate transparency logs&lt;/li&gt;
&lt;li&gt;Technology fingerprinting at scale&lt;/li&gt;
&lt;li&gt;Running known payload patterns against hundreds of endpoints simultaneously&lt;/li&gt;
&lt;li&gt;Tracking which findings have been validated vs. just detected&lt;/li&gt;
&lt;li&gt;Generating properly formatted reports for each platform's requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What automation handles poorly:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Novel vulnerability classes that don't match existing patterns&lt;/li&gt;
&lt;li&gt;Context-aware exploitation (is this XSS actually exploitable in this specific app context?)&lt;/li&gt;
&lt;li&gt;Deciding whether a finding is worth a researcher's reputation&lt;/li&gt;
&lt;li&gt;Anything that requires reading the room on a specific target&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding this division is more important than any technical decision you'll make.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Architecture: 4 Agents, One Orchestrator
&lt;/h2&gt;

&lt;p&gt;After rebuilding the system twice, the architecture that works is a 4-agent pipeline coordinated by a central orchestrator.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Orchestrator (Claude Opus)
├── Recon Agents (parallel)
├── Testing Agents (max 4 concurrent)
├── Validation Agent (single, evidence-gated)
└── Reporter Agent (platform-specific formatters)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The orchestrator is a project manager, not a worker. It distributes tasks, manages rate limit budgets, detects agent failures, and persists session state between runs. It never touches an endpoint directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recon Agents
&lt;/h3&gt;

&lt;p&gt;Recon runs in parallel across multiple discovery methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Subdomain enumeration&lt;/strong&gt; via certificate transparency (crt.sh, Censys)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technology fingerprinting&lt;/strong&gt; with httpx to identify frameworks, servers, CDNs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JavaScript analysis&lt;/strong&gt; for hidden endpoints, API keys in source, internal route paths&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GraphQL introspection&lt;/strong&gt; where applicable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All discovered assets feed into a shared SQLite database. Recon agents never block each other — if subdomain enum hits a rate limit, JavaScript analysis keeps running.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing Agents
&lt;/h3&gt;

&lt;p&gt;Testing agents take the recon output and probe for vulnerabilities. I cap these at 4 concurrent to avoid triggering WAFs or rate limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What they test:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IDOR: multi-account replay of authenticated requests&lt;/li&gt;
&lt;li&gt;XSS: payload injection with response diff analysis&lt;/li&gt;
&lt;li&gt;SQL injection: error-based and time-based patterns&lt;/li&gt;
&lt;li&gt;SSRF: metadata service probing, internal network access&lt;/li&gt;
&lt;li&gt;Authentication issues: token fixation, session handling edge cases&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each testing agent handles one vulnerability class. Failure is isolated — if the IDOR agent crashes, XSS testing continues unaffected.&lt;/p&gt;

&lt;h3&gt;
  
  
  Validation Agent: The Most Important Part
&lt;/h3&gt;

&lt;p&gt;Here's the thing most bug bounty automation gets wrong: &lt;strong&gt;detection is not exploitation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My payload appearing in a response means nothing. It might be in an error log that's never rendered, in an HTML attribute that's properly escaped, on a WAF block page, or in a JSON response that's never interpreted as HTML.&lt;/p&gt;

&lt;p&gt;The Validation Agent's only job is to &lt;strong&gt;disprove findings&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The evidence gate process:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every finding starts with a confidence score of 0.0 to 1.0 based on initial detection (around 0.3 for most). Confidence determines routing, not just advancement:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Confidence&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0.85+&lt;/td&gt;
&lt;td&gt;Immediate human review queue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.70–0.84&lt;/td&gt;
&lt;td&gt;Same-day batch review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0.40–0.69&lt;/td&gt;
&lt;td&gt;Weekly review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Below 0.40&lt;/td&gt;
&lt;td&gt;Discarded, pattern logged&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;To reach 0.85+:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Baseline capture:&lt;/strong&gt; Normal request with innocuous input. Record response headers, body length, content type.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PoC execution:&lt;/strong&gt; Same request with malicious payload in a sandboxed environment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response diff analysis:&lt;/strong&gt; Not "does the response contain my payload?" but "does the response differ from baseline in an exploitable way?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;False positive signature matching:&lt;/strong&gt; Known-harmless patterns get auto-dismissed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If PoC succeeds and diff analysis confirms exploitability: confidence rises to 0.85+. Queued for human review.&lt;/p&gt;

&lt;p&gt;If PoC fails: confidence drops. Finding goes to weekly batch review, not discarded.&lt;/p&gt;

&lt;p&gt;This is adversarial validation. The agent is trying to kill findings. Findings that survive are credible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Since implementing this: 0 false positives submitted across 3 months.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The finding lifecycle is a state machine. Findings move through defined states with explicit transitions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;States: new → validating → reviewed → submitted / dismissed

new → validating (automatic)
validating → validating (confidence adjustment, up or down)
validating → reviewed (0.70+ confidence)
reviewed → submitted (human approval)
reviewed → dismissed (human rejection)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Confidence isn't binary. A finding can gain or lose credibility based on evidence at every step.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reporter Agent
&lt;/h3&gt;

&lt;p&gt;Once a finding clears human review and gets approved, the Reporter Agent handles formatting. Every platform has different submission requirements. I built a unified findings model plus platform-specific formatters — write the finding once, output to HackerOne, Intigriti, or Bugcrowd format automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Learning Layer: SQLite RAG
&lt;/h2&gt;

&lt;p&gt;The piece I didn't plan but won't remove.&lt;/p&gt;

&lt;p&gt;Every time an agent hits a rate limit, gets banned, or has a finding dismissed, it logs that to a SQLite database with semantic embeddings. Before running against a new target, the orchestrator queries this database — "have we seen this stack before? what broke?"&lt;/p&gt;

&lt;p&gt;After 3 months of data, the system meaningfully avoids mistakes it's already made. That wasn't in the original design. I added it after watching the system make the same rate-limit mistake on three targets in a row. The fourth target, it slowed down automatically. That was the moment I stopped thinking of this as a script.&lt;/p&gt;

&lt;p&gt;Three tables do most of the work:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Table&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;knowledge_base&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Semantic embeddings of past findings and techniques&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;false_positive_signatures&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Known patterns that look like vulnerabilities but aren't&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;failure_patterns&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Recovery strategies for different error types&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first month is calibration, not hunting. The RAG database starts empty. Every finding is evaluated without prior context, so the false positive rate is higher than steady state. By week 2, the system starts filtering patterns it's already rejected. By week 4, confidence scores mean something specific to your programs and testing patterns. Skip the calibration month and month two is chaos.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Human-in-the-Loop Gate
&lt;/h2&gt;

&lt;p&gt;Full automation for security research is wrong.&lt;/p&gt;

&lt;p&gt;Not in a theoretical sense. Wrong in a "your reputation will be destroyed" sense.&lt;/p&gt;

&lt;p&gt;Consider two hypothetical researchers. Researcher A submits 200 reports, 50 accepted (25% rate). Researcher B submits 50, 40 accepted (80% rate). Programs trust Researcher B. They triage faster. They pay higher. The acceptance rate compounds over months.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Finding cleared by Validation Agent (confidence 0.85+)
    ↓
Human review queue (checked once per day)
    ↓
[APPROVE] → Reporter Agent formats + submits
[DISMISS] → Logged with reason, updates false positive signatures
[INVESTIGATE] → Flagged for manual testing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every submission has been through my eyes before it goes to a program. Non-negotiable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the system will never do:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Submit reports without human approval&lt;/li&gt;
&lt;li&gt;Test targets outside registered bug bounty programs&lt;/li&gt;
&lt;li&gt;Test out-of-scope domains (hard-blocked before execution, not just warned)&lt;/li&gt;
&lt;li&gt;Exaggerate severity for higher bounties&lt;/li&gt;
&lt;li&gt;Auto-resume after a ban without human authorization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After switching to mandatory human review: acceptance rate went above 80%. Programs respond faster because trust is established. Evidence packages prevent disputes.&lt;/p&gt;

&lt;p&gt;The slow-down is worth it. 5 high-quality reports per week beats 50 that damage your reputation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation: Why Detection Isn't Exploitation
&lt;/h2&gt;

&lt;p&gt;The validation layer is what makes or breaks a bug bounty automation system. Most systems skip it. That's why most systems produce garbage.&lt;/p&gt;

&lt;p&gt;A scanner finding your payload in a response proves nothing. The payload might appear in an error message that's never rendered. It might appear HTML-escaped in an attribute. It might appear on a WAF block page explaining what was filtered. Every one of those looks like a vulnerability to a pattern matcher. None of them are.&lt;/p&gt;

&lt;p&gt;Response diff analysis is the fix. Instead of asking "is my payload in the response?" the validation agent asks "does the response differ from baseline in an exploitable way?"&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pattern&lt;/th&gt;
&lt;th&gt;Why It's a False Positive&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Payload in error message&lt;/td&gt;
&lt;td&gt;Error messages aren't rendered as HTML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payload in JSON response&lt;/td&gt;
&lt;td&gt;JSON with correct Content-Type isn't executed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;&amp;amp;lt;script&amp;amp;gt;&lt;/code&gt; in HTML&lt;/td&gt;
&lt;td&gt;Properly escaped, not XSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;403 response with payload&lt;/td&gt;
&lt;td&gt;WAF blocked it, not vulnerable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reflected in &lt;code&gt;src=""&lt;/code&gt; attribute&lt;/td&gt;
&lt;td&gt;Often non-exploitable context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SQL syntax error on invalid input&lt;/td&gt;
&lt;td&gt;Input validation, not injection&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For XSS specifically: regex can't tell you if JavaScript executes. Browser validation via Playwright loads the target page, injects a marker that fires if code runs, and checks whether that marker triggers. If &lt;code&gt;alert()&lt;/code&gt; fires, XSS is confirmed. If not — regardless of how "vulnerable" the response looks — the finding gets rejected.&lt;/p&gt;

&lt;p&gt;The false positive signatures database stores every pattern the system has learned to dismiss. Every rejected finding adds to it. After 3 months, it filters hundreds of known-harmless patterns before they reach the review queue.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before validation:&lt;/strong&gt; ~40 findings per scan, 2-3 valid (90%+ false positive rate).&lt;br&gt;
&lt;strong&gt;After validation:&lt;/strong&gt; ~40 detections, 8-12 survive for human review, 5-7 valid (~40% false positive rate at review stage).&lt;/p&gt;

&lt;p&gt;Still not perfect. But humans now review 12 findings instead of 40 — and 60% of what they see is real.&lt;/p&gt;
&lt;h2&gt;
  
  
  Failure Recovery: The 6 Categories
&lt;/h2&gt;

&lt;p&gt;My testing agent hit a rate limit at 2 AM. It retried immediately. Got rate limited again. Retried. Rate limited. Retried faster. By morning, I was IP-banned from the target's entire infrastructure.&lt;/p&gt;

&lt;p&gt;That specific failure taught me that error handling in security automation isn't optional. Generic retry loops make things worse. Every error needs classification first.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Detection Pattern&lt;/th&gt;
&lt;th&gt;Recovery Strategy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rate Limit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HTTP 429, "too many requests"&lt;/td&gt;
&lt;td&gt;Exponential backoff (2x multiplier, 1hr max)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ban Detected&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CAPTCHA, IP block, consecutive 403s&lt;/td&gt;
&lt;td&gt;Immediate halt + human alert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Auth Error&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;401, expired token, invalid session&lt;/td&gt;
&lt;td&gt;Credential refresh + retry (3 max)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Timeout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No response &amp;gt;30 seconds&lt;/td&gt;
&lt;td&gt;Reduce parallelism + extend timeout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scope Violation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Testing out-of-scope domain&lt;/td&gt;
&lt;td&gt;Remove from queue + blacklist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;False Positive&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Validation rejection&lt;/td&gt;
&lt;td&gt;Log pattern + update signatures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Exponential backoff for rate limits: 30s, 60s, 120s, 240s, capped at 1 hour. The ceiling matters. HackerOne resets rate limits every 15 minutes — waiting 4 hours wastes time.&lt;/p&gt;

&lt;p&gt;Ban detection has highest priority. It checks before rate limit detection. When triggered: all agents stop immediately, human alert fires, session state saves for investigation. Never auto-resume. Human must explicitly authorize continuation.&lt;/p&gt;

&lt;p&gt;Escalation threshold: same error category 5+ times within 5 minutes triggers human intervention. First-occurrence rate limits and single timeouts never escalate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before categorized recovery:&lt;/strong&gt; ~30% of scans interrupted by unhandled errors, bans monthly.&lt;br&gt;
&lt;strong&gt;After:&lt;/strong&gt; ~5% need human intervention, zero bans in 6 months, 200+ learned error signatures.&lt;/p&gt;
&lt;h2&gt;
  
  
  Multi-Platform Integration
&lt;/h2&gt;

&lt;p&gt;HackerOne needs severity ratings with their specific weakness taxonomy. Intigriti wants different field names and inline severity justification. Bugcrowd has unique bounty table structures. Without a unified model, you end up maintaining three separate report generators for the same findings.&lt;/p&gt;

&lt;p&gt;The approach that works: one internal findings model with three platform-specific formatters. Every agent works with the unified model. Platform awareness lives only at two boundaries — ingestion (pulling scope from platforms) and submission (sending reports to platforms). Everything between is platform-agnostic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Finding&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;vulnerabilityType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;VulnType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;cvssVector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;        &lt;span class="c1"&gt;// Full CVSS v3.1 vector&lt;/span&gt;
  &lt;span class="nl"&gt;cvssScore&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="c1"&gt;// Calculated from vector&lt;/span&gt;
  &lt;span class="nl"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;critical&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;high&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;medium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;low&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;informational&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;poc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="nl"&gt;curl&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;script&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;evidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;screenshots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="nl"&gt;requestResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="nl"&gt;hashes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;FindingStatus&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;Each platform formatter implements the same interface: format, validate, submit. They transform the unified Finding into what each platform expects. HackerOne maps vulnerability types to their weakness taxonomy IDs. Intigriti uses different field names. Bugcrowd requires bounty table entries mapped from severity.&lt;/p&gt;

&lt;p&gt;The Budget Manager tracks API rate quotas per platform. Before every API call, agents check canRead() or canWrite(). If exhausted, the request queues until quota resets.&lt;/p&gt;

&lt;p&gt;A first-mover priority system monitors all three platforms for programs launched in the last 24 hours. New programs get immediate passive recon. Active testing starts after a 2-4 hour delay for scope to stabilize. Early submissions on new programs have higher acceptance rates — less competition, more unreported surface area.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools and Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration:&lt;/strong&gt; Claude Opus (orchestrator), Claude Haiku (testing agents)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recon:&lt;/strong&gt; httpx, subfinder, amass, crt.sh API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Testing:&lt;/strong&gt; Custom Python agents per vulnerability class, Playwright for JS analysis&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation:&lt;/strong&gt; Docker sandboxed execution, custom response diff library&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage:&lt;/strong&gt; SQLite with sqlite-vec for semantic search&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform integration:&lt;/strong&gt; HackerOne API, Intigriti API, Bugcrowd API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure:&lt;/strong&gt; VPS ($40/mo) — not serverless, you need persistent state. See my &lt;a href="https://chudi.dev/blog/deploy-python-agent-digitalocean" rel="noopener noreferrer"&gt;Python agent deployment guide&lt;/a&gt; for setup&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Total monthly cost:&lt;/strong&gt; ~$180 ($40 VPS + ~$140 Claude API)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with the Validation Agent, not the scanner.&lt;/strong&gt; The scanner is interesting. The validation layer is what actually matters. Build it first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cap concurrent agents at 4 from day one.&lt;/strong&gt; Started with 10. Got IP-banned from 3 programs in two weeks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build the human review queue before anything else.&lt;/strong&gt; The moment you can submit without a gate is the moment you will. Build the gate first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accept that it won't make you rich quickly.&lt;/strong&gt; This system makes you roughly 3.5x more effective. That's the actual value proposition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Current Results (3 Months In)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;12 active programs being monitored&lt;/li&gt;
&lt;li&gt;~30 findings surfaced for human review per week&lt;/li&gt;
&lt;li&gt;~4-6 submitted after review&lt;/li&gt;
&lt;li&gt;0 false positives submitted&lt;/li&gt;
&lt;li&gt;~$180/month running cost&lt;/li&gt;
&lt;li&gt;~3.5x throughput increase vs. manual research&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Building something similar? The hardest part is the validation layer. Start there — everything else is just plumbing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The multi-agent patterns behind this system are in the &lt;a href="https://chudi.dev/products" rel="noopener noreferrer"&gt;Battle-Tested Builder Kit&lt;/a&gt; — CLAUDE.md templates, agent routing rules, and verification gates you can drop into your own projects.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://owasp.org/www-project-web-security-testing-guide/" rel="noopener noreferrer"&gt;OWASP Web Security Testing Guide&lt;/a&gt; (OWASP)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://owasp.org/www-project-top-ten/" rel="noopener noreferrer"&gt;OWASP Top Ten&lt;/a&gt; (OWASP)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cwe.mitre.org/index.html" rel="noopener noreferrer"&gt;MITRE CWE&lt;/a&gt; (MITRE)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>bugbounty</category>
      <category>automation</category>
      <category>multiagent</category>
      <category>security</category>
    </item>
    <item>
      <title>Claude Code vs Cursor vs GitHub Copilot: Which One Actually Ships Better Production Code?</title>
      <dc:creator>Chudi Nnorukam</dc:creator>
      <pubDate>Fri, 10 Apr 2026 23:54:36 +0000</pubDate>
      <link>https://dev.to/chudi_nnorukam/claude-code-vs-cursor-vs-github-copilot-which-one-actually-ships-better-production-code-3eh8</link>
      <guid>https://dev.to/chudi_nnorukam/claude-code-vs-cursor-vs-github-copilot-which-one-actually-ships-better-production-code-3eh8</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://chudi.dev/blog/claude-code-vs-cursor-vs-copilot" rel="noopener noreferrer"&gt;chudi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I spent three months building a trading bot in production. Real money on the line. 4,000 lines of Python across 22 files. WebSocket feeds from Polymarket, Binance price data, Chainlink oracles, SQLite databases, and a systemd deployment pipeline.&lt;/p&gt;

&lt;p&gt;During those three months, I used Claude Code for 95% of the work. But I also tested Cursor and GitHub Copilot on the exact same codebase to understand where each tool actually excels.&lt;/p&gt;

&lt;p&gt;All three tools are good. But they solve completely different problems.&lt;/p&gt;

&lt;p&gt;Claude Code shipped the bot. Cursor could have shipped it faster if I sat at the keyboard the whole time. Copilot could autocomplete most of it if I knew exactly what I wanted to write.&lt;/p&gt;

&lt;p&gt;I paid for all three tools myself. Claude Code costs me $200/month, Cursor is $20/month, Copilot is $19/month. I have skin in the game to pick the right tool.&lt;/p&gt;

&lt;p&gt;Here's what nobody tells you: picking the wrong tool doesn't just slow you down. It trains you to work differently. I watched a friend spend six months with Copilot autocomplete, then switch to Claude Code and feel completely lost because he'd built a mental model around "I drive, the tool types." Claude Code requires the opposite mental model. The tool drives. You supervise.&lt;/p&gt;

&lt;p&gt;That inversion is where most developers get tripped up. They grab whatever their team already uses, force it to do things it wasn't designed for, and blame the tool when it underperforms. Meanwhile the engineer down the hall using the right tool for the right workflow ships twice as fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Does Each Tool Actually Do Best?
&lt;/h2&gt;

&lt;p&gt;Claude Code is an autonomous agent: it reads your codebase, writes code, runs tests, and fixes failures without you in the loop. Cursor is an IDE built for inline editing speed. GitHub Copilot is autocomplete. Each excels at a different layer of the coding workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for production systems with real money&lt;/strong&gt;: Claude Code (prevents costly mistakes via instruction system)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for code editing speed&lt;/strong&gt;: Cursor (2-3x faster than Claude Code's terminal workflow)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for pure autocomplete&lt;/strong&gt;: GitHub Copilot (trained on GitHub, knows all patterns)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Claude Code&lt;/th&gt;
&lt;th&gt;Cursor&lt;/th&gt;
&lt;th&gt;GitHub Copilot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Multi-file editing&lt;/td&gt;
&lt;td&gt;Autonomous (20+ files)&lt;/td&gt;
&lt;td&gt;Manual per file&lt;/td&gt;
&lt;td&gt;Manual per file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost/month&lt;/td&gt;
&lt;td&gt;$100-200 (Max plan)&lt;/td&gt;
&lt;td&gt;$20 (Pro)&lt;/td&gt;
&lt;td&gt;$10-19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Architecture, refactors&lt;/td&gt;
&lt;td&gt;Inline editing&lt;/td&gt;
&lt;td&gt;Autocomplete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context window&lt;/td&gt;
&lt;td&gt;200K tokens&lt;/td&gt;
&lt;td&gt;128K tokens&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terminal integration&lt;/td&gt;
&lt;td&gt;Native CLI&lt;/td&gt;
&lt;td&gt;IDE plugin&lt;/td&gt;
&lt;td&gt;IDE plugin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Autonomous execution&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Partial&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How Did I Test These? Same Codebase, Same Tasks, Real Metrics
&lt;/h2&gt;

&lt;p&gt;I used the same 4,000-line Python trading bot as the test environment for all three tools. Same five tasks, same codebase, same definition of done: all tests pass, no LSP errors, feature works in production. I timed every task from "start" to "verified complete."&lt;/p&gt;

&lt;p&gt;I didn't run contrived benchmarks. I used each tool to solve actual problems in a real production trading bot.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;4,000 lines across 22 Python files&lt;/li&gt;
&lt;li&gt;WebSocket integrations, asyncio loops, SQLite database layer&lt;/li&gt;
&lt;li&gt;Real external dependencies (py-clob-client, Binance SDK, web3.py, Chainlink feeds)&lt;/li&gt;
&lt;li&gt;87 unit tests&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ol&gt;
&lt;li&gt;Add a new signal source (Chainlink oracle, 150 lines)&lt;/li&gt;
&lt;li&gt;Refactor position tracking across 5 files (200 lines changed)&lt;/li&gt;
&lt;li&gt;Fix a bug in accumulator state machine (10 lines, wrong location)&lt;/li&gt;
&lt;li&gt;Deploy and verify on VPS via SSH&lt;/li&gt;
&lt;li&gt;Write a test file from scratch (80 lines)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;How I measured:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Time from "start" to "all tests pass"&lt;/li&gt;
&lt;li&gt;Number of iterations before correct solution&lt;/li&gt;
&lt;li&gt;Whether the tool caught type errors before runtime&lt;/li&gt;
&lt;li&gt;Whether the tool understood cross-file dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Does Claude Code Win for Production Systems?
&lt;/h2&gt;

&lt;p&gt;Claude Code wins for production systems because it's the only tool that understands your entire codebase, enforces your architecture rules via CLAUDE.md, runs tests autonomously, and catches type errors before runtime. For multi-file work with real money on the line, that autonomy is worth $200/month.&lt;/p&gt;

&lt;p&gt;Claude Code is not a copilot. It's an agent that can explore your codebase, understand dependencies, write code, run tests, and fix failures without you touching the keyboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-file autonomy
&lt;/h3&gt;

&lt;p&gt;I said "add a Chainlink oracle feed to the signal bot." Claude Code:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Explored the codebase structure (Glob, Grep, lsp_workspace_symbols)&lt;/li&gt;
&lt;li&gt;Read existing signal sources to match patterns&lt;/li&gt;
&lt;li&gt;Created the new oracle module&lt;/li&gt;
&lt;li&gt;Wired it into signal_bot.py&lt;/li&gt;
&lt;li&gt;Added it to config.py with safe defaults&lt;/li&gt;
&lt;li&gt;Wrote tests&lt;/li&gt;
&lt;li&gt;Ran the test suite&lt;/li&gt;
&lt;li&gt;Fixed failures without asking&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;150 lines written. Zero follow-ups needed. 45 minutes elapsed. All tests passed on first try.&lt;/p&gt;

&lt;p&gt;Cursor and Copilot could not do this. They would write individual files, and I would have to wire them together, run tests, and tell them what broke. This is the core difference that makes Claude Code a force multiplier for &lt;a href="https://chudi.dev/blog/how-i-build-with-claude-code" rel="noopener noreferrer"&gt;large refactors and architecture work&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The instruction system (CLAUDE.md)
&lt;/h3&gt;

&lt;p&gt;I maintain a project instructions file that Claude Code reads on startup:&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="p"&gt;-&lt;/span&gt; Architecture: "All database operations use async context managers"
&lt;span class="p"&gt;-&lt;/span&gt; Naming: "Signal modules are signal_&lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;.py"
&lt;span class="p"&gt;-&lt;/span&gt; Error handling: "All state machine transitions log to SQLite"
&lt;span class="p"&gt;-&lt;/span&gt; Deployment: "Never use sed -i on .env. Always backup first"
&lt;span class="p"&gt;-&lt;/span&gt; Testing: "Run pytest before deployment. Check lsp_diagnostics for type errors"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude Code follows these instructions. Cursor and Copilot don't even know they exist.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Example: I had a bug where config.py loaded .env via &lt;code&gt;load_dotenv()&lt;/code&gt; on every import. This caused all instances to read the wrong config. The fix was in my instruction file: "Never use load_dotenv(). Pass ENV_PATH explicitly." Claude Code caught this when reviewing other code. Cursor would not.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Type checking and diagnostics
&lt;/h3&gt;

&lt;p&gt;Claude Code runs LSP diagnostics and &lt;code&gt;pytest&lt;/code&gt; before declaring victory. It catches 80% of runtime errors at write time.&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;# Claude Code ran lsp_diagnostics after editing position_executor.py
# Output: error at line 47: "position_id" is not defined
# Claude Code read the file, found the typo, fixed it
# Never got to runtime
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cursor has inline type hints but doesn't proactively check. Copilot has no type awareness. This automated verification is critical for production systems. I built mine with &lt;a href="https://chudi.dev/blog/ai-code-verification-evidence-based" rel="noopener noreferrer"&gt;a two-gate verification system&lt;/a&gt; that Claude Code enforces via the instruction system.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where Claude Code falls short
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Terminal-only workflow.&lt;/strong&gt; Claude Code is a terminal agent. For single-file edits, Cursor is 10x faster. Editing a line in Cursor takes 2 seconds. Editing via Claude Code takes 20 seconds (read, understand, edit, verify, diagnostics).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expensive.&lt;/strong&gt; $200/month on the Max plan. For small projects, it's not worth it. For my use case (22 files, multi-file refactors, real money), Claude Code paid for itself by preventing 2 bugs that would have cost $50+ each. If you're wondering if it's worth the cost, &lt;a href="https://chudi.dev/blog/claude-code-production-trading-bot" rel="noopener noreferrer"&gt;check how I built my trading bot&lt;/a&gt;. That project shows the real ROI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can go off the rails.&lt;/strong&gt; Agents can hallucinate. I've had Claude Code delete the wrong file, write tests that don't test anything, and suggest changes that break other parts. The safety valve is always: "Did you run tests? Are all diagnostics clean?" This is why I built my &lt;a href="https://chudi.dev/blog/ai-code-verification-evidence-based" rel="noopener noreferrer"&gt;AI code verification system&lt;/a&gt;: two gates before every deploy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learning curve.&lt;/strong&gt; You need to understand prompts, git, bash, and &lt;a href="https://chudi.dev/blog/claude-context-management-dev-docs" rel="noopener noreferrer"&gt;context management&lt;/a&gt;. If you're building ADHD-friendly workflows, Claude Code's instruction system is a game-changer: &lt;a href="https://chudi.dev/blog/claude-code-adhd-workflows" rel="noopener noreferrer"&gt;see how I use it for focused work&lt;/a&gt;. Cursor and Copilot work in any IDE without ceremony.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Is Cursor the Fastest for Editing?
&lt;/h2&gt;

&lt;p&gt;Cursor beats every other tool on single-file edit speed. Highlight code, describe the change in chat, accept: 5 seconds versus 25 seconds in Claude Code's terminal workflow. If you spend 4 hours a day editing existing files, Cursor saves you 3+ hours per week. That's the one thing it does better than everything else.&lt;/p&gt;

&lt;p&gt;Cursor is VS Code with AI built in: tab autocomplete trained on your codebase, inline chat, Composer for multi-file editing, and @codebase context that understands your entire repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inline editing speed
&lt;/h3&gt;

&lt;p&gt;I timed myself editing the same file in both tools.&lt;/p&gt;

&lt;p&gt;File: &lt;code&gt;position_executor.py&lt;/code&gt; (200 lines). Task: "Add a size calculation that scales with volatility."&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code: Read file, understand context, edit via Edit tool, verify, run diagnostics = &lt;strong&gt;25 seconds&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Cursor: Highlight region, type in chat, accept changes = &lt;strong&gt;5 seconds&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you spend 4 hours a day editing code, Cursor saves you 3+ hours per week.&lt;/p&gt;

&lt;h3&gt;
  
  
  @codebase understanding
&lt;/h3&gt;

&lt;p&gt;Cursor's @codebase context is genuinely good. I asked "Where are all the places we parse market prices?" and it found all three locations across different files. All correct, all in one search.&lt;/p&gt;

&lt;p&gt;Claude Code can do this via &lt;code&gt;lsp_workspace_symbols&lt;/code&gt; + Grep, but it's more manual.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where Cursor falls short
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Context limits.&lt;/strong&gt; I hit the limit trying to refactor the entire signal pipeline (22 files, 4,000 lines). It could only see 15 files at once. Claude Code has 1M context tokens and can load your entire codebase. &lt;a href="https://chudi.dev/blog/claude-context-management-dev-docs" rel="noopener noreferrer"&gt;See how I manage context for large projects&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No autonomy.&lt;/strong&gt; Cursor requires you to drive each file. I asked it to add an oracle feed. It wrote the oracle module perfectly. But it didn't wire it into signal_bot.py, didn't update config.py, didn't write tests. I had to ask four more times.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No instruction system.&lt;/strong&gt; Cursor has no equivalent to CLAUDE.md. You can't set project-wide rules like "always backup .env before editing." It has no memory of your patterns across sessions. &lt;a href="https://chudi.dev/blog/claude-code-adhd-workflows" rel="noopener noreferrer"&gt;See how I use instruction files for focused work&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Should You Just Use GitHub Copilot?
&lt;/h2&gt;

&lt;p&gt;Use GitHub Copilot when your primary workflow is writing new boilerplate in languages you already know well. It's the cheapest option ($10-19/month), works in every IDE including Vim and PyCharm, and autocompletes class definitions, imports, and repetitive patterns at 5x your typing speed. Don't expect it to understand your architecture.&lt;/p&gt;

&lt;p&gt;Copilot is the narrowest tool: autocomplete. You type, it predicts the next line. And it's genuinely good at that one thing.&lt;/p&gt;

&lt;p&gt;I opened a fresh file and typed &lt;code&gt;class PositionExecutor:&lt;/code&gt; with &lt;code&gt;def __init__&lt;/code&gt;. Copilot predicted the next 8 lines perfectly. Instance variables, type hints, docstring. Hit Tab, done.&lt;/p&gt;

&lt;p&gt;For boilerplate you've written 100 times, Copilot is 5x faster than typing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The trade-off:&lt;/strong&gt; Copilot has no multi-file awareness. It doesn't know your architecture. It doesn't run tests. It doesn't know if the code it autocompleted is correct.&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;# Copilot autocompleted:
&lt;/span&gt;&lt;span class="n"&gt;position_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order_response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Fails: 'id' not in order_response
&lt;/span&gt;
&lt;span class="c1"&gt;# Should be:
&lt;/span&gt;&lt;span class="n"&gt;position_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order_response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;tokenId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# Correct
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copilot doesn't know the difference. It just saw similar patterns on GitHub.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Do They Compare Head-to-Head?
&lt;/h2&gt;

&lt;p&gt;The table below covers every meaningful dimension: autonomy, cost, speed, context limits, and learning curve. Claude Code dominates multi-file work. Cursor dominates single-file speed. Copilot dominates cost and breadth. No single tool wins every category.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Claude Code&lt;/th&gt;
&lt;th&gt;Cursor&lt;/th&gt;
&lt;th&gt;GitHub Copilot&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Autocomplete&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (trained on your codebase)&lt;/td&gt;
&lt;td&gt;Yes (trained on GitHub)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chat with code&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (terminal)&lt;/td&gt;
&lt;td&gt;Yes (inline)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-file understanding&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (LSP + Grep)&lt;/td&gt;
&lt;td&gt;Partial (@codebase limited)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-file editing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (autonomous)&lt;/td&gt;
&lt;td&gt;Partial (Composer)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Autonomous refactoring&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Testing integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (runs pytest)&lt;/td&gt;
&lt;td&gt;No (syntax only)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Type checking&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (LSP diagnostics)&lt;/td&gt;
&lt;td&gt;Partial (IDE background)&lt;/td&gt;
&lt;td&gt;No (IDE only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Instruction system&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Yes (CLAUDE.md)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IDE native&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No (terminal)&lt;/td&gt;
&lt;td&gt;Yes (VS Code)&lt;/td&gt;
&lt;td&gt;Yes (all IDEs)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Single-file edit speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;25s&lt;/td&gt;
&lt;td&gt;5s&lt;/td&gt;
&lt;td&gt;2s (autocomplete)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-file refactor speed&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;45 min (autonomous)&lt;/td&gt;
&lt;td&gt;2-3 hours (manual)&lt;/td&gt;
&lt;td&gt;Not feasible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$200/month&lt;/td&gt;
&lt;td&gt;$20/month&lt;/td&gt;
&lt;td&gt;$19/month&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Learning curve&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High (shell, LSP, git)&lt;/td&gt;
&lt;td&gt;Low (IDE, chat)&lt;/td&gt;
&lt;td&gt;None (autocomplete)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Which Tool Should You Pick?
&lt;/h2&gt;

&lt;p&gt;Pick Claude Code for production systems with multi-file complexity. Pick Cursor if you edit existing code all day and want IDE-native speed. Pick Copilot if autocomplete is enough and you need the cheapest option across all your IDEs. Most serious developers end up using two or all three.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Or use all three.&lt;/strong&gt; They don't conflict. Cursor and Claude Code live in different workflows (IDE vs terminal). Copilot enhances both.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use Cursor for inline editing (fastest for single files)&lt;/li&gt;
&lt;li&gt;Use Claude Code for multi-file refactors and testing&lt;/li&gt;
&lt;li&gt;Use Copilot for autocompleting boilerplate&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What Does This Actually Cost?
&lt;/h2&gt;

&lt;p&gt;All three tools together cost $239/month ($2,868/year). That sounds like a lot until you price your time. Claude Code at $200/month prevented two bugs in my trading bot that would have cost $200+ in lost capital. Cursor at $20/month saves 3-4 hours per week. The math works at senior engineer rates.&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;Price&lt;/th&gt;
&lt;th&gt;Per Year&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;th&gt;ROI&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code Max Plan&lt;/td&gt;
&lt;td&gt;$200/month&lt;/td&gt;
&lt;td&gt;$2,400&lt;/td&gt;
&lt;td&gt;Large codebases, autonomous work, testing&lt;/td&gt;
&lt;td&gt;Prevents 2-3 bugs per month worth $50+ each&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cursor Pro&lt;/td&gt;
&lt;td&gt;$20/month&lt;/td&gt;
&lt;td&gt;$240&lt;/td&gt;
&lt;td&gt;Single-file editing velocity, IDE native&lt;/td&gt;
&lt;td&gt;Saves 3-4 hours per week of keyboard time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Copilot&lt;/td&gt;
&lt;td&gt;$19/month&lt;/td&gt;
&lt;td&gt;$228&lt;/td&gt;
&lt;td&gt;Boilerplate autocomplete, all IDEs&lt;/td&gt;
&lt;td&gt;Saves 1-2 hours per week on routine typing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$239/month&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$2,868&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All three tools together&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Best coverage for all workflows&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For my trading bot project, Claude Code cost $800 over 4 months. It prevented bugs that would have cost me $200+ in lost capital. ROI: 4x.&lt;/p&gt;

&lt;p&gt;For a smaller project (one person, 500 lines), Claude Code is not worth it. Cursor + Copilot at $39/month is the sweet spot.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Difference: Can This Tool Ship Without You?
&lt;/h2&gt;

&lt;p&gt;The only question that matters for production systems: if you step away for an hour, can the tool keep shipping? Claude Code can. Cursor and Copilot cannot. That's the boundary that determines which tool fits your project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude Code:&lt;/strong&gt; Yes. Full codebase understanding, tests, deployment verification, post-deploy error checking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cursor:&lt;/strong&gt; Partially. It can edit files fast, but you drive the sequence. You run tests. You deploy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Copilot:&lt;/strong&gt; No. It's autocomplete. You write the code, it guesses the next line.&lt;/p&gt;

&lt;p&gt;For a trading bot with real money on the line, Claude Code's ability to understand the entire system, write tests, and catch errors before deployment is worth the cost.&lt;/p&gt;

&lt;p&gt;For editing speed and IDE-native workflow, Cursor wins.&lt;/p&gt;

&lt;p&gt;For pure typing speed, Copilot's autocomplete wins.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My workflow today:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code for new features, multi-file refactors, testing&lt;/li&gt;
&lt;li&gt;Cursor for quick edits in the IDE (when I know exactly what to change)&lt;/li&gt;
&lt;li&gt;Copilot for autocompleting boilerplate (when I don't want to type import statements)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three earn their cost.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.github.com/en/copilot" rel="noopener noreferrer"&gt;GitHub Copilot Official Documentation&lt;/a&gt; (GitHub)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cursor.sh/docs" rel="noopener noreferrer"&gt;Cursor Documentation&lt;/a&gt; (Cursor)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.anthropic.com/en/docs/claude-code" rel="noopener noreferrer"&gt;Claude Code Documentation&lt;/a&gt; (Anthropic)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aicodingtools</category>
      <category>claudecode</category>
      <category>cursor</category>
      <category>githubcopilot</category>
    </item>
    <item>
      <title>I Built a 36,000-Line Production Trading Bot With Claude Code</title>
      <dc:creator>Chudi Nnorukam</dc:creator>
      <pubDate>Fri, 10 Apr 2026 23:53:59 +0000</pubDate>
      <link>https://dev.to/chudi_nnorukam/i-built-a-4000-line-production-trading-bot-with-claude-code-1l5p</link>
      <guid>https://dev.to/chudi_nnorukam/i-built-a-4000-line-production-trading-bot-with-claude-code-1l5p</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://chudi.dev/blog/claude-code-production-trading-bot" rel="noopener noreferrer"&gt;chudi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I finished the first version of Polyphemus in 6 weeks. Fully autonomous Polymarket trading bot. 4,000+ lines, real money on the line. Couldn't have shipped it without Claude Code. I also wasted $340 in one month using it wrong.&lt;/p&gt;

&lt;p&gt;Four months later, the same codebase is &lt;strong&gt;36,000+ lines&lt;/strong&gt;. Two live instances running pair arbitrage on BTC/ETH/SOL/XRP. Strategy evolved from directional to market-neutral. Eight silent production bugs found and killed before they touched live capital. All caught by a shadow-first deployment gate I built after month two.&lt;/p&gt;

&lt;p&gt;The five principles that got it to 4,000 lines are the same ones that got it to 36,000 without entropy. Here's the case study, updated April 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Claude Get Dumber as Your Project Gets Bigger?
&lt;/h2&gt;

&lt;p&gt;The bigger your codebase grows, the faster Claude's context window fills up. Each session becomes less useful because the window is full of stale context instead of relevant code. By week three of Polyphemus, I was spending 20 minutes re-explaining context Claude had already "seen." By month one, my token bill hit $340. The problem isn't Claude. It's the missing system around it.&lt;/p&gt;

&lt;p&gt;Every Claude Code guide starts with CLAUDE.md tips. Not wrong, just backwards.&lt;/p&gt;

&lt;p&gt;The first thing I had to understand was not "how do I write better prompts." It was: why does Claude get dumber as my project gets bigger?&lt;/p&gt;

&lt;p&gt;The answer is the context window. Every session loads your project into Claude's working memory. As your codebase grows, that memory fills up faster. By week three, I was re-explaining decisions Claude had made with me two days earlier. Architecture choices, naming conventions, API patterns: all gone. I was paying for Claude to relearn the project I'd already taught it.&lt;/p&gt;

&lt;p&gt;That's not a Claude problem. That's a system problem. And the symptoms compound fast: re-explaining context is demoralizing, it produces worse outputs because you're summarizing instead of being precise, and eventually you stop correcting Claude because it feels pointless. You start accepting mediocre outputs. You start doing the "hard parts" manually. You've turned an AI assistant into an expensive autocomplete. By month one, I was close to giving up on the whole approach.&lt;/p&gt;

&lt;p&gt;Here are the five things that fixed it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 1: Context is a Resource. Manage it Like One.
&lt;/h2&gt;

&lt;p&gt;Most developers treat Claude's context window like unlimited RAM: load everything, let it sort out what matters. That approach blew my token budget by 58% and produced hallucinations on files Claude "remembered" but didn't actually have in scope. The fix is three tiers: always-loaded project identity (under 500 tokens), per-session task file (under 1,000 tokens), and explicit on-demand file loading. Nothing else.&lt;/p&gt;

&lt;p&gt;Tiered context loading in practice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 1. Always loaded (under 500 tokens).&lt;/strong&gt; CLAUDE.md at project root. What the project is, file structure, conventions. Nothing else. The map, not the territory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 2. Per-session (under 1,000 tokens).&lt;/strong&gt; A CURRENT_TASK.md file. What you're building today, what files are involved, what "done" looks like.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tier 3. On demand.&lt;/strong&gt; Specific files, loaded explicitly. "Read src/core/kelly.py before we start."&lt;/p&gt;

&lt;p&gt;Result: average session token usage dropped from ~10,000 to ~4,200 tokens. 58% reduction from one workflow change.&lt;/p&gt;

&lt;p&gt;The rule that makes Tier 3 work: never reference a file by name without loading it first. "Update the execution module" produces hallucination. "Read src/execution/orders.py, then update the retry logic" produces accurate output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 2: Claude's Built-in Memory is Better Than Manual Note-Taking
&lt;/h2&gt;

&lt;p&gt;Claude Code has &lt;a href="https://docs.anthropic.com/en/docs/claude-code/memory" rel="noopener noreferrer"&gt;two memory systems&lt;/a&gt;: the CLAUDE.md file you write by hand, and Auto Memory, which Claude writes itself based on corrections you make. Most developers only use the first. Using both cuts the manual overhead of session management by more than half and produces more accurate recall than notes you wrote yourself.&lt;/p&gt;

&lt;p&gt;I wasted two weeks maintaining a sprawling set of markdown notes before I discovered this. I was carefully updating files that Claude was already tracking more accurately through auto memory.&lt;/p&gt;

&lt;p&gt;What auto memory doesn't do: strategic decisions. If you've chosen PostgreSQL over SQLite for a reason, write that in CLAUDE.md. Auto memory captures patterns. CLAUDE.md captures architecture.&lt;/p&gt;

&lt;p&gt;The CLAUDE.md that actually worked for Polyphemus:&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;# Polyphemus — Claude Context&lt;/span&gt;

&lt;span class="gu"&gt;## What this is&lt;/span&gt;
Autonomous Polymarket trading bot. Real money. Kelly Criterion sizing. 
Never lets an exception stop the main loop.

&lt;span class="gu"&gt;## Hard rules&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Never hardcode API keys. Doppler only.
&lt;span class="p"&gt;-&lt;/span&gt; All amounts in USDC, not cents. One violation cost a real trade.
&lt;span class="p"&gt;-&lt;/span&gt; Log every trade decision with rationale BEFORE executing.
&lt;span class="p"&gt;-&lt;/span&gt; MAX_POSITION_SIZE is a ceiling, not a suggestion.

&lt;span class="gu"&gt;## What we are NOT doing&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; No async. Sync is predictable.
&lt;span class="p"&gt;-&lt;/span&gt; No ML models. Signal threshold is a float.
&lt;span class="p"&gt;-&lt;/span&gt; No framework for the trading loop. Too much magic.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;300 tokens. That's it. Short CLAUDE.md, accurate auto memory, clean context.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 3: Plan Mode Before You Write a Single Line
&lt;/h2&gt;

&lt;p&gt;Plan mode (&lt;code&gt;/plan&lt;/code&gt;) lets Claude research your codebase and propose an approach without making any changes. You review the plan, redirect if needed, then approve. On any task touching more than two files, this single step eliminates the most expensive class of Claude mistake: confident, multi-file output that conflicts with existing architecture.&lt;/p&gt;

&lt;p&gt;Without plan mode, Claude wrote 200 lines of code conflicting with an architectural decision buried in a different file. Confident. Wrong. Two hours lost.&lt;/p&gt;

&lt;p&gt;With plan mode on anything touching more than two files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Me: /plan Add circuit breaker to execution module.
    Pause trading after 3 consecutive losses.

Claude: [researches without touching anything]
        Proposed approach: [plan]
        Files: src/execution/orders.py, src/core/state.py
        I noticed MAX_LOSS_DAILY in src/core/config.py —
        should the circuit breaker integrate with that?

Me: Yes, but use config module, not state.py.

Claude: Understood. Implementing now...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude caught an integration point I hadn't mentioned. I redirected before any code was written. Plan mode costs nothing and consistently saves hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 4: Two Gates, Not One
&lt;/h2&gt;

&lt;p&gt;Two-gate verification means every Claude output clears an automated check (type checks, linting, tests in under 30 seconds) before it gets a human review pass using a fixed 6-question checklist. Before this system, 1 in 6 Claude outputs reached production with an error. After: 1 in 40. On a trading bot, that gap is the difference between an incident log and a boring afternoon.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gate 1 is automated.&lt;/strong&gt; A bash script: type checks, linting, tests. 30 seconds. Catches ~60% of errors.&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;
python &lt;span class="nt"&gt;-m&lt;/span&gt; mypy &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--ignore-missing-imports&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;"✓ Types"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
python &lt;span class="nt"&gt;-m&lt;/span&gt; ruff check &lt;span class="nb"&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;"✓ Lint"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
python &lt;span class="nt"&gt;-m&lt;/span&gt; pytest tests/ &lt;span class="nt"&gt;-q&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;"✓ Tests"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Gate 1 fails, paste the error back: &lt;em&gt;"Fix only what's causing this error. Nothing else."&lt;/em&gt; That last sentence matters. Without it, Claude fixes the error and refactors three other things.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gate 2 is a 6-question checklist.&lt;/strong&gt; Five minutes, manual, non-negotiable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Does this do exactly what I asked — not more?
2. Are external API calls using the correct endpoints?
3. Is error handling present on every async/IO operation?
4. Are there hardcoded values that should be env vars?
5. Does this break anything that was already working?
6. Can I explain every line if someone asks me tomorrow?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Question 6 catches the most issues. If I can't explain a line, I don't ship it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 5: Treat Compaction Like a Power Outage. Plan for It.
&lt;/h2&gt;

&lt;p&gt;When Claude's context window fills, it compacts: nuance gets discarded, recent decisions disappear, and the next response starts from a degraded state. The fix is a pre-compaction ritual at the end of every meaningful session. One prompt to update CURRENT_TASK.md, record new decisions, and write a 2-sentence handoff note for the next Claude instance. Recovery time: 90 seconds.&lt;/p&gt;

&lt;p&gt;At the end of every meaningful session, one prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;We're wrapping up. Please:
1. Update CURRENT_TASK.md with current state
2. Add new decisions to CLAUDE.md's decisions section
3. Write a 2-sentence next-session starter — what the next 
   instance of you needs to know to resume immediately
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That third item is the key. Claude writing handoff notes for Claude produces better handoffs than I can write myself. When a new session starts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read CLAUDE.md and the "Next session" section of CURRENT_TASK.md.
Confirm your understanding before we continue.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;90 seconds. Full speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers, Updated April 2026
&lt;/h2&gt;

&lt;p&gt;These aren't projections. This is the actual state of a production system that started at 4,247 lines in December 2025 and hit 36,096 lines four months later, running pair arbitrage on BTC/ETH/SOL/XRP. Every number below is from a live system, not a benchmark.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before System&lt;/th&gt;
&lt;th&gt;After System&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lines of code&lt;/td&gt;
&lt;td&gt;4,247 (Dec 2025)&lt;/td&gt;
&lt;td&gt;36,096 (Apr 2026)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg session tokens&lt;/td&gt;
&lt;td&gt;~10,000&lt;/td&gt;
&lt;td&gt;~4,200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API cost/month&lt;/td&gt;
&lt;td&gt;$136 (month 1, unoptimized: ~$340)&lt;/td&gt;
&lt;td&gt;$136 ongoing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error rate to production&lt;/td&gt;
&lt;td&gt;1 per 6 outputs&lt;/td&gt;
&lt;td&gt;1 per 40 outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Silent bugs caught by shadow gate&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;8 (none hit live capital)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test coverage&lt;/td&gt;
&lt;td&gt;41%&lt;/td&gt;
&lt;td&gt;73%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Uptime since December&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;99.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code sessions&lt;/td&gt;
&lt;td&gt;~180 (Dec)&lt;/td&gt;
&lt;td&gt;400+ (Apr)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The system rather than the prompts made the difference. Claude Code is a force multiplier. Without a system, it's an expensive way to ship buggy code faster. For a deeper look at verification workflows, see my post on &lt;a href="https://chudi.dev/blog/ai-code-verification-evidence-based" rel="noopener noreferrer"&gt;evidence-based AI code verification&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The 6th principle I'd add today: shadow-first before every live deploy.&lt;/strong&gt; Run a dry-run instance in parallel. Collect evidence. Gate on n_completed &amp;gt;= 50 before promoting. In April alone, that gate caught a bug where &lt;code&gt;set("BTC")&lt;/code&gt; was returning &lt;code&gt;{'B','T','C'}&lt;/code&gt; instead of &lt;code&gt;{'BTC'}&lt;/code&gt;. The bot would have traded the wrong assets live. Eight bugs like that. Zero P&amp;amp;L damage.&lt;/p&gt;

&lt;p&gt;Every principle here was learned the hard way: real bugs, real money at risk, real debugging sessions at 2am on a QuantVPS SSH terminal. I also built a &lt;a href="https://chudi.dev/blog/self-improving-rag-claude-code" rel="noopener noreferrer"&gt;self-improving RAG system&lt;/a&gt; that captures these learnings automatically so future Claude sessions don't repeat past mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Didn't Cover Here
&lt;/h2&gt;

&lt;p&gt;The five principles in this post are the foundation. There's a full advanced layer above them: hooks that run Gate 1 automatically after every file write, subagents routing cheap tasks to smaller models, agent teams for parallel feature development, and MCP servers giving Claude direct live database access. Each of these requires the foundation to be working first.&lt;/p&gt;

&lt;p&gt;The full advanced system is in the &lt;a href="https://chudi.dev/products" rel="noopener noreferrer"&gt;Claude Code Guide: Advanced Edition&lt;/a&gt;. It includes hooks that run Gate 1 automatically after every file write (no manual step), subagents routing cheap tasks to smaller models (44% cost reduction with &lt;a href="https://chudi.dev/blog/reduce-ai-token-usage-progressive-disclosure" rel="noopener noreferrer"&gt;progressive context loading&lt;/a&gt;), agent teams for parallel feature development, checkpointing for safe architectural experiments, and MCP servers giving Claude direct access to the live database for debugging. You need the foundation before the advanced layer is useful.&lt;/p&gt;

&lt;p&gt;The guide includes the complete Polyphemus architecture walkthrough. If you're deploying your own bot, here's my &lt;a href="https://chudi.dev/blog/deploy-python-agent-digitalocean" rel="noopener noreferrer"&gt;step-by-step VPS setup on DigitalOcean&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If this case study was useful, the best thing you can do is send it to one developer still burning money using Claude Code without a system.&lt;/p&gt;

&lt;p&gt;— Chudi&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="mailto:hello@chudi.dev"&gt;hello@chudi.dev&lt;/a&gt; | chudi.dev&lt;/em&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Kelly_criterion" rel="noopener noreferrer"&gt;Kelly Criterion - Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.anthropic.com/en/docs/claude-code/overview" rel="noopener noreferrer"&gt;Claude Code - Anthropic Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.anthropic.com/en/docs/claude-code/memory" rel="noopener noreferrer"&gt;Claude Code Memory Systems - Anthropic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.blog/engineering/ml-ai/how-to-build-effective-ai-powered-code-review-systems/" rel="noopener noreferrer"&gt;Best Practices for AI-Assisted Code Review - GitHub Blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>trading</category>
      <category>production</category>
    </item>
    <item>
      <title>Building a Python Trading Bot: What Actually Works in Production</title>
      <dc:creator>Chudi Nnorukam</dc:creator>
      <pubDate>Fri, 10 Apr 2026 23:53:08 +0000</pubDate>
      <link>https://dev.to/chudi_nnorukam/how-i-built-an-algorithmic-trading-system-with-python-ai-and-live-signals-3l87</link>
      <guid>https://dev.to/chudi_nnorukam/how-i-built-an-algorithmic-trading-system-with-python-ai-and-live-signals-3l87</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://chudi.dev/blog/algorithmic-trading-python-ai-complete-guide" rel="noopener noreferrer"&gt;chudi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  System Architecture Overview
&lt;/h2&gt;

&lt;p&gt;When I started building a trading bot, I expected the hard part to be the trading logic. It wasn't. The hard part was building a system that could run continuously for weeks without losing state, crashing silently, or entering impossible positions.&lt;/p&gt;

&lt;p&gt;A production trading bot needs five core modules that work together:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Signal Generation&lt;/strong&gt; - Monitors price feeds and generates trade signals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Position Management&lt;/strong&gt; - Executes trades, tracks holdings, and prevents overlapping positions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exit Strategies&lt;/strong&gt; - Knows when to close positions and takes profits or cuts losses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State Persistence&lt;/strong&gt; - Survives process crashes, power failures, and restarts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health Monitoring&lt;/strong&gt; - Detects stuck orders, orphaned positions, and api failures&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each module can fail independently, so the system needs to handle partial failures gracefully. A signal can fail without crashing position management. An API call can timeout without losing the position state. The monitoring system watches everything and alerts when something goes wrong.&lt;/p&gt;

&lt;p&gt;The entire codebase is about 4,000 lines of Python. Claude Code wrote 95% of it, including the most complex parts: the asyncio event loop, the database schema and queries, and the deployment scripts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The repo is private for now, but I'm planning to open source the core trading logic once it's hardened further.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Signal Generation
&lt;/h2&gt;

&lt;p&gt;Signals are the input to the entire system. My signals come from two sources: Binance price momentum and Polymarket market odds.&lt;/p&gt;

&lt;p&gt;Binance provides real-time price data via WebSocket. I watch 5-minute price movements and detect breakouts above 2-sigma bands. When BTC price breaks upward sharply, I look for corresponding prediction markets on Polymarket that are underpriced relative to the momentum.&lt;/p&gt;

&lt;p&gt;The signal pipeline is documented in my post on &lt;a href="https://chudi.dev/blog/binance-polymarket-momentum-signal-pipeline" rel="noopener noreferrer"&gt;Binance-Polymarket momentum signal generation&lt;/a&gt;. The key insight is that prediction market prices lag price momentum by 30-90 seconds, creating a small window to profit from the difference.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Momentum Window
&lt;/h3&gt;

&lt;p&gt;Here's how the signal generation actually works in code:&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;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;detect_momentum_breakout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;candles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Watch 5-min BTC candles and detect 2-sigma breakouts.
    Returns signal strength (0-1) if breakout detected.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;closes&lt;/span&gt; &lt;span class="o"&gt;=&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;close&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;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;candles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:]]&lt;/span&gt;  &lt;span class="c1"&gt;# Last 20 candles
&lt;/span&gt;    &lt;span class="n"&gt;mean&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;closes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;closes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;variance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;closes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;closes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;std_dev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;variance&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;

    &lt;span class="n"&gt;current_close&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;closes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;z_score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_close&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;std_dev&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;z_score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Upside breakout
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;z_score&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;3.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Cap at 3-sigma
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a signal fires, the system calculates how many standard deviations above the mean the price has moved. A 2-sigma move occurs about 2% of the time randomly, but when combined with Polymarket underpricing, the edge becomes real.&lt;/p&gt;

&lt;p&gt;The efficiency gap between crypto spot prices and prediction markets exists because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Crypto moves on technicals and sentiment (fast)&lt;/li&gt;
&lt;li&gt;Prediction markets move on fundamental news cycles (slower)&lt;/li&gt;
&lt;li&gt;Retail traders on Polymarket have longer decision latency than algo traders on Binance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This isn't a edge I'll have forever. As more traders build similar systems, the 30-90 second window compresses. But for now, it's consistent enough to trade.&lt;/p&gt;

&lt;p&gt;Signals feed into a queue. The position manager processes signals one at a time, ensuring we never accidentally open two positions on the same market.&lt;/p&gt;

&lt;h2&gt;
  
  
  Position Management
&lt;/h2&gt;

&lt;p&gt;Once a signal arrives, the position manager decides: do we take this trade, or skip it?&lt;/p&gt;

&lt;p&gt;The decision logic checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do we already have a position in this market? If yes, skip.&lt;/li&gt;
&lt;li&gt;Is the order book deep enough to execute at reasonable prices? If no, skip.&lt;/li&gt;
&lt;li&gt;Has this market been active for more than 24 hours? Recent markets are illiquid.&lt;/li&gt;
&lt;li&gt;Are we at our maximum concurrent positions? If yes, skip.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If all checks pass, the position manager places a limit order on the Polymarket CLOB. The CLOB is Polymarket's central limit order book for derivatives trading. It's lower latency than the REST API but requires understanding the order book structure.&lt;/p&gt;

&lt;p&gt;See my detailed post on &lt;a href="https://chudi.dev/blog/how-i-built-polymarket-trading-bot" rel="noopener noreferrer"&gt;building the Polymarket trading bot&lt;/a&gt; for the specifics of CLOB integration.&lt;/p&gt;

&lt;p&gt;The position manager tracks every open position in SQLite. Each position stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Market ID and outcome tokens&lt;/li&gt;
&lt;li&gt;Entry price and quantity&lt;/li&gt;
&lt;li&gt;Timestamp and signal strength&lt;/li&gt;
&lt;li&gt;Current mark-to-market value&lt;/li&gt;
&lt;li&gt;Status (open, closing, closed)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This database survives process restarts. On startup, the position manager reads the database and reconstructs the exact state it was in before the crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exit Strategies
&lt;/h2&gt;

&lt;p&gt;The hardest part of algorithmic trading is exits. Most retail traders focus on entries but skip profitable or know when to close positions. It's the difference between "cool idea" and "actual profit."&lt;/p&gt;

&lt;p&gt;I use three exit types:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Profit target&lt;/strong&gt; - Close 50% of position at 2% profit, rest at 5% profit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stop loss&lt;/strong&gt; - Close entire position if it drops 3% below entry&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time decay&lt;/strong&gt; - If a position hasn't moved in 4 hours, close it (markets that aren't moving are wasting capital)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The exit manager runs every minute, checks all open positions, and executes exits that meet criteria. It places exit orders as limit orders too, so we get the best available prices.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Math on Asymmetric Position Sizing
&lt;/h3&gt;

&lt;p&gt;Here's why the split profit target works. Say I enter a position at $0.45 on a binary market with $100 per trade:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winning trades (55% of the time):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exit 50% at $0.459 (2% profit): +$0.90&lt;/li&gt;
&lt;li&gt;Exit remaining 50% at $0.4725 (5% profit): +$2.25&lt;/li&gt;
&lt;li&gt;Total on winner: +$3.15 (3.15% return on $100)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Losing trades (45% of the time):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stop loss hits at $0.4365 (3% below entry): -$3.00&lt;/li&gt;
&lt;li&gt;Total on loser: -$3.00 (-3% return on $100)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Expected value calculation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EV = (0.55 × $3.15) + (0.45 × -$3.00)
EV = $1.73 + (-$1.35)
EV = +$0.38 per $100 bet
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's positive EV at a 55% win rate. Drop to 54% and the math flips negative. This is why signal quality matters more than quantity. A 60% win rate turns $0.38 per $100 into $0.60 per $100. Signal strength compounds.&lt;/p&gt;

&lt;p&gt;The split exit structure protects against reversal. If the market hits 2% and I've already cashed out half, the second half can either hit 5% or get stopped out. Either way, I've locked 50% of the winning outcome. That's risk management.&lt;/p&gt;

&lt;p&gt;The asymmetry is deliberate. I lose 3% on losers but capture 2-5% on winners because prices move differently on prediction markets. They don't move in smooth linear paths. They bounce. A tight 1% stop gets triggered by noise. A 3% stop lets the position breathe.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Tracking for Reliable Exits
&lt;/h3&gt;

&lt;p&gt;The position database tracks exit status for each open position:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;CREATE TABLE positions (
    id INTEGER PRIMARY KEY,
    market_id TEXT,
    entry_price REAL,
    entry_qty INTEGER,
    entry_time TEXT,
    stop_loss_price REAL,      -- 0.4365 for 0.45 entry
    target_one_price REAL,     -- 0.459 (2% profit)
    target_two_price REAL,     -- 0.4725 (5% profit)
    exit_status TEXT,          -- 'open', 'half_closed', 'closing', 'closed'
    target_one_filled_at TEXT,
    target_two_filled_at TEXT
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On every minute, the exit manager reads current market price and compares against these thresholds. When target one hits, it updates &lt;code&gt;exit_status&lt;/code&gt; to 'half_closed' and records the fill time. This prevents double-exits and ensures the second half of the position doesn't exit prematurely.&lt;/p&gt;

&lt;p&gt;Limit orders are crucial here. Market orders on Polymarket can slip 0.5-1% depending on order book depth. A limit order at target_one_price of $0.459 sits on the book until filled. If the market only reaches $0.458, the order stays open. If it bounces to $0.461, it fills at $0.459 (better than market order at $0.461). Over 100 trades, limit order precision saves 0.3-0.5% in aggregate slippage.&lt;/p&gt;

&lt;p&gt;The real lesson: don't set stop losses too tight on prediction markets. Binary outcomes mean prices bounce around more than equity markets. A 1% stop gets triggered by noise. 3% gives the position room to breathe while still protecting against real reversals.&lt;/p&gt;

&lt;p&gt;Read my post on &lt;a href="https://chudi.dev/blog/directional-betting-binary-markets-math" rel="noopener noreferrer"&gt;betting math for binary markets&lt;/a&gt; to understand the probability calculations that feed into exit sizing.&lt;/p&gt;

&lt;p&gt;The trickiest exit is time decay. Prediction markets converge to 0% or 100% as the event approaches. If I'm long and the market isn't moving, the time decay works against me. Exiting stale positions frees capital for new signals.&lt;/p&gt;

&lt;p&gt;One thing that surprised me: the time decay exit generated more total profit than the profit target exit. Not because individual exits were bigger, but because freeing stale capital meant the bot could take 2-3 more trades per day. Capital velocity matters more than any single trade's P&amp;amp;L.&lt;/p&gt;

&lt;h2&gt;
  
  
  Paper to Live Transition
&lt;/h2&gt;

&lt;p&gt;I paper-traded for 6 weeks before going live. Paper trading means simulating trades without real money, just tracking P&amp;amp;L in spreadsheet.&lt;/p&gt;

&lt;p&gt;Paper trading revealed two strategy failures:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Liquidity trap&lt;/strong&gt; - The culprit was insidious: entry prices looked good because I was catching markets in transition, when stale limit orders sat on the books. But exit? Nightmare. On markets with under &amp;lt; $50K order book depth, I'd win the entry at $0.45 then get forced out at $0.42 because no one was buying. The tight spread at entry reversed hard at exit. Across 30 paper trades, this cost pattern showed up 7 times. Each time: entry P&amp;amp;L looked profitable (+2-3%), but the exit slippage erased it. One trade: up $2.25 on 50% exit at 2% profit, then the final 50% couldn't execute near target. Ended up closing at $0.41 (-4% from entry) because the order book evaporated. That single trade went from +$3 to -$1. Multiply that by 7 failed exits across the paper period, and I'm looking at roughly $2,000 in prevented losses once I added the liquidity check.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fix was a simple gate before position entry:&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;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_market_liquidity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;market_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Only enter if order book has sufficient depth.
    Skip if spread &amp;gt; 1% of mid-price or total depth &amp;lt; threshold.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;order_book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;polymarket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_order_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;market_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;best_bid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order_book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bids&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;best_ask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;order_book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;asks&lt;/span&gt;&lt;span class="sh"&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;best_bid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;best_ask&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="n"&gt;spread_pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;best_ask&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;best_bid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

    &lt;span class="n"&gt;total_depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qty&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;order_book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bids&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; \
                  &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qty&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;qty&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;order_book&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;asks&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][:&lt;/span&gt;&lt;span class="mi"&gt;5&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;spread_pct&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Spread too wide
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total_depth&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# Not enough size to exit
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This single filter would have prevented 7 bad trades and saved ~$2,000 in real losses.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Signal timing&lt;/strong&gt; - The second failure was time-dependent. Signals arriving during European market hours (roughly 2-8 AM UTC, when US traders sleep) got filled at punishment prices. Same signal, same market, but the order book was thin and slow-moving. I'd get a signal on BTC momentum at 5 AM UTC. By the time I placed the order, retail traders on Polymarket hadn't woken up yet. Bid-ask spread was 0.5-1% instead of 0.2%. Fills were 200-300 basis points worse than signals arriving during US peak hours (12pm-11pm UTC). Over the 30 paper trades, only 6 arrived during European dead hours, but those 6 had 0.5-1% worse fills than identical signals during US hours. That's roughly $1,500 in prevented slippage if I'd filtered those out.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The time-of-day filter was even simpler:&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;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_peak_trading_hour&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Only process signals during peak US trading hours.
    12pm-11pm UTC captures most US market activity.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;now_utc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;current_hour&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now_utc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hour&lt;/span&gt;

    &lt;span class="c1"&gt;# Peak hours: 12pm-11pm UTC (7am-6pm EST)
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;current_hour&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;should_process_signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;is_peak_trading_hour&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# Skip this signal
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. One hour check prevented $1,500 in unnecessary slippage by avoiding markets where the book moves like molasses.&lt;/p&gt;

&lt;p&gt;Both failures would have cost real money live. The 6-week cost in time was worth it.&lt;/p&gt;

&lt;p&gt;I ran at least 30 paper trades before going live. That's the minimum to see the major failure modes. Anything less and you're just guessing.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.polymarket.com" rel="noopener noreferrer"&gt;Polymarket CLOB API Documentation&lt;/a&gt; (Polymarket)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://chudi.dev/blog/claude-code-production-trading-bot" rel="noopener noreferrer"&gt;How I Built a 4,000-Line Production Trading Bot With Claude Code&lt;/a&gt; (chudi.dev)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>algorithmictrading</category>
      <category>python</category>
      <category>aibuilding</category>
      <category>polymarket</category>
    </item>
    <item>
      <title>Deploy Python Agents on DigitalOcean for $6/Month</title>
      <dc:creator>Chudi Nnorukam</dc:creator>
      <pubDate>Fri, 10 Apr 2026 23:52:14 +0000</pubDate>
      <link>https://dev.to/chudi_nnorukam/python-agents-on-digitalocean-deploy-in-5-steps-bmb</link>
      <guid>https://dev.to/chudi_nnorukam/python-agents-on-digitalocean-deploy-in-5-steps-bmb</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://chudi.dev/blog/deploy-python-agent-digitalocean" rel="noopener noreferrer"&gt;chudi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; This post contains affiliate links to DigitalOcean. If you sign up through these links, I earn a commission at no extra cost to you, and you get $200 in free credits for 60 days. I ran my trading bot on a DigitalOcean Droplet before migrating to a specialized VPS for lower latency. I recommend DO for Python agents because I used it and it worked.&lt;/p&gt;




&lt;p&gt;My Python trading bot worked perfectly on my laptop. Asyncio event loop, WebSocket connections to Binance, real-time order placement on Polymarket. Clean code. Passed review. Ran great locally.&lt;/p&gt;

&lt;p&gt;Then I tried to deploy it.&lt;/p&gt;

&lt;p&gt;The first attempt was AWS Lambda. Cold starts added 400ms to every signal. The 15-minute timeout killed my long-running WebSocket connections. I spent two days fighting CloudWatch logs before I realized: Lambda was built for HTTP request handlers, not for a process that needs to stay alive.&lt;/p&gt;

&lt;p&gt;Here's what I wish someone had told me: deploying a Python agent that runs 24/7 costs $6/month and takes 30 minutes. Not $50. Not $80. Six dollars.&lt;/p&gt;

&lt;p&gt;I ran my Polymarket trading bot on a $6 DigitalOcean Droplet for months. It processed live Binance price feeds, placed orders on the CLOB, and managed exits autonomously. 69.6% win rate across 23 clean trades. The infrastructure never failed me. The server was not the bottleneck. It never was.&lt;/p&gt;

&lt;p&gt;This is the setup guide that would have saved me those two days on Lambda.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does a Python agent actually need?
&lt;/h2&gt;

&lt;p&gt;Most developers pick their deployment platform based on what they already know. If you've used AWS before, you reach for EC2 or Lambda. If you're a Heroku person, you spin up a dyno. Nobody stops to ask: what are the actual infrastructure requirements?&lt;/p&gt;

&lt;p&gt;A long-running Python agent requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A process that &lt;strong&gt;stays alive&lt;/strong&gt; (not a function that runs and dies)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent connections&lt;/strong&gt; (WebSocket feeds, database connections)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictable cost&lt;/strong&gt; (not pay-per-invocation that spikes when your bot gets active)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full control&lt;/strong&gt; over the runtime (Python version, system packages, cron)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's what it does not need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-scaling (your bot is one process)&lt;/li&gt;
&lt;li&gt;Load balancers (it's not serving HTTP traffic)&lt;/li&gt;
&lt;li&gt;Managed runtimes (you want control, not guardrails)&lt;/li&gt;
&lt;li&gt;A $50/month bill for resources you'll never use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point stings. If you're running a single Python agent on Heroku ($7-25/mo), Railway ($5 + metered usage), or an EC2 instance you forgot to right-size ($15-80/mo depending on how lost you got in the AWS console), you're paying for infrastructure designed for problems you don't have.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do DigitalOcean costs compare to AWS, Heroku, and Railway?
&lt;/h2&gt;

&lt;p&gt;I evaluated four providers before my first deploy. Here's the honest comparison:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;th&gt;The Catch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS EC2&lt;/td&gt;
&lt;td&gt;$8-80/mo&lt;/td&gt;
&lt;td&gt;You already live in AWS&lt;/td&gt;
&lt;td&gt;Console is a maze. Surprise bills are real. A t3.micro costs $8, but you'll add EBS, bandwidth, and by month 3 you're at $35 wondering what happened.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heroku&lt;/td&gt;
&lt;td&gt;$7-25/mo&lt;/td&gt;
&lt;td&gt;Quick web app deploys&lt;/td&gt;
&lt;td&gt;Dynos sleep on the free tier. Paid tier starts at $7 but a worker dyno for a bot is $25. No SSH access. Limited debugging.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://railway.com?referralCode=eMKKpV" rel="noopener noreferrer"&gt;Railway&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;$5 + usage&lt;/td&gt;
&lt;td&gt;Git-push simplicity&lt;/td&gt;
&lt;td&gt;Usage-based pricing sounds cheap until your bot runs 24/7. A busy month can cost $20-40.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.kqzyfj.com/click-101701942-15836243" rel="noopener noreferrer"&gt;DigitalOcean&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$6/mo flat&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bots, agents, anything that runs 24/7&lt;/td&gt;
&lt;td&gt;Fewer regions than AWS. You manage your own server. That's it.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I picked DigitalOcean. $6/month flat. No metered surprises. Full root access. The documentation read like it was written by someone who actually uses the product. I went from zero to running bot in 28 minutes.&lt;/p&gt;

&lt;p&gt;Here's what that $6 gets you: 1 vCPU, 1GB RAM, 25GB SSD, 1TB transfer. My trading bot (asyncio event loop, multiple WebSocket connections, real-time order placement) used about 200MB of that RAM. The CPU barely touched 5% between trading signals.&lt;/p&gt;

&lt;p&gt;Most of you are overpaying. Let me show you the setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  What do you need before starting?
&lt;/h2&gt;

&lt;p&gt;You need three things: Python 3.10 or higher on your local machine, an SSH key pair (generate one with &lt;code&gt;ssh-keygen -t ed25519&lt;/code&gt; if you don't have one), and 30 minutes of uninterrupted time.&lt;/p&gt;

&lt;p&gt;That's literally all the prerequisites.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Create a Droplet (2 Minutes)
&lt;/h2&gt;

&lt;p&gt;Log into DigitalOcean and create a new Droplet:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Region&lt;/strong&gt;: Pick the closest to your data source. For my trading bot, I chose Amsterdam (5-12ms to Polymarket's CLOB in London). For a general agent, pick the region nearest whatever API you call most.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image&lt;/strong&gt;: Ubuntu 24.04 LTS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Size&lt;/strong&gt;: Basic, Regular, $6/month (1 vCPU, 1GB RAM, 25GB SSD)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication&lt;/strong&gt;: SSH Key (paste your public key from &lt;code&gt;~/.ssh/id_ed25519.pub&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Never choose password authentication. SSH keys only. This is non-negotiable. I'll explain why in Step 2.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The Droplet spins up in about 60 seconds. Grab the IP address from the dashboard. Test your connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh root@YOUR_DROPLET_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That root prompt means you have a server. Running. Waiting for your code. For $6/month, you now own a machine that will run 24/7 whether or not you're watching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Lock It Down (10 Minutes)
&lt;/h2&gt;

&lt;p&gt;I skipped this step the first time. A week later, I checked the auth log and found 3,000 brute-force SSH attempts from IPs I'd never seen. Nothing was compromised (SSH keys are strong), but it was a wake-up call.&lt;/p&gt;

&lt;p&gt;Ten minutes of security setup prevents real problems:&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;# Create a deploy user (never run your bot as root)&lt;/span&gt;
adduser &lt;span class="nt"&gt;--disabled-password&lt;/span&gt; agent
usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;agent

&lt;span class="c"&gt;# Copy your SSH key to the new user&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /home/agent/.ssh
&lt;span class="nb"&gt;cp&lt;/span&gt; /root/.ssh/authorized_keys /home/agent/.ssh/
&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; agent:agent /home/agent/.ssh
&lt;span class="nb"&gt;chmod &lt;/span&gt;700 /home/agent/.ssh
&lt;span class="nb"&gt;chmod &lt;/span&gt;600 /home/agent/.ssh/authorized_keys

&lt;span class="c"&gt;# Disable root login and password auth&lt;/span&gt;
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/PermitRootLogin yes/PermitRootLogin no/'&lt;/span&gt; /etc/ssh/sshd_config
&lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="s1"&gt;'s/#PasswordAuthentication yes/PasswordAuthentication no/'&lt;/span&gt; /etc/ssh/sshd_config
systemctl restart sshd

&lt;span class="c"&gt;# Enable the firewall&lt;/span&gt;
ufw allow OpenSSH
ufw &lt;span class="nt"&gt;--force&lt;/span&gt; &lt;span class="nb"&gt;enable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log out. Reconnect as the &lt;code&gt;agent&lt;/code&gt; user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh agent@YOUR_DROPLET_IP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You now have a locked-down server. Root login disabled, password auth disabled, firewall active. This is what separates a production server from a tutorial project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Set Up Python (3 Minutes)
&lt;/h2&gt;

&lt;p&gt;Ubuntu 24.04 ships with Python 3.12. Set up a virtual environment:&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;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; python3-venv python3-pip

&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/my-agent
&lt;span class="nb"&gt;cd&lt;/span&gt; ~/my-agent

python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate

pip &lt;span class="nb"&gt;install &lt;/span&gt;aiohttp websockets python-dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Always use a venv. I learned this the hard way when a system-level pip install broke apt's Python dependencies on my first Droplet. Took me an hour to untangle. A venv takes 10 seconds to create and prevents that entirely.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Step 4: Deploy Your Agent (5 Minutes)
&lt;/h2&gt;

&lt;p&gt;Here's a production-ready async agent skeleton. This is the same pattern my trading bot used. Replace the inner logic with whatever your agent does:&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;# ~/my-agent/agent.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&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;datetime&lt;/span&gt;

&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basicConfig&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="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%(asctime)s [%(levelname)s] %(message)s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;handlers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;FileHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent.log&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;StreamHandler&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;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&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;shutdown_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Event&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;handle_signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&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;Received signal &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, shutting down gracefully...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;shutdown_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Your agent logic goes here.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&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;Processing: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_agent&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Main agent loop. Replace with your real logic.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent started&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;cycle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;shutdown_event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_set&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;process_tick&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cycle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cycle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()})&lt;/span&gt;
            &lt;span class="n"&gt;cycle&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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;Agent error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent stopped cleanly&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;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGTERM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handle_signal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handle_signal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;run_agent&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a &lt;code&gt;.env&lt;/code&gt; file for configuration:&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;# ~/my-agent/.env&lt;/span&gt;
&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_api_key_here
&lt;span class="nv"&gt;POLL_INTERVAL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;span class="nv"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;INFO
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test it on the Droplet:&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;cd&lt;/span&gt; ~/my-agent
&lt;span class="nb"&gt;source &lt;/span&gt;venv/bin/activate
python3 agent.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see log output. Press Ctrl+C to stop. If it runs for 30 seconds clean, you're ready for the part most tutorials skip.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you keep a Python agent running 24/7 on a Droplet?
&lt;/h2&gt;

&lt;p&gt;Use systemd. This is the critical step separating "I deployed my bot" from "my bot runs in production."&lt;/p&gt;

&lt;p&gt;Running your agent in &lt;code&gt;screen&lt;/code&gt; or &lt;code&gt;tmux&lt;/code&gt; kills it when SSH disconnects, server reboots, or when your code crashes at 3am with nobody watching. I lost 6 hours of trading signals before learning about systemd.&lt;/p&gt;

&lt;p&gt;systemd handles three essential tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Automatically restarts your agent if it crashes&lt;/li&gt;
&lt;li&gt;Starts it on server reboot without manual intervention&lt;/li&gt;
&lt;li&gt;Manages logs for debugging and audit trails&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Create a service file:&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;sudo tee&lt;/span&gt; /etc/systemd/system/my-agent.service &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
[Unit]
Description=My Python Agent
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=agent
WorkingDirectory=/home/agent/my-agent
Environment=PATH=/home/agent/my-agent/venv/bin:/usr/bin
EnvironmentFile=/home/agent/my-agent/.env
ExecStart=/home/agent/my-agent/venv/bin/python3 agent.py
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enable and start:&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;sudo &lt;/span&gt;systemctl daemon-reload
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;my-agent
&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl start my-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check it:&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;sudo &lt;/span&gt;systemctl status my-agent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;active (running)&lt;/code&gt;. Your agent now survives reboots, crashes, and SSH disconnects. Close your laptop. Go to sleep. It keeps running.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: What are the common mistakes after deployment?
&lt;/h2&gt;

&lt;p&gt;I hit all of these. You'll hit at least two.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. "ModuleNotFoundError" after deploy&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;systemd runs its own environment. If &lt;code&gt;ExecStart&lt;/code&gt; points to &lt;code&gt;python3&lt;/code&gt; instead of &lt;code&gt;/home/agent/my-agent/venv/bin/python3&lt;/code&gt;, it uses the system Python which doesn't have your packages. Exact path. Every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Agent dies silently after 6 hours&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An unhandled exception in the main loop. Without the &lt;code&gt;try/except&lt;/code&gt; wrapper, the agent crashes, systemd restarts it, it crashes again on the same data, and you hit the restart rate limit. The backoff sleep is not optional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Disk fills up from logs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;journalctl handles systemd logs, but your &lt;code&gt;agent.log&lt;/code&gt; file grows without bounds. I discovered this after a 2GB log file ate my 25GB disk. Add log rotation:&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;sudo tee&lt;/span&gt; /etc/logrotate.d/my-agent &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
/home/agent/my-agent/agent.log {
    daily
    rotate 7
    compress
    missingok
    notifempty
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. SSH disconnect kills the agent&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Only happens if you started with &lt;code&gt;python3 agent.py &amp;amp;&lt;/code&gt; in a shell session. systemd doesn't have this problem. If you're still running bots in tmux: stop. Use systemd.&lt;/p&gt;

&lt;h2&gt;
  
  
  How do you deploy code changes to your running agent?
&lt;/h2&gt;

&lt;p&gt;When you update your agent code:&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;# From your local machine&lt;/span&gt;
scp agent.py agent@YOUR_DROPLET_IP:~/my-agent/

&lt;span class="c"&gt;# Restart the service&lt;/span&gt;
ssh agent@YOUR_DROPLET_IP &lt;span class="s2"&gt;"sudo systemctl restart my-agent"&lt;/span&gt;

&lt;span class="c"&gt;# Verify clean start&lt;/span&gt;
ssh agent@YOUR_DROPLET_IP &lt;span class="s2"&gt;"sudo journalctl -u my-agent -n 10 --no-pager"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three commands. Under 10 seconds. I started with &lt;code&gt;scp&lt;/code&gt; and switched to git-based deploys after the third time I forgot to push a dependency file. For a single agent, &lt;code&gt;scp&lt;/code&gt; is fine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What should you verify before going live?
&lt;/h2&gt;

&lt;p&gt;Before you trust your agent with real work or money:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] &lt;strong&gt;SSH key auth only&lt;/strong&gt; (password auth disabled)&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Firewall active&lt;/strong&gt; (&lt;code&gt;ufw status&lt;/code&gt; shows SSH allowed, everything else denied)&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Non-root user&lt;/strong&gt; running the agent&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;systemd service&lt;/strong&gt; with &lt;code&gt;Restart=always&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Error handling&lt;/strong&gt; in the main loop (catch, log, backoff, continue)&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Signal handlers&lt;/strong&gt; for SIGTERM/SIGINT&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Log rotation&lt;/strong&gt; configured&lt;/li&gt;
&lt;li&gt;[ ] &lt;strong&gt;Monitoring&lt;/strong&gt; (check logs daily until stable)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your agent handles money, also add: position limits, stop-losses, health check endpoints, and alert notifications. I documented the full trading bot architecture, risk management, and signal detection patterns in my &lt;a href="https://chudi.dev/blog/how-i-built-polymarket-trading-bot" rel="noopener noreferrer"&gt;Polymarket trading bot guide&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When do you outgrow a $6 Droplet?
&lt;/h2&gt;

&lt;p&gt;I migrated away from DigitalOcean when my trading strategy demanded sub-10ms round-trip latency to Polymarket's CLOB. The Amsterdam region provided 5-12ms, which worked for my initial strategy. When I needed 3-5ms consistency, I switched to a specialized VPS provider colocated near the exchange.&lt;/p&gt;

&lt;p&gt;That migration took 4 months. It only mattered because I was doing latency arbitrage where 100ms cost me money.&lt;/p&gt;

&lt;p&gt;For most Python agents, you will not outgrow a $6 Droplet for years.&lt;/p&gt;

&lt;p&gt;You'll know it's time to upgrade when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Milliseconds matter&lt;/strong&gt;: Your agent's profitability depends on execution speed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple agents&lt;/strong&gt;: You're running 4+ processes and hitting CPU/RAM limits&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GPU inference&lt;/strong&gt;: Your agent runs ML models that need a GPU&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance&lt;/strong&gt;: You need certifications or regions DO doesn't offer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Until then, every month you spend more than $6 on infrastructure for a single Python agent is money you didn't need to spend.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start Here
&lt;/h2&gt;

&lt;p&gt;If you've been running your bot on Lambda hitting execution timeouts, paying $25/month for a Heroku worker dyno, or avoiding deployment because AWS console complexity paralyzes you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://www.kqzyfj.com/click-101701942-15836243" rel="noopener noreferrer"&gt;Sign up for DigitalOcean&lt;/a&gt; (claim &lt;strong&gt;$200 in free credits&lt;/strong&gt; for 60 days, enough to test this entire setup cost-free)&lt;/li&gt;
&lt;li&gt;Follow the 6-step process above (30 minutes total, no dependencies)&lt;/li&gt;
&lt;li&gt;Replace the example agent skeleton with your actual logic&lt;/li&gt;
&lt;li&gt;Run through the production checklist and monitor for 48 hours&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Result: $6/month. 30 minutes setup time. Your agent running 24/7 on infrastructure you control completely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For trading bot builders:&lt;/strong&gt; Start with my guide on &lt;a href="https://chudi.dev/blog/how-i-built-polymarket-trading-bot" rel="noopener noreferrer"&gt;building a Polymarket trading bot&lt;/a&gt; to understand signal detection, order placement, risk management, and profitability metrics. Then return here for the deployment infrastructure. My bot achieved 69.6% win rate on this exact setup before migrating for latency reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For general AI agents:&lt;/strong&gt; This Droplet setup works equally well for data crawlers, scheduled scrapers, webhook processors, LLM orchestration systems, and autonomous agents. The pattern applies to any Python process that needs 24/7 uptime without paying cloud premium prices.&lt;/p&gt;

&lt;p&gt;What are you deploying? I'm curious what agents people are running on VPS these days.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.digitalocean.com/products/droplets/" rel="noopener noreferrer"&gt;DigitalOcean Droplet Documentation&lt;/a&gt; (DigitalOcean)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.freedesktop.org/software/systemd/man/systemd.service.html" rel="noopener noreferrer"&gt;systemd Service Units&lt;/a&gt; (freedesktop.org)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.python.org/3/library/asyncio.html" rel="noopener noreferrer"&gt;Python asyncio Documentation&lt;/a&gt; (Python Software Foundation)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.digitalocean.com/products/droplets/how-to/add-ssh-keys/" rel="noopener noreferrer"&gt;DigitalOcean SSH Key Setup&lt;/a&gt; (DigitalOcean)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>agents</category>
      <category>infrastructure</category>
      <category>deployment</category>
    </item>
    <item>
      <title>Claude Code Hooks Tutorial: 4 Production Patterns for Code Guardrails</title>
      <dc:creator>Chudi Nnorukam</dc:creator>
      <pubDate>Fri, 10 Apr 2026 23:51:56 +0000</pubDate>
      <link>https://dev.to/chudi_nnorukam/build-ai-code-guardrails-claude-hooks-in-5-steps-4k04</link>
      <guid>https://dev.to/chudi_nnorukam/build-ai-code-guardrails-claude-hooks-in-5-steps-4k04</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://chudi.dev/blog/claude-code-hooks-tutorial" rel="noopener noreferrer"&gt;chudi.dev&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;I ran a secret scanner on every project for months before I realized Claude Code was writing &lt;code&gt;.env&lt;/code&gt; files with real credentials baked in. Not because it was malicious. Just because the context had a key, and it needed a value.&lt;/p&gt;

&lt;p&gt;The fix took five minutes once I knew hooks existed.&lt;/p&gt;

&lt;p&gt;Claude Code hooks let you run any shell command automatically when tool events fire. Before a file gets written, after a bash command runs, when Claude finishes a task. You get full context about what's happening via stdin, and for PreToolUse hooks, you can block the operation entirely.&lt;/p&gt;

&lt;p&gt;This is the guide I wish I had when I started.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Claude Code Hooks and Why Do You Need Them?
&lt;/h2&gt;

&lt;p&gt;Claude Code is an autonomous agent. It reads files, writes code, runs commands, and makes decisions faster than you can review each one. That autonomy is the point. But it creates a gap: how do you enforce standards without reviewing every action manually?&lt;/p&gt;

&lt;p&gt;Hooks close that gap. They're your enforcement layer — running in the background, checking every operation against your rules, and either approving it or blocking it before any damage is done.&lt;/p&gt;

&lt;p&gt;Think of them as middleware for your AI agent. The tool fires an event, your hook intercepts it, does its check, and returns a decision. If the hook returns &lt;code&gt;{"continue": false}&lt;/code&gt;, Claude stops. If it returns &lt;code&gt;{"continue": true}&lt;/code&gt; (or nothing), Claude proceeds.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Are the Four Claude Code Hook Events?
&lt;/h2&gt;

&lt;p&gt;Claude Code exposes four events you can hook into (see &lt;a href="https://docs.anthropic.com/en/docs/claude-code/hooks" rel="noopener noreferrer"&gt;official hooks docs&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PreToolUse&lt;/strong&gt; — fires before any tool runs. You can inspect the tool input and block execution. This is where guardrails live.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PostToolUse&lt;/strong&gt; — fires after a tool completes. You get the tool output. Use this for logging, formatting, or triggering follow-on actions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Notification&lt;/strong&gt; — fires when Claude sends a notification (waiting for input, task complete, etc.). Good for custom alerts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop&lt;/strong&gt; — fires when the agent finishes a task. Use this for cleanup, summaries, or Slack notifications.&lt;/p&gt;

&lt;p&gt;There's also &lt;strong&gt;SubagentStop&lt;/strong&gt; which fires when a subagent finishes, if you're running parallel agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Do You Configure Claude Code Hooks?
&lt;/h2&gt;

&lt;p&gt;Everything goes in &lt;code&gt;~/.claude/settings.json&lt;/code&gt;. The structure looks like this:&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;"Write|Edit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/you/scripts/scan-secrets.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="nl"&gt;"PostToolUse"&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;"Edit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/you/scripts/auto-format.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="nl"&gt;"Stop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/you/scripts/notify-complete.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;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;matcher&lt;/code&gt; field is a regex matched against the tool name. &lt;code&gt;Write|Edit&lt;/code&gt; matches both the Write tool and the Edit tool. Leave it out to match all tools for that event.&lt;/p&gt;

&lt;h2&gt;
  
  
  What your hook receives
&lt;/h2&gt;

&lt;p&gt;Every hook gets a JSON object via stdin. For a PreToolUse hook on the Write tool, it looks like this:&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;"session_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;"abc123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hook_event_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;"PreToolUse"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tool_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;"Write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tool_input"&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;"file_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;"/Users/you/project/.env"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STRIPE_SECRET_KEY=sk_live_abc123..."&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;For a Bash tool, &lt;code&gt;tool_input&lt;/code&gt; contains &lt;code&gt;command&lt;/code&gt; instead of &lt;code&gt;file_path&lt;/code&gt;. For Edit, you get &lt;code&gt;file_path&lt;/code&gt;, &lt;code&gt;old_string&lt;/code&gt;, and &lt;code&gt;new_string&lt;/code&gt;. The shape matches the tool's schema.&lt;/p&gt;

&lt;p&gt;PostToolUse hooks also get &lt;code&gt;tool_response&lt;/code&gt; — the actual output the tool returned.&lt;/p&gt;

&lt;h2&gt;
  
  
  What your hook must return
&lt;/h2&gt;

&lt;p&gt;This is the part that trips people up.&lt;/p&gt;

&lt;p&gt;If your hook writes anything to stdout, it must be valid JSON. The Claude Code protocol reads stdout as structured data. If you print plain text, you'll get protocol errors.&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;# WRONG - will break the protocol&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Scanning for secrets..."&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"continue": true}'&lt;/span&gt;

&lt;span class="c"&gt;# RIGHT - suppress all non-JSON output&lt;/span&gt;
&lt;span class="nb"&gt;exec &lt;/span&gt;2&amp;gt;/dev/null
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"continue": true}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The valid return fields are:&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;"continue"&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;"suppressOutput"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"approve"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No secrets found"&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;&lt;code&gt;continue: false&lt;/code&gt; blocks the tool. &lt;code&gt;suppressOutput: true&lt;/code&gt; hides the hook output from Claude's context. &lt;code&gt;reason&lt;/code&gt; gets shown in the UI when you block.&lt;/p&gt;

&lt;p&gt;If your script exits with code 0 and returns nothing, Claude proceeds. If it exits non-zero, Claude treats it as a blocking error.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hook 1: Secret scanner
&lt;/h2&gt;

&lt;p&gt;This is the one I wish I'd had from day one. It runs before any Write or Edit and blocks the operation if it finds credentials.&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/scripts/scan-secrets.sh&lt;/span&gt;

&lt;span class="nb"&gt;exec &lt;/span&gt;2&amp;gt;/dev/null

&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;CONTENT&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; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, sys
d = json.load(sys.stdin)
ti = d.get('tool_input', {})
print(ti.get('content', '') + ti.get('new_string', ''))
"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Check for common secret patterns&lt;/span&gt;
&lt;span class="nv"&gt;PATTERNS&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;
  &lt;span class="s1"&gt;'sk_live_[A-Za-z0-9]+'&lt;/span&gt;
  &lt;span class="s1"&gt;'xoxb-[A-Za-z0-9-]+'&lt;/span&gt;
  &lt;span class="s1"&gt;'AKIA[A-Z0-9]{16}'&lt;/span&gt;
  &lt;span class="s1"&gt;'ghp_[A-Za-z0-9]{36}'&lt;/span&gt;
  &lt;span class="s1"&gt;'rpa_[A-Za-z0-9]+'&lt;/span&gt;
  &lt;span class="s1"&gt;'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;pattern &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$\&lt;/span&gt;&lt;span class="s2"&gt;{PATTERNS[@]}"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  if &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;$CONTENT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pattern&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;continue&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: false, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;reason&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Blocked: potential secret detected matching pattern &lt;/span&gt;&lt;span class="nv"&gt;$pattern&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
  &lt;span class="k"&gt;fi
done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"continue": true}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register it in settings.json:&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;"Write|Edit|NotebookEdit"&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;"/Users/you/.claude/scripts/scan-secrets.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;p&gt;Now every file write goes through the scanner. If it finds a Stripe live key, Slack token, or AWS key, it blocks with a reason Claude can read and explain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hook 2: Auto-formatter
&lt;/h2&gt;

&lt;p&gt;After Claude edits a TypeScript or Python file, run the formatter automatically. No more "Claude wrote valid code but wrong indentation."&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/scripts/auto-format.sh&lt;/span&gt;

&lt;span class="nb"&gt;exec &lt;/span&gt;2&amp;gt;/dev/null

&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;FILE&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; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, sys
d = json.load(sys.stdin)
print(d.get('tool_input', {}).get('file_path', ''))
"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"continue": true}'&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&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;.ts|&lt;span class="k"&gt;*&lt;/span&gt;.tsx&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; prettier &amp;amp;&amp;gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; prettier &lt;span class="nt"&gt;--write&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &amp;amp;&amp;gt;/dev/null
    &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;.py&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; ruff &amp;amp;&amp;gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ruff format &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &amp;amp;&amp;gt;/dev/null
    &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"continue": true}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostToolUse on Edit:&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;"PostToolUse"&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;"Edit|Write"&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;"/Users/you/.claude/scripts/auto-format.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;p&gt;The formatter runs silently after every edit. Claude's next read of the file sees clean, formatted code without any back-and-forth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hook 3: Slack notification on task complete
&lt;/h2&gt;

&lt;p&gt;I work with Claude running in the background while I do other things. The Stop hook lets me know when it's done without watching the terminal.&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/scripts/notify-complete.sh&lt;/span&gt;

&lt;span class="nb"&gt;exec &lt;/span&gt;2&amp;gt;/dev/null

&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$\&lt;/span&gt;&lt;span class="s2"&gt;{SLACK_BOT_TOKEN:-}"&lt;/span&gt;
&lt;span class="nv"&gt;CHANNEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$\&lt;/span&gt;&lt;span class="s2"&gt;{SLACK_NOTIFY_CHANNEL:-}"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CHANNEL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"continue": true}'&lt;/span&gt;
  &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="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;SESSION&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; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, sys
print(json.load(sys.stdin).get('session_id', 'unknown'))
"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"unknown"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

curl &lt;span class="nt"&gt;-sf&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://slack.com/api/chat.postMessage &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;channel&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="nv"&gt;$CHANNEL&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;,&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:white_check_mark: Claude finished task (session: &lt;/span&gt;&lt;span class="nv"&gt;$SESSION&lt;/span&gt;&lt;span class="s2"&gt;)&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"continue": true}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Stop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="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;"/Users/you/.claude/scripts/notify-complete.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;p&gt;Now when a long refactor finishes, my phone buzzes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hook 4: Approval gate for destructive bash commands
&lt;/h2&gt;

&lt;p&gt;This one requires more care. Some bash commands are irreversible — dropping databases, deleting branches, modifying production configs. The PreToolUse hook on Bash lets you intercept these.&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/scripts/approve-destructive.sh&lt;/span&gt;

&lt;span class="nb"&gt;exec &lt;/span&gt;2&amp;gt;/dev/null

&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; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
import json, sys
print(json.load(sys.stdin).get('tool_input', {}).get('command', ''))
"&lt;/span&gt; 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;DESTRUCTIVE_PATTERNS&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;
  &lt;span class="s1"&gt;'rm -rf'&lt;/span&gt;
  &lt;span class="s1"&gt;'drop table'&lt;/span&gt;
  &lt;span class="s1"&gt;'DROP TABLE'&lt;/span&gt;
  &lt;span class="s1"&gt;'git push --force'&lt;/span&gt;
  &lt;span class="s1"&gt;'git reset --hard'&lt;/span&gt;
  &lt;span class="s1"&gt;'kubectl delete'&lt;/span&gt;
  &lt;span class="s1"&gt;'systemctl stop'&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;pattern &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$\&lt;/span&gt;&lt;span class="s2"&gt;{DESTRUCTIVE_PATTERNS[@]}"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  if &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;$CMD&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qF&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pattern&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;continue&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: false, &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;reason&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Blocked: '&lt;/span&gt;&lt;span class="nv"&gt;$pattern&lt;/span&gt;&lt;span class="s2"&gt;' requires explicit approval. Run the command manually if intended.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;}"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;0
  &lt;span class="k"&gt;fi
done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"continue": true}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This doesn't ask for approval interactively — that would deadlock. Instead it blocks and explains. You review the command, run it yourself if it's correct, and Claude continues from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas that cost me time
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Shell profile output breaks hooks.&lt;/strong&gt; If your &lt;code&gt;.zshrc&lt;/code&gt; or &lt;code&gt;.bashrc&lt;/code&gt; prints anything (greeting messages, nvm output, conda activation), it will pollute the hook stdout. Either suppress it or use &lt;code&gt;exec 2&amp;gt;/dev/null&lt;/code&gt; at the top of every hook script.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hooks run in a non-interactive shell.&lt;/strong&gt; Your PATH, aliases, and shell functions aren't loaded. Use full absolute paths to commands (&lt;code&gt;/opt/homebrew/bin/prettier&lt;/code&gt;, not &lt;code&gt;prettier&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PreToolUse latency adds up.&lt;/strong&gt; If your hook takes 500ms and Claude runs 50 Edit operations, that's 25 extra seconds. Profile your hooks. Secret scanning should be under 50ms. If it's slow, check for regex backtracking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The matcher is a regex, not a glob.&lt;/strong&gt; &lt;code&gt;Write|Edit&lt;/code&gt; works. &lt;code&gt;Write*&lt;/code&gt; does not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Empty stdout is fine, but non-JSON stdout breaks things.&lt;/strong&gt; Add &lt;code&gt;exec 2&amp;gt;/dev/null&lt;/code&gt; to redirect stderr, then only ever &lt;code&gt;echo&lt;/code&gt; valid JSON.&lt;/p&gt;

&lt;h2&gt;
  
  
  My actual settings.json hooks section
&lt;/h2&gt;

&lt;p&gt;This is what I run across all projects:&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;"Write|Edit|NotebookEdit"&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;"/Users/chudinnorukam/.claude/scripts/scan-secrets.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="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;"/Users/chudinnorukam/.claude/scripts/approve-destructive.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="nl"&gt;"PostToolUse"&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;"Edit|Write"&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;"/Users/chudinnorukam/.claude/scripts/auto-format.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="nl"&gt;"Stop"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="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;"/Users/chudinnorukam/.claude/scripts/notify-complete.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;p&gt;Four hooks. They cover the 90% case: secrets, destructive commands, formatting, and completion notifications. Everything else I handle manually because the hook overhead isn't worth it for low-frequency events.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go from here
&lt;/h2&gt;

&lt;p&gt;Hooks are composable. You can chain multiple hooks on the same event. You can use them to log every tool call to a file for auditing. You can build approval workflows that post to Slack and wait for a reply before proceeding.&lt;/p&gt;

&lt;p&gt;The pattern I'm building toward: a full audit log of every Claude action, with replay capability. Every Write, Edit, and Bash call gets logged with the session ID, timestamp, and tool input. When something goes wrong, I can reconstruct exactly what happened and in what order.&lt;/p&gt;

&lt;p&gt;That's the next post. For now, start with the secret scanner. It's the one hook that pays for itself the first time it catches something.&lt;/p&gt;




&lt;p&gt;If you're using Claude Code for real projects, you already know the trust issue. You can't review every edit. Hooks are how you stop trusting blindly and start trusting with guardrails.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.anthropic.com/en/docs/claude-code/hooks" rel="noopener noreferrer"&gt;Claude Code Documentation - Hooks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Anthropic Engineering - Building Effective Agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.anthropic.com/en/docs/claude-code/settings" rel="noopener noreferrer"&gt;Claude Code Documentation - Settings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>claudecode</category>
      <category>aitools</category>
      <category>developerproductivity</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
