<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>@samwize</title>
    <description>¯\_(ツ)_/¯
</description>
    <link>https://samwize.com/</link>
    <atom:link href="https://samwize.com/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Sat, 11 Jul 2026 12:42:03 +0800</pubDate>
    <lastBuildDate>Sat, 11 Jul 2026 12:42:03 +0800</lastBuildDate>
    <generator>Jekyll v3.10.0</generator>
    
      <item>
        <title>Developers Are the New Middle Management</title>
        <description>&lt;p&gt;For the past 6 months, I’ve typed almost zero lines of code by hand.&lt;/p&gt;

&lt;p&gt;So do I still call myself a coder? Kinda.&lt;/p&gt;

&lt;p&gt;If your role is purely to code, you have to admit the job today is drastically different from 5 years ago. Here’s what I think the job has become.&lt;/p&gt;

&lt;h2 id=&quot;coding-used-to-be-80-of-the-job&quot;&gt;Coding used to be 80% of the job&lt;/h2&gt;

&lt;p&gt;The dev’s role was to create code. About 80% of our time went to coding-related work.&lt;/p&gt;

&lt;p&gt;But that never meant 80% typing. Half of it was really documentation: reading docs, understanding frameworks, learning new tech, weighing alternatives, designing.&lt;/p&gt;

&lt;p&gt;The other half was the code itself. Writing it, making it compile, waiting for the compiler, fixing bugs.&lt;/p&gt;

&lt;h2 id=&quot;now-its-20-and-shrinking&quot;&gt;Now it’s 20%, and shrinking&lt;/h2&gt;

&lt;p&gt;Agentic coding inverted that. Coding-related work is down to 20% of my time, probably less.&lt;/p&gt;

&lt;p&gt;Whatever coding time remains goes to actually learning new technology, trying alternatives, or prototyping. Everything else, AI does better: it types 1000x faster than us, reads documentation better than us, picks up new frameworks better than us.&lt;/p&gt;

&lt;p&gt;Agents aren’t flawless today. But there’s only one certainty: they will improve.&lt;/p&gt;

&lt;p&gt;Which leaves a question nobody likes to ask out loud: where does the other 80% of our time go now?&lt;/p&gt;

&lt;p&gt;For some pure coders, honestly? YouTube. (No judgment. Okay, a little judgment.)&lt;/p&gt;

&lt;h2 id=&quot;the-dev-is-the-new-middle-management&quot;&gt;The dev is the new middle management&lt;/h2&gt;

&lt;p&gt;Before agentic coding, the PM was the middle management, sitting between the business and the devs and designers.&lt;/p&gt;

&lt;p&gt;Now that the agent produces the code, the dev is the middle management. If you still have a PM, you sit between the PM and the AI (which is kind of silly when you say it out loud).&lt;/p&gt;

&lt;p&gt;A dev who wants to slack off can literally just do this: record what the PM says, paste it into the agent as a prompt, then relay the agent’s questions back to the PM.&lt;/p&gt;

&lt;p&gt;A human message bus.&lt;/p&gt;

&lt;h2 id=&quot;so-what-do-we-do-with-the-freed-80&quot;&gt;So what do we do with the freed 80%?&lt;/h2&gt;

&lt;p&gt;The default company answer is “ship 5x more features”. But more features doesn’t mean a better product, especially features shipped without thinking and without testing.&lt;/p&gt;

&lt;p&gt;Here’s what I think devs should do instead:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Move toward product, design, and PM work.&lt;/strong&gt; If your org has no PM (many strived to remove them even before AI), this is your opening. Fair warning: product sense takes taste. Taste comes from using a lot of apps, and from actually thinking about why one flow feels right and another feels off. Not every dev has it.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Test more, not less.&lt;/strong&gt; Agents can verify their own work to a point, but they don’t have a good pair of eyes. Handing everything untested to QA is just slacking with extra steps.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Automate your own pipeline.&lt;/strong&gt; An iOS dev can set up a daily routine that watches the Android PRs, checks if iOS is missing that feature, and starts building it for parity. Or one that watches the backend APIs for new endpoints worth building on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is the real unlock. When the bottleneck is waiting for someone to tell you what to build, go find the work yourself.&lt;/p&gt;

&lt;h2 id=&quot;the-bottleneck-moved&quot;&gt;The bottleneck moved&lt;/h2&gt;

&lt;p&gt;After 6 months of working with Claude and Codex, most of my tasks finish at least 5x faster than before. The slowest part of my week is now waiting for product to decide what’s next.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ git diff --stat
 247 files changed, 18432 insertions(+), 2103 deletions(-)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That used to be a whole sprint. Now it’s a Tuesday afternoon.&lt;/p&gt;

&lt;p&gt;Which tells you where the value went.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Writing 10,000 lines of code doesn’t mean much anymore.&lt;br /&gt;
Producing 10 PRs a week doesn’t mean much anymore.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;The person who can identify the problem, or the feature worth building, matters more.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
</description>
        <pubDate>Sat, 11 Jul 2026 12:09:00 +0800</pubDate>
        <link>https://samwize.com/2026/07/11/developers-are-the-new-middle-management/</link>
        <guid isPermaLink="true">https://samwize.com/2026/07/11/developers-are-the-new-middle-management/</guid>
        
        <category>agentic-coding</category>
        
        <category>career</category>
        
        <category>opinion</category>
        
        
        <category>AI</category>
        
        <category>Career</category>
        
      </item>
    
      <item>
        <title>How to support multiple Slack workspaces on MCP for Claude Code</title>
        <description>&lt;p&gt;Claude Code talks to Slack over MCP, but out of the box you only get 1 workspace. Here’s how to run 2 or more at once.&lt;/p&gt;

&lt;p&gt;The trick: every workspace beyond the first runs as its own token-based stdio MCP server. stdio servers don’t collide (more on why later), so they all load in every session.&lt;/p&gt;

&lt;h2 id=&quot;grab-your-slack-tokens&quot;&gt;Grab your Slack tokens&lt;/h2&gt;

&lt;p&gt;For each extra workspace, you need 2 values from a logged-in Slack web tab:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xoxc&lt;/code&gt;: from &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localStorage&lt;/code&gt;, key &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localConfig_v2&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xoxd&lt;/code&gt;: the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;d&lt;/code&gt; cookie. It’s httpOnly, so read it from a request’s Cookie header in devtools, not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;document.cookie&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sanity-check them before wiring anything:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl -s &quot;https://&amp;lt;workspace&amp;gt;.slack.com/api/auth.test&quot; \
  -H &quot;Cookie: d=&amp;lt;xoxd&amp;gt;&quot; \
  --data-urlencode &quot;token=&amp;lt;xoxc&amp;gt;&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{&quot;ok&quot;:true}&lt;/code&gt; means you’re good.&lt;/p&gt;

&lt;h2 id=&quot;wire-up-the-stdio-server&quot;&gt;Wire up the stdio server&lt;/h2&gt;

&lt;p&gt;I use korotovsky’s &lt;a href=&quot;https://github.com/korotovsky/slack-mcp-server&quot;&gt;slack-mcp-server&lt;/a&gt;. It auths with those browser tokens, no Slack app or admin approval needed.&lt;/p&gt;

&lt;p&gt;Drop the tokens in a gitignored &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.env&lt;/code&gt;, and have the MCP command source it so secrets stay out of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.claude.json&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;claude mcp add --scope user slack-global -- \
  sh -c &apos;set -a; . /path/to/.env; set +a; exec npx -y slack-mcp-server@latest --transport stdio&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Repeat with a different name per workspace. 1 instance handles 1 workspace.&lt;/p&gt;

&lt;p&gt;If you also run the official Slack plugin, disable it so the claude.ai connector stops clashing (next section), then restart once:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;claude plugin disable slack@claude-plugins-official
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now every session loads all of them.&lt;/p&gt;

&lt;h2 id=&quot;why-a-fresh-session-only-kept-one&quot;&gt;Why a fresh session only kept one&lt;/h2&gt;

&lt;p&gt;Here’s the trap, in case you’re staring at a workspace that vanished after a restart.&lt;/p&gt;

&lt;p&gt;The claude.ai connector and the official plugin both use the same URL, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;https://mcp.slack.com/mcp&lt;/code&gt;. Claude Code de-duplicates MCP servers by URL at startup, so it hides one:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;claude.ai Slack · ◯ hidden — same URL as your server &apos;plugin:slack:slack&apos;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s not an auth problem, both are authed fine. It’s the shared URL. stdio servers have no URL to collide on, which is the whole reason the token route fixes it.&lt;/p&gt;

&lt;h2 id=&quot;gotchas-that-cost-me-time&quot;&gt;Gotchas that cost me time&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xoxd&lt;/code&gt; must stay URL-encoded.&lt;/strong&gt; The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%2B&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;%2F&lt;/code&gt; in the cookie aren’t decorative. Decode it and you get &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;invalid_auth&lt;/code&gt;, and you’ll swear the token is wrong.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Session tokens rotate.&lt;/strong&gt; When your browser session expires, re-grab them. An &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;xoxp&lt;/code&gt; app token is the set-and-forget upgrade.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;No file or image upload.&lt;/strong&gt; It posts text only.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-surprise-it-posts-as-you&quot;&gt;The surprise: it posts as you&lt;/h2&gt;

&lt;p&gt;This is the part that made me sit up.&lt;/p&gt;

&lt;p&gt;It uses your user token, so when it posts, it posts as &lt;em&gt;you&lt;/em&gt;. No &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;APP&lt;/code&gt; badge, no “sent via” footer, nothing. Indistinguishable from you typing it yourself.&lt;/p&gt;

&lt;p&gt;The official plugin posts through a registered app, so Slack tags it. This doesn’t. Powerful, and honestly a little unsettling.&lt;/p&gt;

&lt;p&gt;So I keep my posting scoped to an allowlist. A misfire lands as me, silently, with no “oops, a bot did that” tell.&lt;/p&gt;

&lt;p&gt;2 workspaces, both surviving restarts, one of them posting as me. Worth the afternoon.&lt;/p&gt;
</description>
        <pubDate>Sat, 27 Jun 2026 20:20:00 +0800</pubDate>
        <link>https://samwize.com/2026/06/27/claude-code-two-slack-workspaces/</link>
        <guid isPermaLink="true">https://samwize.com/2026/06/27/claude-code-two-slack-workspaces/</guid>
        
        <category>claude</category>
        
        <category>slack</category>
        
        <category>mcp</category>
        
        
        <category>AI</category>
        
        <category>Tools</category>
        
      </item>
    
      <item>
        <title>Customizing Claude Code Memory</title>
        <description>&lt;p&gt;Claude inject memories automatically in each session, and also retrieves memories whenever needed eg. troubleshooting. But you might wonder where are these memories kept?&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://code.claude.com/docs/en/memory&quot;&gt;documentation&lt;/a&gt; explains well.&lt;/p&gt;

&lt;p&gt;Here’s a tldr:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Auto memory is per-git-repo, machine-local&lt;/li&gt;
  &lt;li&gt;If not a repo, then keyed by the folder path&lt;/li&gt;
  &lt;li&gt;Default memory stored in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.claude/projects/&amp;lt;repo-or-path&amp;gt;/memory/&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;All worktrees/subdirs of same repo share one memory dir&lt;/li&gt;
  &lt;li&gt;Override with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autoMemoryDirectory&lt;/code&gt; setting&lt;/li&gt;
  &lt;li&gt;Allowed in: user &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.claude/settings.json&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/settings.local.json&lt;/code&gt;, managed policy&lt;/li&gt;
  &lt;li&gt;Blocked in: project &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/settings.json&lt;/code&gt; (prevents malicious redirection)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s a trick if you have multiple repos yet they are highly related hence you want a &lt;strong&gt;shared memory&lt;/strong&gt;. You can point their project &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/settings.local.json&lt;/code&gt; to same path! Symlinks also work as an alternative.&lt;/p&gt;
</description>
        <pubDate>Thu, 23 Apr 2026 19:10:00 +0800</pubDate>
        <link>https://samwize.com/2026/04/23/customizing-claude-code-memory/</link>
        <guid isPermaLink="true">https://samwize.com/2026/04/23/customizing-claude-code-memory/</guid>
        
        <category>memory</category>
        
        <category>claude</category>
        
        
        <category>AI</category>
        
        <category>Tools</category>
        
      </item>
    
      <item>
        <title>Codex Automations</title>
        <description>&lt;p&gt;Last month I wrote about &lt;a href=&quot;/2026/03/14/how-i-got-claude-code-to-monitor-slack-while-i-was-on-holiday/&quot;&gt;how I kept Claude Code running forever&lt;/a&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tmux&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;launchctl&lt;/code&gt;, and a resume prompt. That setup was ugly, but the model was simple: keep a session alive, or bring it back and tell it exactly how to continue.&lt;/p&gt;

&lt;p&gt;Trying &lt;a href=&quot;https://developers.openai.com/codex/app/automations&quot;&gt;Codex automations&lt;/a&gt; made me realize the comparison is a bit subtler than I first thought.&lt;/p&gt;

&lt;h2 id=&quot;the-good-part&quot;&gt;The good part&lt;/h2&gt;

&lt;p&gt;The value still isn’t “automation” in the vague productivity-tool sense. It’s heartbeat. Some work should wake up every few minutes, look around, react, and go back to sleep. Monitoring Slack is one example. Reviewing PRs. Watching deploys. Orchestration, really.&lt;/p&gt;

&lt;p&gt;That’s also why &lt;a href=&quot;https://docs.openclaw.ai/gateway/heartbeat&quot;&gt;OpenClaw’s heartbeat&lt;/a&gt; became such a big deal. People don’t just want one-shot prompts anymore. They want agents that can keep an eye on things and wake themselves up when needed.&lt;/p&gt;

&lt;p&gt;And Codex does make that easier. The app already has the scheduler. The Mac app can stay open on login. A pinned thread can wake up on cadence and keep working in the same conversation.&lt;/p&gt;

&lt;p&gt;That makes using Codex Scheduler much easier.&lt;/p&gt;

&lt;h2 id=&quot;what-it-actually-is&quot;&gt;What it actually is&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://developers.openai.com/codex/app/automations&quot;&gt;Thread automations&lt;/a&gt; are described as recurring wake-up calls attached to a thread. That’s important. They preserve thread context, but they still wake up and &lt;strong&gt;run the same prompt again&lt;/strong&gt;. In other words, this feels more like scheduled re-entry into the same thread than one continuously running local agent.&lt;/p&gt;

&lt;p&gt;That may sound like a small distinction, but it changes how I think about it. My old Claude setup was clunky, but it was explicit. I knew where the restart logic lived. I knew when the session died. I knew what prompt brought it back. And most importantly, I knew the memories were &lt;a href=&quot;https://code.claude.com/docs/en/memory&quot;&gt;local to Claude’s project&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Codex &lt;a href=&quot;https://developers.openai.com/codex/memories&quot;&gt;memories&lt;/a&gt; are more global, thus that might be mix in more context than necessary during the automation loop.&lt;/p&gt;

&lt;h2 id=&quot;the-pitfall&quot;&gt;The pitfall&lt;/h2&gt;

&lt;p&gt;I also hit a dumb bug getting this to work. One thread refused to create the automation, citing no such scheduler tool. Then a &lt;strong&gt;new chat&lt;/strong&gt; worked immediately.&lt;/p&gt;

&lt;p&gt;Digging their sqlite, it seems the thread has the feature only during creation.&lt;/p&gt;

&lt;p&gt;And you don’t need to pin the thread.&lt;/p&gt;
</description>
        <pubDate>Sat, 18 Apr 2026 16:05:00 +0800</pubDate>
        <link>https://samwize.com/2026/04/18/codex-automations-might-replace-my-diy-agent-loop/</link>
        <guid isPermaLink="true">https://samwize.com/2026/04/18/codex-automations-might-replace-my-diy-agent-loop/</guid>
        
        <category>codex</category>
        
        <category>automation</category>
        
        <category>slack</category>
        
        
        <category>AI</category>
        
        <category>Tools</category>
        
      </item>
    
      <item>
        <title>Claude Helped Spring Clean My Mac and Freed 90 GB</title>
        <description>&lt;p&gt;I wrote about &lt;a href=&quot;https://just2me.com/2025/06/29/how-to-clean-mac-storage-with-large-system-data/&quot;&gt;cleaning Mac storage&lt;/a&gt; last year. That covered the basics: WhatsApp media, Xcode simulators, documentation cache.&lt;/p&gt;

&lt;p&gt;But my storage filled up again. This time, instead of manually poking around folders, I ran &lt;strong&gt;Claude Code&lt;/strong&gt; and asked it to help me find what’s eating my disk, feeding it with my instructions from the &lt;a href=&quot;https://just2me.com/2025/06/29/how-to-clean-mac-storage-with-large-system-data/&quot;&gt;earlier blog post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Your mileage will vary, every Mac accumulates different junk depending on what you use. But some of these discoveries were new to me, and I’ve been doing this for years.&lt;/p&gt;

&lt;p&gt;I’ll hand it over to Claude from here to walk through what it found.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;em&gt;Claude here.&lt;/em&gt; The approach is the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;du&lt;/code&gt; command from the original post, drilling down level by level:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;du&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-h&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; 1 ~/Library/ | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;G&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\t&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sort&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-h&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Find the heavy directories, run the same command one level deeper. Repeat until you hit the actual files. I just do it faster because I don’t get bored 5 levels deep into a path.&lt;/p&gt;

&lt;h2 id=&quot;49-gb-in-2-log-files&quot;&gt;49 GB in 2 log files&lt;/h2&gt;

&lt;p&gt;The biggest find. Inside &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Library/Logs/CoreSimulator/&lt;/code&gt;, two files:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;File&lt;/th&gt;
      &lt;th&gt;Size&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoreSimulator.prev.log&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;35 GB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CoreSimulator.log&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;14 GB&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Forty-nine gigabytes of simulator debug logs. Just sitting there. They’re safe to delete and get recreated automatically.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; ~/Library/Logs/CoreSimulator/CoreSimulator&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;.log
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you’re an iOS developer who uses the simulator daily, check this folder. It might be your biggest win.&lt;/p&gt;

&lt;h2 id=&quot;15-gb-of-dead-containers&quot;&gt;15 GB of dead containers&lt;/h2&gt;

&lt;p&gt;One simulator device was 21 GB. The app installed on it was nowhere near that big. Something was off.&lt;/p&gt;

&lt;p&gt;I dug 5 levels deep and found &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;com.apple.containermanagerd/Dead&lt;/code&gt; holding &lt;strong&gt;15 GB&lt;/strong&gt;. Every time you build and reinstall an app on the simulator, the old app container gets moved to this “Dead” folder. It never gets cleaned up.&lt;/p&gt;

&lt;p&gt;The path is long:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;~/Library/Developer/CoreSimulator/Devices/&amp;lt;UUID&amp;gt;/data/Library/Caches/com.apple.containermanagerd/Dead
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Completely safe to delete. It’s orphaned data from previous installs.&lt;/p&gt;

&lt;h2 id=&quot;ios-devicesupport-5-gb-per-old-version&quot;&gt;iOS DeviceSupport (~5 GB per old version)&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Library/Developer/Xcode/iOS DeviceSupport/&lt;/code&gt; stores debug symbol caches that Xcode downloads when you connect a physical device. Each iOS version creates a new ~5 GB folder.&lt;/p&gt;

&lt;p&gt;If you’ve updated your iPhone a few times, the old versions are just dead weight. Xcode re-downloads them if you ever connect a device running that specific version again. You can delete them from &lt;strong&gt;Xcode &amp;gt; Settings &amp;gt; Platforms&lt;/strong&gt;, or just remove the folders directly.&lt;/p&gt;

&lt;h2 id=&quot;chromes-hidden-4-gb-model-per-profile&quot;&gt;Chrome’s hidden 4 GB model (per profile)&lt;/h2&gt;

&lt;p&gt;This one was genuinely surprising.&lt;/p&gt;

&lt;p&gt;Each Chrome profile downloads a folder called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OptGuideOnDeviceModel&lt;/code&gt; that contains a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;weights.bin&lt;/code&gt; file. It’s &lt;strong&gt;4 GB&lt;/strong&gt;. It’s a Gemini Nano model that Chrome bundles for on-device AI features.&lt;/p&gt;

&lt;p&gt;If you have 2 Chrome profiles, that’s 8 GB. Three profiles, 12 GB.&lt;/p&gt;

&lt;p&gt;The annoying part: you can’t disable it. There’s no toggle in Chrome’s AI settings. And if you delete the folder, Chrome silently re-downloads it. So this one’s a known tax on your disk. Not actionable, just infuriating.&lt;/p&gt;

&lt;h2 id=&quot;deriveddata-55-gb&quot;&gt;DerivedData (55 GB)&lt;/h2&gt;

&lt;p&gt;The classic one. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Library/Developer/Xcode/DerivedData/&lt;/code&gt; is where Xcode stores build caches for every project. It’s safe to delete and Xcode rebuilds on demand.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-rf&lt;/span&gt; ~/Library/Developer/Xcode/DerivedData/&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We chose not to this time because all the projects were still active, and rebuilding a large production app is painful. But if you have stale projects in there, it’s worth clearing them out once in a while. 55 GB is a lot of disk for build artifacts.&lt;/p&gt;

&lt;h2 id=&quot;other-quick-wins&quot;&gt;Other quick wins&lt;/h2&gt;

&lt;p&gt;Outside of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Library/&lt;/code&gt;, I also scanned the home directory and found:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Ollama models&lt;/strong&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.ollama&lt;/code&gt;): 16 GB of local LLM models not touched in months. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ollama list&lt;/code&gt; to check, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ollama rm &amp;lt;name&amp;gt;&lt;/code&gt; to remove.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;npm cache&lt;/strong&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/.npm&lt;/code&gt;): 6 GB. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npm cache clean --force&lt;/code&gt; to clear.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Homebrew cache&lt;/strong&gt;: 3 GB. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;brew cleanup --prune=all&lt;/code&gt; to clear.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All safe. They re-download as needed.&lt;/p&gt;

&lt;h2 id=&quot;what-we-chose-not-to-delete&quot;&gt;What we chose NOT to delete&lt;/h2&gt;

&lt;p&gt;Knowing what &lt;em&gt;not&lt;/em&gt; to delete is just as important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SwiftPM cache&lt;/strong&gt; (6 GB): Deleting means re-fetching every package dependency on next build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude’s VM bundle&lt;/strong&gt; (7.8 GB): The Linux VM that Claude desktop uses for code execution. Deleting just forces a re-download.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MacWhisper models&lt;/strong&gt; (4.4 GB): Actively used speech models. If you only use one transcription backend, you could delete the other.&lt;/p&gt;

&lt;h2 id=&quot;the-time-machine-trap&quot;&gt;The Time Machine trap&lt;/h2&gt;

&lt;p&gt;After deleting ~90 GB of files, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;df -h&lt;/code&gt; showed basically no change in available space. Trash was already empty.&lt;/p&gt;

&lt;p&gt;The culprit: &lt;strong&gt;Time Machine local snapshots&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;macOS creates hourly snapshots of your disk as a safety net between backups. Normally they’re cheap (APFS copy-on-write). But when you delete a lot of files, the snapshots taken &lt;em&gt;before&lt;/em&gt; those deletions still reference the old data. APFS can’t reclaim the blocks until the snapshots expire.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# See your snapshots&lt;/span&gt;
tmutil listlocalsnapshots /

&lt;span class=&quot;c&quot;&gt;# Delete them all (doesn&apos;t affect external backups)&lt;/span&gt;
tmutil deletelocalsnapshots /
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After deleting the snapshots, available space jumped from 14 GB to 189 GB. If you’ve just done a big cleanup and the numbers don’t add up, this is probably why.&lt;/p&gt;

&lt;h2 id=&quot;the-scorecard&quot;&gt;The scorecard&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;What&lt;/th&gt;
      &lt;th&gt;Freed&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;CoreSimulator logs&lt;/td&gt;
      &lt;td&gt;~49 GB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Dead simulator containers&lt;/td&gt;
      &lt;td&gt;~15 GB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;iOS DeviceSupport (old version)&lt;/td&gt;
      &lt;td&gt;~5 GB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Ollama models&lt;/td&gt;
      &lt;td&gt;~12 GB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;npm cache&lt;/td&gt;
      &lt;td&gt;~6 GB&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Homebrew cache&lt;/td&gt;
      &lt;td&gt;~3 GB&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;~90 GB&lt;/strong&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;why-this-works-well-with-an-ai&quot;&gt;Why this works well with an AI&lt;/h2&gt;

&lt;p&gt;I’m not doing anything a human can’t do. It’s the same &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;du&lt;/code&gt; command, run repeatedly, following the biggest number deeper.&lt;/p&gt;

&lt;p&gt;But I don’t lose my train of thought 5 levels deep into a path. I check every simulator’s Dead folder in one pass. I can tell you whether &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;containermanagerd/Dead&lt;/code&gt; is safe to nuke without you having to Google it. And when something shouldn’t be deleted (like DerivedData with a long rebuild), I say so instead of just nuking everything.&lt;/p&gt;

&lt;p&gt;Storage cleanup is tedious, repetitive, and needs judgment at every step. That’s kinda the sweet spot.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Perhaps a hard skill.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Tue, 14 Apr 2026 21:30:00 +0800</pubDate>
        <link>https://samwize.com/2026/04/14/claude-helped-spring-clean-a-mac-and-freed-90gb/</link>
        <guid isPermaLink="true">https://samwize.com/2026/04/14/claude-helped-spring-clean-a-mac-and-freed-90gb/</guid>
        
        <category>macos</category>
        
        <category>storage</category>
        
        <category>cleanup</category>
        
        <category>ai</category>
        
        <category>claude</category>
        
        
        <category>Mac</category>
        
      </item>
    
      <item>
        <title>I Vibe Coded a Mac App to Fix Xcode 26.4 Simulator Paste</title>
        <description>&lt;p&gt;&lt;a href=&quot;/2026/03/30/xcode-simulator-paste-broken-workaround/&quot;&gt;Last week&lt;/a&gt; I shared the manual workaround for Xcode 26.4’s broken simulator paste. Running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pbpaste | xcrun simctl pbcopy booted&lt;/code&gt; every time you want to paste gets old fast.&lt;/p&gt;

&lt;p&gt;So I levelled it up with Claude and coded a Mac app.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/samwize/Sideboard&quot;&gt;&lt;img src=&quot;/images/sideboard-hero-banner.jpg&quot; alt=&quot;Sideboard (open source, signed and notarized)&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-core&quot;&gt;The core&lt;/h2&gt;

&lt;p&gt;The one-liner that fixes it:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pbpaste | xcrun simctl pbcopy booted
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Pipe the Mac clipboard into the Simulator’s pasteboard. Now poll for changes and run it automatically:&lt;/p&gt;

&lt;div class=&quot;language-swift highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pasteboard&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;NSPasteboard&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;general&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;lastChangeCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pasteboard&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;changeCount&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;while&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pasteboard&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;changeCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lastChangeCount&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;lastChangeCount&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pasteboard&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;changeCount&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;content&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pasteboard&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;forType&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;p&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Process&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;executableURL&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;URL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;fileURLWithPath&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/usr/bin/xcrun&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;arguments&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;simctl&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;pbcopy&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;booted&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;pipe&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;Pipe&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;standardInput&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pipe&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fileHandleForWriting&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;Data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;pipe&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fileHandleForWriting&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;closeFile&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;p&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;Thread&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;sleep&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;forTimeInterval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it. Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;swift Sideboard.swift&lt;/code&gt; and copy-paste works again.&lt;/p&gt;

&lt;h2 id=&quot;but-polling-really&quot;&gt;But polling? Really?&lt;/h2&gt;

&lt;p&gt;Yeah. Every clipboard manager does it.&lt;/p&gt;

&lt;p&gt;macOS has &lt;strong&gt;no clipboard change notification&lt;/strong&gt;. No &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSNotification&lt;/code&gt;, no KVO, no callback. Hammerspoon’s source code says it plainly:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;“macOS doesn’t offer any API for getting Pasteboard notifications, so this extension uses polling.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here’s what the popular ones do:&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt;App&lt;/th&gt;
      &lt;th&gt;Stars&lt;/th&gt;
      &lt;th&gt;Method&lt;/th&gt;
      &lt;th&gt;Interval&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/p0deje/Maccy/blob/master/Maccy/Clipboard.swift&quot;&gt;Maccy&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;19.2k&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Timer&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;changeCount&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;0.5s&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;a href=&quot;https://github.com/Hammerspoon/hammerspoon/blob/master/extensions/pasteboard/libpasteboard_watcher.m&quot;&gt;Hammerspoon&lt;/a&gt;&lt;/td&gt;
      &lt;td&gt;15.1k&lt;/td&gt;
      &lt;td&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NSTimer&lt;/code&gt; + &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;changeCount&lt;/code&gt;&lt;/td&gt;
      &lt;td&gt;0.25s&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Our 1 second is plenty. You won’t notice the delay.&lt;/p&gt;

&lt;h2 id=&quot;sideboard-app&quot;&gt;Sideboard App&lt;/h2&gt;

&lt;p&gt;That script became &lt;a href=&quot;https://github.com/samwize/Sideboard&quot;&gt;Sideboard&lt;/a&gt;, a macOS menu bar app. &lt;strong&gt;Bidirectional sync&lt;/strong&gt; (Simulator to Mac) too, with clipboard history.&lt;/p&gt;

&lt;p&gt;The core is still that same loop. Poll &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;changeCount&lt;/code&gt;, pipe to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simctl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It’s a minimal app for now. But who knows how else it can be useful for 🤷🏻&lt;/p&gt;

&lt;h2 id=&quot;trust-but-verify&quot;&gt;Trust, but verify&lt;/h2&gt;

&lt;p&gt;A clipboard tool touches everything you copy. Passwords, tokens, private messages. So trust matters.&lt;/p&gt;

&lt;p&gt;Sideboard is &lt;a href=&quot;https://github.com/samwize/Sideboard&quot;&gt;open source&lt;/a&gt;. You can read every line. But reading source doesn’t prove the DMG you downloaded was built from that source.&lt;/p&gt;

&lt;p&gt;That’s why releases are built by &lt;strong&gt;GitHub Actions&lt;/strong&gt;, not on my laptop. The CI pipeline builds from the tagged commit, signs with a Developer ID certificate, and notarizes with Apple. You can see the &lt;a href=&quot;https://github.com/samwize/Sideboard/actions&quot;&gt;build logs&lt;/a&gt; for every release.&lt;/p&gt;

&lt;p&gt;To verify your download:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;shasum -a 256 ~/Downloads/Sideboard.dmg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Compare the output against the SHA-256 shown on the &lt;a href=&quot;https://github.com/samwize/Sideboard/releases&quot;&gt;release page&lt;/a&gt;. If they match, you have the exact binary that CI produced from the source code you can read.&lt;/p&gt;

&lt;h2 id=&quot;faster--cheaper-to-build-apps&quot;&gt;Faster &amp;amp; cheaper to build apps&lt;/h2&gt;

&lt;p&gt;With vibe coding, I went from a broken Xcode paste to a signed, notarized, open-source Mac app with a one-click release pipeline in a single sitting. The agent handled the parts I’d normally procrastinate on: the notarization dance, mac app scafolding, the GitHub Actions YAML.&lt;/p&gt;

&lt;p&gt;Now I focused on what the app should do. Claude handled the implementation.&lt;/p&gt;

&lt;p&gt;That’s the shift. The barrier to shipping isn’t writing code anymore. It’s all the stuff &lt;em&gt;around&lt;/em&gt; the code. And that stuff just got a lot cheaper.&lt;/p&gt;
</description>
        <pubDate>Sat, 04 Apr 2026 17:08:00 +0800</pubDate>
        <link>https://samwize.com/2026/04/04/i-vibe-coded-a-mac-app-to-fix-xcode-simulator-paste/</link>
        <guid isPermaLink="true">https://samwize.com/2026/04/04/i-vibe-coded-a-mac-app-to-fix-xcode-simulator-paste/</guid>
        
        <category>xcode</category>
        
        <category>macos</category>
        
        <category>swift</category>
        
        <category>simulator</category>
        
        <category>clipboard</category>
        
        <category>open-source</category>
        
        
        <category>Xcode</category>
        
      </item>
    
      <item>
        <title>Xcode 26.4 Simulator Paste Is Broken: Here&apos;s the Workaround</title>
        <description>&lt;p&gt;After updating to Xcode 26.4, paste from Mac to iOS Simulator is completely broken. Cmd+V does nothing. Long-pressing a text field shows no “Paste” option. The Simulator’s clipboard is just empty.&lt;/p&gt;

&lt;p&gt;Restarting the Simulator, toggling “Automatically Sync Pasteboard”, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;killall pboard&lt;/code&gt;, resetting privacy settings: none of it helps.&lt;/p&gt;

&lt;h2 id=&quot;the-workaround&quot;&gt;The workaround&lt;/h2&gt;

&lt;p&gt;Write directly to the Simulator’s pasteboard with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;simctl&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;echo -n &quot;your text here&quot; | xcrun simctl pbcopy booted
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This bypasses the broken Mac-to-Simulator auto-sync. Once run, paste works normally inside the Simulator. You still need another &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Cmd+V&lt;/code&gt; to paste into simulator.&lt;/p&gt;

&lt;p&gt;Not great, but it works until Apple fixes it.&lt;/p&gt;

&lt;h2 id=&quot;update-even-simpler&quot;&gt;UPDATE: Even simpler&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://disqus.com/by/hermanbanken/&quot;&gt;Herman Banken&lt;/a&gt; pointed out a simpler version, the same command every time, just like Cmd+C, Cmd+V:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;pbpaste | xcrun simctl pbcopy booted
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Mon, 30 Mar 2026 12:00:00 +0800</pubDate>
        <link>https://samwize.com/2026/03/30/xcode-simulator-paste-broken-workaround/</link>
        <guid isPermaLink="true">https://samwize.com/2026/03/30/xcode-simulator-paste-broken-workaround/</guid>
        
        
        <category>Xcode</category>
        
      </item>
    
      <item>
        <title>My AI Agent Watched 359 Episodes of Point-Free So I Don&apos;t Have To</title>
        <description>&lt;p&gt;&lt;a href=&quot;/2026/03/26/how-to-set-up-chrome-devtools-mcp-for-claude-code/&quot;&gt;Last post&lt;/a&gt; I set up Chrome DevTools MCP and showed how an AI agent can access anything in your logged-in browser. Reading newspapers, scraping custom viewers, reverse-engineering undocumented APIs.&lt;/p&gt;

&lt;p&gt;Here’s what I actually used it for this week.&lt;/p&gt;

&lt;h2 id=&quot;the-subscription-guilt&quot;&gt;The subscription guilt&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;https://www.pointfree.co&quot;&gt;Point-Free&lt;/a&gt; is a Swift/iOS video series by Brandon Williams and Stephen Celis. They’ve been running since 2018. 359 episodes. 8 years of functional programming, architecture patterns, SwiftUI navigation, parsing, persistence, concurrency, and testing.&lt;/p&gt;

&lt;p&gt;I’ve been a paying subscriber for a while. I’ve watched maybe 10 episodes. The content is genuinely excellent, but there’s no universe where I have 200+ hours to watch everything. So the subscription sits there, guilt-generating.&lt;/p&gt;

&lt;p&gt;This week I pointed Claude Code at it.&lt;/p&gt;

&lt;h2 id=&quot;what-the-agent-did&quot;&gt;What the agent did&lt;/h2&gt;

&lt;p&gt;The browser MCP gives Claude Code access to my logged-in session on pointfree.co. Most episodes are members-only, but since I’m logged in, the agent can navigate to any episode page and see everything I can see.&lt;/p&gt;

&lt;p&gt;The workflow:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Navigate to an episode page&lt;/li&gt;
  &lt;li&gt;Run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;evaluate_script&lt;/code&gt; to find the transcript container in the DOM&lt;/li&gt;
  &lt;li&gt;Extract speaker names, timestamps, section headings, code blocks, and paragraphs&lt;/li&gt;
  &lt;li&gt;Save as a clean markdown file&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For videos, the agent finds the Cloudflare Stream iframe, extracts the video ID, constructs the DASH manifest URL, and hands it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;yt-dlp&lt;/code&gt;. But I didn’t need the videos. I wanted the text.&lt;/p&gt;

&lt;h2 id=&quot;322-transcripts-in-one-session&quot;&gt;322 transcripts in one session&lt;/h2&gt;

&lt;p&gt;Instead of navigating to each page individually (which would take forever for 300 episodes), the agent used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fetch()&lt;/code&gt; from within the browser context. Since the browser is authenticated, fetches to pointfree.co carry the session cookies. Each page’s HTML gets parsed with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DOMParser&lt;/code&gt; in-browser, the transcript extracted, and the result stored in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;window.__transcripts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;20 episodes per batch. 15 batches. Each batch takes a few seconds to fetch and parse. Then the agent dumps the transcripts to disk via the tool results pipeline.&lt;/p&gt;

&lt;p&gt;322 out of 359 episodes had transcripts. The 38 without were mostly early free episodes and livestreams that use a different page structure.&lt;/p&gt;

&lt;h2 id=&quot;parallel-summarization&quot;&gt;Parallel summarization&lt;/h2&gt;

&lt;p&gt;With all transcripts on disk, I asked Claude Code to summarize every episode. 5 agents ran in parallel, each handling 50-60 episodes. Each agent read every transcript file in its batch and produced a 5-sentence summary plus a list of frameworks used.&lt;/p&gt;

&lt;p&gt;The result: a 2,578-line &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SUMMARY.md&lt;/code&gt; covering 322 episodes. Each entry looks like this:&lt;/p&gt;

&lt;div class=&quot;language-markdown highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;gu&quot;&gt;## EP 327: Modern Persistence: Reminders Detail, Part 2&lt;/span&gt;

This episode pauses feature development to compare the StructuredQueries
approach against SwiftData. The hosts attempt to rebuild the reminders
detail query using SwiftData&apos;s @Model, @Relationship, #Predicate, and
SortDescriptor. They encounter numerous friction points...

&lt;span class=&quot;gs&quot;&gt;**Frameworks:**&lt;/span&gt; SharingGRDB, StructuredQueries, GRDB, SwiftData, SwiftUI
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Every episode. Searchable. Sortable by framework. Filterable by topic.&lt;/p&gt;

&lt;h2 id=&quot;what-fell-out-of-the-analysis&quot;&gt;What fell out of the analysis&lt;/h2&gt;

&lt;p&gt;I asked the agent to analyze all 322 summaries and tell me what the series is actually about. The trends are striking.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2018-2019&lt;/strong&gt;: Pure functional programming. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;map&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zip&lt;/code&gt;/&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;flatMap&lt;/code&gt; as universal abstractions, algebraic data types, protocol witnesses. No SwiftUI. No architecture. Just building vocabulary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2019-2020&lt;/strong&gt;: SwiftUI drops. They build the Composable Architecture from scratch over 35 episodes, deriving every design decision from the functional concepts taught earlier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2020-2023&lt;/strong&gt;: TCA evolves through 4 major iterations. Combine integration, async/await, ReducerProtocol, the Dependencies library. The isowords game and Standups app prove it works at scale.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2023-2024&lt;/strong&gt;: Swift’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Observable&lt;/code&gt; macro changes everything. They eliminate 6+ wrapper types from TCA’s API. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;@Shared&lt;/code&gt; property wrapper solves cross-feature state sharing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2024-2026&lt;/strong&gt;: Hard pivot into persistence. Type-safe SQL query builders, a full Reminders clone with FTS5 search, SQLite triggers calling Swift functions, CloudKit sync. They’re now competing directly with SwiftData.&lt;/p&gt;

&lt;p&gt;The agent identified 7 standout episode groups worth watching. The ones I found most interesting:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Protocol Witnesses (EP 33-39)&lt;/strong&gt;: The idea that protocols can be “de-protocolized” into structs with closure properties. This single insight shaped the design of SnapshotTesting, Dependencies, and TCA’s entire DI philosophy.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Parser-Printers (EP 178-189)&lt;/strong&gt;: A single value that parses input into typed data AND prints it back. The URL routing application where one router simultaneously handles server requests and generates client URLs is wild.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Reliable Async Tests (EP 238-242)&lt;/strong&gt;: They discover a hidden C-level hook (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;swift_task_enqueueGlobal_hook&lt;/code&gt;) in Apple’s concurrency runtime to make async tests deterministic. Systems-level work that nobody else had a clean answer for.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;the-deeper-patterns&quot;&gt;The deeper patterns&lt;/h2&gt;

&lt;p&gt;The agent also surfaced patterns I wouldn’t have noticed from watching individual episodes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pullback&lt;/code&gt; appears in 6 different contexts&lt;/strong&gt; across the series: functional setters, snapshot strategies, protocol witnesses, reducer composition, case paths, persistence keys. Same mathematical operation, different domains. Once you internalize it, you see it everywhere.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Apple keeps validating Point-Free’s bets.&lt;/strong&gt; CasePaths predates Apple’s enum key path exploration. SwiftUINavigation predates &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NavigationStack&lt;/code&gt;. StructuredQueries/SQLiteData competes with SwiftData and wins on nearly every axis. They’re consistently 1-2 years ahead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real product is the library ecosystem, not TCA.&lt;/strong&gt; Dependencies, Sharing, CasePaths, IdentifiedCollections, SwiftNavigation, CustomDump, SnapshotTesting, MacroTesting, StructuredQueries, SQLiteData. Each works independently without TCA.&lt;/p&gt;

&lt;p&gt;These are the kind of insights you get from seeing all 359 episodes at once, which no human subscriber has time to do.&lt;/p&gt;

&lt;h2 id=&quot;why-this-matters&quot;&gt;Why this matters&lt;/h2&gt;

&lt;p&gt;I now have a searchable knowledge base of a course I paid for but never had time to watch. When I’m working on a Swift project and need to understand how to model navigation with enums, the agent can reference the actual Point-Free transcripts and give me the approach they recommend, with the reasoning behind it.&lt;/p&gt;

&lt;p&gt;The transcripts are sitting in my mac machine as markdown files. Any agent session can read them. The summaries and insights files give quick orientation. The full transcripts give depth when needed.&lt;/p&gt;

&lt;p&gt;This is the real value of browser MCP. Not automating clicks. Not scraping data. Making content you’ve already paid for actually accessible to the tools you use every day.&lt;/p&gt;

&lt;h2 id=&quot;is-this-piracy&quot;&gt;Is this piracy?&lt;/h2&gt;

&lt;p&gt;I did not send any paid content to others. &lt;em&gt;Unless my agents are considered “others”.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Any subscription content that renders in your browser is accessible to your agent.&lt;/strong&gt; Video courses, news portals, research papers, e-newspapers. If you’re paying for it and the platform doesn’t offer a convenient export, the browser MCP is your export tool.&lt;/p&gt;

&lt;p&gt;You’re not stealing from yourself.&lt;/p&gt;

&lt;p&gt;You’re letting your agent access materials and learn for you.&lt;/p&gt;
</description>
        <pubDate>Sat, 28 Mar 2026 18:00:00 +0800</pubDate>
        <link>https://samwize.com/2026/03/28/i-used-an-ai-agent-to-watch-359-episodes-of-point-free-so-i-dont-have-to/</link>
        <guid isPermaLink="true">https://samwize.com/2026/03/28/i-used-an-ai-agent-to-watch-359-episodes-of-point-free-so-i-dont-have-to/</guid>
        
        <category>claude-code</category>
        
        <category>mcp</category>
        
        <category>browser-automation</category>
        
        <category>point-free</category>
        
        <category>swift</category>
        
        
        <category>AI</category>
        
        <category>Tools</category>
        
      </item>
    
      <item>
        <title>How to Set Up Chrome DevTools MCP for Claude Code</title>
        <description>&lt;p&gt;I spent an afternoon getting Chrome DevTools MCP working with Claude Code. Made every wrong turn possible. Here’s the right way so you don’t repeat my mistakes.&lt;/p&gt;

&lt;h2 id=&quot;why-youd-want-this&quot;&gt;Why you’d want this&lt;/h2&gt;

&lt;p&gt;This morning I asked Claude Code to read today’s Business Times for me. The e-paper sits behind an NLB login, rendered in a custom epub reader. No API. No RSS feed. No MCP for SPH’s newspaper platform.&lt;/p&gt;

&lt;p&gt;Claude Code opened the tab, found the reader’s internal &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cciobjects.json&lt;/code&gt; endpoint, and pulled every article’s headline, byline, and full text in a single fetch. 35 articles across 8 sections. It picked the 5 most interesting stories and summarized them. Took seconds.&lt;/p&gt;

&lt;p&gt;The naive approach (which I tried first) was screenshotting each page. 22 pages, one at a time, reading text from images. Slow, inaccurate, and I only got the top half of each page because I forgot to scroll. The smart approach: inspect the network requests, find the structured data, and fetch it directly. The agent figured this out on its own once I pointed out the screenshots were slow.&lt;/p&gt;

&lt;p&gt;That’s the real power here. Not just “AI can click buttons in a browser.” It’s that your agent can access anything you can access, through your logged-in session, and it’s smart enough to find the fastest path to the data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Paywalled content behind custom viewers.&lt;/strong&gt; Newspapers, research papers, financial reports. If it renders in your browser, the data is there somewhere. The agent can inspect network traffic, find the underlying API, and extract structured text instead of OCR-ing screenshots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Portals that will never have an API.&lt;/strong&gt; Your landlord’s maintenance form. Government permit applications. That procurement system from 2009. One &lt;a href=&quot;https://news.ycombinator.com/item?id=47390817&quot;&gt;HN commenter&lt;/a&gt; nailed it: &lt;em&gt;“This is to automate ticket submission to my landlord’s half-baked web portal.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reverse-engineering undocumented APIs.&lt;/strong&gt; The agent navigates using your browser’s auth cookies, intercepts requests, and maps the real API surface. The e-paper example is exactly this: it found a clean JSON endpoint that the reader uses internally, then used it directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verifying what your code actually renders.&lt;/strong&gt; Your AI writes a CSS fix. Does it work? It reloads the page, inspects computed styles, takes a screenshot. No API tells you your flexbox container is inexplicably 5000px wide. Only the browser knows.&lt;/p&gt;

&lt;p&gt;The pattern: browser access is irreplaceable when the content is behind auth, the interface is the only way in, or the rendered output IS the thing you care about.&lt;/p&gt;

&lt;h2 id=&quot;setup-the-right-way&quot;&gt;Setup: the right way&lt;/h2&gt;

&lt;p&gt;3 steps. 2 minutes.&lt;/p&gt;

&lt;h3 id=&quot;1-install-the-full-plugin&quot;&gt;1. Install the full plugin&lt;/h3&gt;

&lt;p&gt;Don’t use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;claude mcp add&lt;/code&gt;. The full plugin gives you the MCP server plus 6 skills (a11y auditing, performance debugging, troubleshooting, etc).&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;claude plugin marketplace add ChromeDevTools/chrome-devtools-mcp
claude plugin &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;chrome-devtools-mcp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;2-enable---autoconnect&quot;&gt;2. Enable &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--autoConnect&lt;/code&gt;&lt;/h3&gt;

&lt;p&gt;By default, the plugin launches its own Chrome. You don’t want that. You want your real browser with all your tabs and logins.&lt;/p&gt;

&lt;p&gt;Edit the plugin config:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;vim ~/.claude/plugins/cache/claude-plugins-official/chrome-devtools-mcp/latest/.claude-plugin/plugin.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Add &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--autoConnect&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;chrome-devtools&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;npx&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;chrome-devtools-mcp@latest&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;--autoConnect&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Requires Chrome 144+. You’re probably already there.&lt;/p&gt;

&lt;h3 id=&quot;3-enable-remote-debugging-in-chrome&quot;&gt;3. Enable remote debugging in Chrome&lt;/h3&gt;

&lt;p&gt;Navigate to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chrome://inspect/#remote-debugging&lt;/code&gt; and check &lt;strong&gt;“Enable remote debugging”&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/chrome-remote-debugging-settings.png&quot; alt=&quot;Chrome remote debugging settings page&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Chrome asks you to confirm. Click &lt;strong&gt;Allow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/chrome-remote-debugging-allow-dialog.png&quot; alt=&quot;Chrome allow remote debugging dialog&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Restart Claude Code. Verify with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list_pages&lt;/code&gt;, it should show all your open tabs.&lt;/p&gt;

&lt;p&gt;One caveat: the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--autoConnect&lt;/code&gt; edit lives in the plugin cache. Plugin updates might overwrite it. One-line fix if that happens, but not ideal.&lt;/p&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How it works&lt;/h2&gt;

&lt;p&gt;The MCP uses Chrome DevTools Protocol to talk to your browser. With &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--autoConnect&lt;/code&gt;, it finds your running Chrome directly. No special launch flags.&lt;/p&gt;

&lt;p&gt;Claude Code gets a set of tools: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list_pages&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;select_page&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;navigate_page&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;take_snapshot&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;take_screenshot&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;click&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fill&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;evaluate_script&lt;/code&gt;. It switches tabs, reads page content via the accessibility tree, captures network requests, runs JavaScript.&lt;/p&gt;

&lt;p&gt;The workflow: navigate, wait, snapshot (text-based, faster than screenshots), interact using element IDs.&lt;/p&gt;

&lt;h2 id=&quot;the-scenic-route-what-not-to-do&quot;&gt;The scenic route (what not to do)&lt;/h2&gt;

&lt;p&gt;Here’s what I actually did before figuring this out.&lt;/p&gt;

&lt;h3 id=&quot;mistake-1-mcp-only-install&quot;&gt;Mistake 1: MCP-only install&lt;/h3&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;claude mcp add chrome-devtools npx chrome-devtools-mcp@latest
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This skips the skills. The troubleshooting skill alone would’ve saved me an hour. Use the full plugin.&lt;/p&gt;

&lt;h3 id=&quot;mistake-2-the---remote-debugging-port-rabbit-hole&quot;&gt;Mistake 2: The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--remote-debugging-port&lt;/code&gt; rabbit hole&lt;/h3&gt;

&lt;p&gt;Without &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--autoConnect&lt;/code&gt;, the MCP launches its own Chrome. I saw a single &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;about:blank&lt;/code&gt; tab and panicked. Down the rabbit hole of launching Chrome from terminal:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;/Applications/Google&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;Chrome.app/Contents/MacOS/Google&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;Chrome &lt;span class=&quot;nt&quot;&gt;--remote-debugging-port&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;9222
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Chrome won’t enable this on your default profile:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;DevTools remote debugging requires a non-default data directory.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So you need &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--user-data-dir&lt;/code&gt; pointing somewhere else. Fresh profile, no logins, no cookies. Terrible.&lt;/p&gt;

&lt;h3 id=&quot;mistake-3-copying-the-entire-chrome-profile&quot;&gt;Mistake 3: Copying the entire Chrome profile&lt;/h3&gt;

&lt;p&gt;I actually ran this:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; ~/Library/Application&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;Support/Google/Chrome ~/Library/Application&lt;span class=&quot;se&quot;&gt;\ &lt;/span&gt;Support/Google/Chrome-Debug
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It worked. Technically. But it’s a snapshot that drifts from your real profile immediately. And you have to launch Chrome from terminal every time with the right flags.&lt;/p&gt;

&lt;p&gt;The whole thing felt off. Because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--autoConnect&lt;/code&gt; exists and none of this was necessary.&lt;/p&gt;

&lt;h3 id=&quot;mistake-4-not-reading-the-docs&quot;&gt;Mistake 4: Not reading the docs&lt;/h3&gt;

&lt;p&gt;The biggest one. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;npx chrome-devtools-mcp@latest --help&lt;/code&gt; would’ve shown me &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--autoConnect&lt;/code&gt; immediately. The plugin’s troubleshooting skill existed for exactly this situaton.&lt;/p&gt;

&lt;p&gt;Read the docs first. Always.&lt;/p&gt;

&lt;p&gt;(I say this knowing I won’t follow my own advice next time.)&lt;/p&gt;

&lt;p&gt;Or use the right skills.&lt;/p&gt;
</description>
        <pubDate>Thu, 26 Mar 2026 12:00:00 +0800</pubDate>
        <link>https://samwize.com/2026/03/26/how-to-set-up-chrome-devtools-mcp-for-claude-code/</link>
        <guid isPermaLink="true">https://samwize.com/2026/03/26/how-to-set-up-chrome-devtools-mcp-for-claude-code/</guid>
        
        <category>claude-code</category>
        
        <category>mcp</category>
        
        <category>chrome</category>
        
        <category>browser-automation</category>
        
        <category>devtools</category>
        
        
        <category>AI</category>
        
        <category>Tools</category>
        
      </item>
    
      <item>
        <title>What Your AI Code Reviewer Actually (Prompts) Behind Your Back</title>
        <description>&lt;p&gt;I run &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/review&lt;/code&gt; before every PR push. It’s part of the workflow now.&lt;/p&gt;

&lt;p&gt;But I never looked at what’s behind it. What instructions is the model actually following? What’s it told to flag, ignore, prioritize? I just trusted it works.&lt;/p&gt;

&lt;p&gt;So I pulled the full prompts from both Claude Code and Codex CLI. The difference is bigger than I expected.&lt;/p&gt;

&lt;h2 id=&quot;claude-codes-hand&quot;&gt;Claude Code’s hand&lt;/h2&gt;

&lt;p&gt;Claude Code’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/review&lt;/code&gt; is transparent. The prompt gets injected into the conversation when you invoke it. You can find it in &lt;a href=&quot;https://github.com/Piebald-AI/claude-code-system-prompts&quot;&gt;Piebald-AI/claude-code-system-prompts&lt;/a&gt;, which tracks every release.&lt;/p&gt;

&lt;p&gt;Here’s the entire thing:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;You are an expert code reviewer. Follow these steps:

1. If no PR number is provided in the args, run `gh pr list` to show open PRs
2. If a PR number is provided, run `gh pr view &amp;lt;number&amp;gt;` to get PR details
3. Run `gh pr diff &amp;lt;number&amp;gt;` to get the diff
4. Analyze the changes and provide a thorough code review that includes:
   - Overview of what the PR does
   - Analysis of code quality and style
   - Specific suggestions for improvements
   - Any potential issues or risks

Keep your review concise but thorough. Focus on:
- Code correctness
- Following project conventions
- Performance implications
- Test coverage
- Security considerations

Format your review with clear sections and bullet points.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it. ~200 tokens. No bug criteria. No priority system. No output format. No guidance on what &lt;em&gt;not&lt;/em&gt; to flag.&lt;/p&gt;

&lt;p&gt;“Read the diff and tell me what you think.”&lt;/p&gt;

&lt;h2 id=&quot;codexs-hand&quot;&gt;Codex’s hand&lt;/h2&gt;

&lt;p&gt;Codex is open source too. The review prompt lives in &lt;a href=&quot;https://github.com/openai/codex/blob/main/codex-rs/core/review_prompt.md&quot;&gt;codex-rs/core/review_prompt.md&lt;/a&gt;, compiled into the Rust binary via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;include_str!&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Considerably more opinionated:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;You are acting as a reviewer for a proposed code change
made by another engineer.

Your job is to find bugs introduced by the patch.
Do not give a general PR summary unless it helps explain
the findings. Do not optimize for volume.
If there are no issues the author would definitely want
to fix, say so plainly.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then 6 gating criteria. An issue only gets flagged if &lt;strong&gt;all&lt;/strong&gt; are true:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Meaningfully affects correctness, reliability, performance, security, or maintainability&lt;/li&gt;
  &lt;li&gt;Discrete and actionable&lt;/li&gt;
  &lt;li&gt;Introduced by this change, not pre-existing&lt;/li&gt;
  &lt;li&gt;Doesn’t depend on unstated assumptions&lt;/li&gt;
  &lt;li&gt;You can point to exact code and explain the concrete failure mode&lt;/li&gt;
  &lt;li&gt;The author would likely fix it if told&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And an explicit “do not flag” list: style nits (unless they obscure meaning), speculative risks without a concrete path, broad architectural opinions, intentional behavior changes.&lt;/p&gt;

&lt;p&gt;Priority levels P0 through P3. A strict output template. And behavioral guardrails: “Prefer no finding over a weak finding. Do not praise the author. Do not pad the review with generic comments.”&lt;/p&gt;

&lt;h2 id=&quot;the-gap&quot;&gt;The gap&lt;/h2&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;Claude &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/review&lt;/code&gt;&lt;/th&gt;
      &lt;th&gt;Codex &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/review&lt;/code&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Prompt length&lt;/td&gt;
      &lt;td&gt;~200 tokens&lt;/td&gt;
      &lt;td&gt;~600 tokens&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Bug criteria&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;6 explicit gates&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;“Do not flag” rules&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;4 exclusions&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Priority levels&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;P0 through P3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Output format&lt;/td&gt;
      &lt;td&gt;“Sections and bullet points”&lt;/td&gt;
      &lt;td&gt;Strict template with verdict&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Pre-existing bugs&lt;/td&gt;
      &lt;td&gt;Not addressed&lt;/td&gt;
      &lt;td&gt;Explicitly excluded&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;False positive control&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;“Prefer no finding over a weak finding”&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Claude trusts the model to figure out what a good review looks like. Codex tells the model exactly what counts and what doesn’t.&lt;/p&gt;

&lt;h2 id=&quot;in-practice&quot;&gt;In practice&lt;/h2&gt;

&lt;p&gt;I’ve been running both on real PRs. Codex consistently flags better findings.&lt;/p&gt;

&lt;p&gt;The priority system is the killer feature. P0 and P1 genuinely need fixing. P2 and P3? Mostly not worth the churn. That triage is built into the output. Scan, fix what matters, move on.&lt;/p&gt;

&lt;p&gt;Claude’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/review&lt;/code&gt; gives you a wall of bullet points at the same level. You do the triage yourself. Which kinda defeats the point.&lt;/p&gt;

&lt;p&gt;One caveat: if you search for “Claude Code Review benchmark,” you’ll find articles claiming less than 1% false positive rates. That’s not the built-in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/review&lt;/code&gt;. That’s a separate &lt;a href=&quot;https://github.com/anthropics/claude-code/blob/main/plugins/code-review/README.md&quot;&gt;/code-review plugin&lt;/a&gt; with a completely different architecture.&lt;/p&gt;

&lt;h2 id=&quot;claudes-other-reviewer-the-one-nobody-uses&quot;&gt;Claude’s other reviewer (the one nobody uses)&lt;/h2&gt;

&lt;p&gt;Claude Code ships an official &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/code-review&lt;/code&gt; plugin that’s far more sophisticated than &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/review&lt;/code&gt;. It’s an 8-step pipeline:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Eligibility check (skip drafts, closed PRs, automated PRs)&lt;/li&gt;
  &lt;li&gt;Gather &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CLAUDE.md&lt;/code&gt; files from affected directories&lt;/li&gt;
  &lt;li&gt;Summarize the PR&lt;/li&gt;
  &lt;li&gt;Launch &lt;strong&gt;5 parallel Sonnet agents&lt;/strong&gt;, each reviewing from a different angle: CLAUDE.md compliance, obvious bugs, git blame history, previous PR comments, and code comment compliance&lt;/li&gt;
  &lt;li&gt;Score every finding 0-100 with Haiku agents using a detailed rubric&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Filter to 80+&lt;/strong&gt;. If nothing qualifies, it stops and posts nothing.&lt;/li&gt;
  &lt;li&gt;Re-check PR is still open&lt;/li&gt;
  &lt;li&gt;Post a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gh pr comment&lt;/code&gt; with only the surviving findings&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If nothing scores 80+, it posts nothing. That’s the reviewer the benchmarks are testing. Not &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/review&lt;/code&gt;.&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/review&lt;/code&gt; (built-in)&lt;/th&gt;
      &lt;th&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/code-review&lt;/code&gt; (plugin)&lt;/th&gt;
      &lt;th&gt;Codex &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;/review&lt;/code&gt;&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;Architecture&lt;/td&gt;
      &lt;td&gt;Single pass&lt;/td&gt;
      &lt;td&gt;8-step, 9-15 model calls&lt;/td&gt;
      &lt;td&gt;Single pass&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Prompt size&lt;/td&gt;
      &lt;td&gt;~200 tokens&lt;/td&gt;
      &lt;td&gt;~1500 tokens + 5 agent prompts&lt;/td&gt;
      &lt;td&gt;~600 tokens&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Bug criteria&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;Implicit via 5 specialized angles&lt;/td&gt;
      &lt;td&gt;6 explicit gates&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;False positive control&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;10 examples + 0-100 scoring, 80+ threshold&lt;/td&gt;
      &lt;td&gt;6 “do not flag” rules&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Priority system&lt;/td&gt;
      &lt;td&gt;None&lt;/td&gt;
      &lt;td&gt;Confidence score&lt;/td&gt;
      &lt;td&gt;P0-P3&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Output format&lt;/td&gt;
      &lt;td&gt;“Sections and bullet points”&lt;/td&gt;
      &lt;td&gt;Strict template with GitHub permalinks&lt;/td&gt;
      &lt;td&gt;Strict verdict template&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Posts to GitHub&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
      &lt;td&gt;Yes (only if 80+ findings)&lt;/td&gt;
      &lt;td&gt;No&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;No findings&lt;/td&gt;
      &lt;td&gt;Generic review anyway&lt;/td&gt;
      &lt;td&gt;Posts nothing&lt;/td&gt;
      &lt;td&gt;“patch is correct” + residual risks&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Cost per review&lt;/td&gt;
      &lt;td&gt;1 call&lt;/td&gt;
      &lt;td&gt;9-15+ calls&lt;/td&gt;
      &lt;td&gt;1 call&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The plugin is powerful but expensive. Codex achieves surprisingly close quality in a single call through better prompt engineering. Claude’s plugin throws compute at the problem.&lt;/p&gt;

&lt;h2 id=&quot;the-trade-off-nobody-talks-about&quot;&gt;The trade-off nobody talks about&lt;/h2&gt;

&lt;p&gt;Codex compiles its review prompt into the Rust binary. You can’t change it without rebuilding from source. There’s a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;review_model&lt;/code&gt; config to swap the model, but the instructions are locked.&lt;/p&gt;

&lt;p&gt;Claude’s prompt is a replaceable markdown file. You could paste Codex’s entire prompt into &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.claude/commands/review.md&lt;/code&gt; and get Codex-quality instructions running on Claude’s model. Or write something better tuned for your stack.&lt;/p&gt;

&lt;p&gt;Codex chose polish. Claude chose extensibility.&lt;/p&gt;

&lt;p&gt;I’m not sure which is right. But I know which one I can fix myself.&lt;/p&gt;

&lt;h2 id=&quot;the-real-lesson&quot;&gt;The real lesson&lt;/h2&gt;

&lt;p&gt;~400 tokens of difference separates “generic feedback” from “structured, prioritized, low-noise findings.” The models are the same tier of capability. The instructions make the difference.&lt;/p&gt;

&lt;p&gt;This applies to every AI tool you use, not just code review. There’s always a prompt behind it. The quality of that prompt determines whether you get useful output or plausible-sounding noise.&lt;/p&gt;

&lt;p&gt;Now you’ve seen what yours actually says.&lt;/p&gt;

&lt;p&gt;Maybe it’s time to &lt;strong&gt;customize your review instructions&lt;/strong&gt;.&lt;/p&gt;
</description>
        <pubDate>Wed, 25 Mar 2026 10:00:00 +0800</pubDate>
        <link>https://samwize.com/2026/03/25/what-your-ai-code-reviewer-actually-prompts-behind-your-back/</link>
        <guid isPermaLink="true">https://samwize.com/2026/03/25/what-your-ai-code-reviewer-actually-prompts-behind-your-back/</guid>
        
        <category>agents</category>
        
        <category>code-review</category>
        
        <category>claude-code</category>
        
        <category>codex</category>
        
        <category>prompts</category>
        
        
        <category>AI</category>
        
        <category>Engineering</category>
        
      </item>
    
  </channel>
</rss>
