<?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: Sakiharu</title>
    <description>The latest articles on DEV Community by Sakiharu (@yw1975).</description>
    <link>https://dev.to/yw1975</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%2F3775304%2F09a1a48b-495d-4e01-b651-52df5020d812.jpeg</url>
      <title>DEV Community: Sakiharu</title>
      <link>https://dev.to/yw1975</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yw1975"/>
    <language>en</language>
    <item>
      <title>A WebSocket Bug Hid in Plain Sight for 8 Years. Two AI Agents Found It in 25 Rounds.</title>
      <dc:creator>Sakiharu</dc:creator>
      <pubDate>Fri, 20 Feb 2026 13:09:36 +0000</pubDate>
      <link>https://dev.to/yw1975/a-websocket-bug-hid-in-plain-sight-for-8-years-two-ai-agents-found-it-in-25-rounds-4hei</link>
      <guid>https://dev.to/yw1975/a-websocket-bug-hid-in-plain-sight-for-8-years-two-ai-agents-found-it-in-25-rounds-4hei</guid>
      <description>&lt;p&gt;If you've ever used the &lt;code&gt;ws&lt;/code&gt; library with webpack, you've probably seen this warning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WARNING: Module not found: Can't resolve 'bufferutil'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Google it. StackOverflow has answers with hundreds of upvotes. GitHub issues going back to 2017. The fix everyone recommends:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;externals&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bufferutil&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;commonjs bufferutil&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8-validate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;commonjs utf-8-validate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We used that fix. It created a bug that took 25 rounds of AI pair debugging to find. The fix was 4 lines.&lt;/p&gt;

&lt;p&gt;I should mention: &lt;strong&gt;I don't write Node.js.&lt;/strong&gt; I haven't written production code in 25 years. I use a dual-agent workflow called &lt;a href="https://github.com/YW1975/Ralph-Lisa-Loop" rel="noopener noreferrer"&gt;Ralph-Lisa Loop&lt;/a&gt; — one AI writes code, another reviews it. This bug is the best example I have of why that matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Zombie Connection
&lt;/h2&gt;

&lt;p&gt;We're building Margay, an Electron app forked from AionUI (15k stars). Remote WebUI mode — access from a browser on your local network — showed a blank screen after login. Desktop mode worked perfectly.&lt;/p&gt;

&lt;p&gt;The WebSocket connection looked completely healthy. Handshake succeeded. Authentication passed. Heartbeat pongs flowing every few seconds. But data messages? Zero.&lt;/p&gt;

&lt;p&gt;Alive on paper. Dead in practice.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Everyone Suggested
&lt;/h2&gt;

&lt;p&gt;First 15 rounds were the StackOverflow greatest hits. Ralph and Lisa tried them all — database path, auth tokens, bridge registration, &lt;code&gt;socket.resume()&lt;/code&gt; for Node.js v23. I also threw the problem at Gemini and ChatGPT independently. Everyone suggested the same "obvious" causes. All ruled out empirically.&lt;/p&gt;

&lt;p&gt;This is where most debugging stories end. You've tried everything reasonable. StackOverflow has nothing new. You start thinking about workarounds, or you give up.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Happened When I Went for Coffee
&lt;/h2&gt;

&lt;p&gt;After 15 rounds of dead ends, Ralph and Lisa wanted to go deeper — add TCP-level monitoring, look inside the &lt;code&gt;ws&lt;/code&gt; library internals. I thought they were crazy. &lt;strong&gt;I'm an application developer. I told them to go back and re-check the application layer.&lt;/strong&gt; There had to be something we missed — a configuration difference between desktop and remote mode, a race condition, something.&lt;/p&gt;

&lt;p&gt;I went to get coffee.&lt;/p&gt;

&lt;p&gt;When I came back, they had done exactly what I asked — re-reviewed the entire application layer one more time, confirmed every hypothesis was eliminated with evidence — and then gone deeper anyway.&lt;/p&gt;

&lt;p&gt;They'd added a raw TCP listener on the socket. The result:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12 TCP frames arrived at the socket. &lt;code&gt;ws.on('message')&lt;/code&gt; fired 0 times.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Data was entering the building. Something inside &lt;code&gt;ws&lt;/code&gt; was eating it silently.&lt;/p&gt;

&lt;p&gt;Over the next few rounds, they traced it down to &lt;code&gt;ws&lt;/code&gt;'s internal Receiver — a Writable stream where the &lt;code&gt;_write&lt;/code&gt; callback stopped firing after the first frame. Then they wrapped it in try-catch and caught the ghost:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bufferUtil.unmask is not a function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every frame, same crash, &lt;strong&gt;zero console output&lt;/strong&gt;. The stream swallowed the exception internally and just stopped. No error, no warning. Frames vanished.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 32-Byte Trap
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;ws&lt;/code&gt; library has an undocumented performance optimization: frames under 32 bytes use a JavaScript fallback, frames over 32 bytes use a native C++ module for unmasking.&lt;/p&gt;

&lt;p&gt;Heartbeat pong: 6 bytes. JavaScript path. &lt;strong&gt;Works.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Data message: 100+ bytes. Native path. &lt;strong&gt;Crashes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is why the connection appeared healthy — heartbeats passed through while all actual data was silently dropped. Perfect camouflage.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Irony
&lt;/h2&gt;

&lt;p&gt;The cause? That popular StackOverflow fix. In normal Node.js, &lt;code&gt;require('bufferutil')&lt;/code&gt; throws MODULE_NOT_FOUND when the package isn't installed, &lt;code&gt;ws&lt;/code&gt; catches it, falls back to JavaScript. Fine.&lt;/p&gt;

&lt;p&gt;But in Electron + webpack 5, the externalized &lt;code&gt;require&lt;/code&gt; didn't throw — it returned a non-functional object. The try-catch never triggered. &lt;code&gt;ws&lt;/code&gt; installed the "native" code path pointing at a ghost module.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The most upvoted fix for this warning was the direct cause of our bug.&lt;/strong&gt; We're not alone — in &lt;a href="https://github.com/websockets/ws/issues/2288" rel="noopener noreferrer"&gt;June 2025&lt;/a&gt;, someone reported the same issue in Next.js production builds. This class of bug has been hiding in the ws + bundler ecosystem for years.&lt;/p&gt;

&lt;p&gt;The fix: tell &lt;code&gt;ws&lt;/code&gt; to skip native modules entirely via environment variables at build time, and remove the externals. 4 lines.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Real Lesson
&lt;/h2&gt;

&lt;p&gt;Here's what I keep thinking about: &lt;strong&gt;I was wrong and the agents were right.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm a 30-year software veteran. My instinct said "go back to the application layer." That instinct is what StackOverflow would have reinforced, what any single AI chat would have supported, and what most developers do when they're stuck — try the same layer one more time, check one more config, ask one more question.&lt;/p&gt;

&lt;p&gt;Ralph and Lisa didn't have that instinct. They had a method: &lt;strong&gt;eliminate a layer with evidence, then go deeper. Don't revisit what's already proven clean.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's the point of a dual-agent loop. It's not that two AIs are smarter than one. It's that the structure enforces a systematic discipline that humans — even experienced ones — resist when the answer requires leaving their comfort zone. Ralph investigates, Lisa validates each step, and neither moves on without evidence. When I tried to pull them back to familiar territory, they went back, proved it clean &lt;em&gt;again&lt;/em&gt;, and continued down.&lt;/p&gt;

&lt;p&gt;Every application developer has hit a bug like this — something that doesn't make sense at your layer, so you keep searching at your layer, getting more frustrated, eventually giving up or working around it. &lt;strong&gt;Next time, don't Google it for the 50th time. Don't ask ChatGPT the same question with different wording. Let a dual-agent engine run the systematic elimination that you won't have the patience to do yourself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;25 rounds. 4 lines of fix. A root cause that had been hiding in plain sight across the Electron ecosystem since 2017.&lt;/p&gt;

&lt;p&gt;We've filed &lt;a href="https://github.com/websockets/ws/issues/2311" rel="noopener noreferrer"&gt;ws#2311&lt;/a&gt; suggesting a defensive typeof check in &lt;code&gt;buffer-util.js&lt;/code&gt; — validate that &lt;code&gt;mask&lt;/code&gt; and &lt;code&gt;unmask&lt;/code&gt; are actual functions before installing the native code path. Zero-cost, one-time check at module load. Would have prevented this entire class of silent failure.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Second in a series on AI pair programming. First post: &lt;a href="https://dev.to/yw1975/after-2-years-of-ai-assisted-coding-i-automated-the-one-thing-that-actually-improved-quality-ai-pair-programming"&gt;After 2 years of AI-assisted coding, I automated the one thing that actually improved quality: AI Pair Programming&lt;/a&gt;. Tool: &lt;a href="https://github.com/YW1975/Ralph-Lisa-Loop" rel="noopener noreferrer"&gt;Ralph-Lisa Loop&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>automation</category>
    </item>
    <item>
      <title>After 2 years of AI-assisted coding, I automated the one thing that actually improved quality: AI Pair Programming</title>
      <dc:creator>Sakiharu</dc:creator>
      <pubDate>Mon, 16 Feb 2026 09:14:05 +0000</pubDate>
      <link>https://dev.to/yw1975/after-2-years-of-ai-assisted-coding-i-automated-the-one-thing-that-actually-improved-quality-ai-2njh</link>
      <guid>https://dev.to/yw1975/after-2-years-of-ai-assisted-coding-i-automated-the-one-thing-that-actually-improved-quality-ai-2njh</guid>
      <description>&lt;p&gt;After nearly 2 years of AI-assisted development — from ChatGPT 3.5 to Claude Code — I kept hitting the same problem: every model makes mistakes it can't catch. Inspired by pair programming and the Ralph Loop, I built a dual-agent workflow where one agent writes and another reviews. Last week, a PR written entirely by the two agents got merged into a 15k-star open source Electron project after 3 rounds of maintainer feedback. I don't write TypeScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problems I kept finding
&lt;/h2&gt;

&lt;p&gt;I've been doing AI-assisted programming for almost 2 years now. Started with ChatGPT 3.5 generating snippets, moved through Claude, Cursor, TRAE, and eventually fell in love with Claude Code.&lt;br&gt;
From the very beginning, I noticed every model and every agent has its own characteristic problems. Not random bugs — consistent patterns of failure:&lt;/p&gt;

&lt;p&gt;Claude Code skips error handling when context gets long. It's brilliant at architecture but gets sloppy on defensive code in later turns.&lt;br&gt;
Codex over-engineers abstractions but catches edge cases Claude misses.&lt;br&gt;
Gemini struggles with complex multi-file changes.&lt;br&gt;
Cursor has context dependency issues — works great in small scope, gets confused across files.&lt;/p&gt;

&lt;p&gt;The severity varies, but the pattern is the same: a single agent can't reliably catch its own mistakes. It writes code AND judges whether that code is good — like grading your own exam.&lt;br&gt;
Every developer knows this problem has a name. It's called "why we do code review."&lt;/p&gt;

&lt;h2&gt;
  
  
  Pair programming, but with AIs
&lt;/h2&gt;

&lt;p&gt;Pair programming was formalized by Kent Beck as part of Extreme Programming (XP) in the late 1990s — one of the most influential practices to come out of the agile movement. The core idea is simple: two developers at one workstation, one drives, one navigates. The navigator catches mistakes in real time, questions design decisions, and keeps the big picture in focus. Research has consistently shown it produces fewer defects and better designs, despite appearing to "waste" half your developers.&lt;br&gt;
The same principle applies to AI agents. If one agent writes and another watches, you catch more bugs.&lt;br&gt;
So that's what I started doing — manually. Way back when I was using Claude (the chat version, before Claude Code), I would take Claude's output, paste it into ChatGPT, ask ChatGPT to review it, then bring the feedback back. Primitive, but it worked better than trusting either one alone.&lt;br&gt;
When Claude Code and Codex CLI came along, the workflow got more serious. Claude Code writes code, I copy the diffs to Codex, Codex reviews and flags issues, I bring the feedback back to Claude Code. Rinse and repeat.&lt;br&gt;
This manual cross-agent coordination worked. But it was slow, repetitive, and cognitively draining. The worst part: it was easy to skip when tired. You tell yourself "this change looks fine, I'll skip the review step" — and that's always the change that bites you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automating the loop
&lt;/h2&gt;

&lt;p&gt;Then I discovered the Ralph Loop (by Geoffrey Huntley) — the concept of wrapping a coding agent in an external loop so it keeps iterating. Powerful idea, and it gave me the push to automate my dual-agent workflow.&lt;br&gt;
But the Ralph Loop team has been transparent about some limitations. It works great for greenfield projects with clear completion criteria. It's harder with legacy codebases, complex refactoring, or multi-step tasks where you need checkpoints along the way.&lt;br&gt;
That matched my experience. I wasn't building new projects from scratch — I was forking and deeply modifying an existing large Electron app. I needed something that could handle ambiguity, maintainer feedback, and incremental consensus.&lt;br&gt;
So I built a structured loop: one agent (Claude Code) writes, another (Codex) reviews, they take turns, and neither moves forward until both agree. I sit in the middle as tech lead — setting scope, making architecture calls, breaking ties.&lt;br&gt;
The efficiency jumped immediately. Not because the agents got smarter, but because the review discipline became automatic instead of depending on my willpower at 2am.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real test: my first open source PR
&lt;/h2&gt;

&lt;p&gt;I'd been using this workflow to fork AionUI (~15k ⭐ Electron + React app) into an internal AI assistant for my company. 30 commits, zero manual code. Full rebrand, core engine rewrite, database migration, CI/CD rebuild — the whole thing done through the dual-agent loop.&lt;br&gt;
During that work, the agents found a real upstream bug: orphan CLI processes that linger when you kill a conversation using ACP agents. I submitted a PR back to AionUI.&lt;br&gt;
The maintainer reviewed it and came back with 3 issues:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Double super.kill()&lt;/strong&gt; race condition — needed an idempotent guard&lt;br&gt;
&lt;strong&gt;Swallowed errors&lt;/strong&gt; — .catch(() =&amp;gt; {}) should log warnings&lt;br&gt;
&lt;strong&gt;treeKill discrepancy&lt;/strong&gt; — the PR description didn't match upstream's actual implementation&lt;/p&gt;

&lt;p&gt;I pointed the two agents at the maintainer's feedback and let them work. The author agent analyzed the issues, wrote the fixes, ran tests (133/133 passing). The reviewer agent reviewed the diffs, verified correctness, confirmed types were clean. A few rounds of back-and-forth. I watched but didn't write code.&lt;br&gt;
Merged. "LGTM — all three review feedback items properly addressed."&lt;br&gt;
This was my first ever PR submitted and merged into someone else's project. I'm a 30-year software veteran — but I spent the last 25 years on product and business, not writing code. I don't write TypeScript. AI tools pulled me back into development, and the dual-agent loop made it possible for me to contribute real fixes to a real project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Independent convergence
&lt;/h2&gt;

&lt;p&gt;After I posted about this, another developer (Hwee-Boon Yar, indie dev, also 30 years experience) shared a similar approach — a skill that shells out to a second agent for review, loops until the reviewer has nothing left to flag. Lighter than mine, works within a single session. Different trade-off, same core insight.&lt;br&gt;
Multiple people are independently arriving at this: one agent is not enough. You need a second pair of eyes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;This is not a magic solution. Here's what doesn't work:&lt;br&gt;
Agent crashes have no auto-recovery. When an agent dies mid-session, the loop stops. You restart manually. No self-healing yet.&lt;br&gt;
Wasted rounds. Sometimes the agents ping-pong — a fix introduces a new issue, review catches it, the next fix introduces another issue. You have to step in and reset scope.&lt;br&gt;
Context window — but with a twist. Quality degrades in long sessions, and when an agent compresses its context, information gets lost. But here's where the dual-agent setup actually helps: when one agent's context is compressed and loses details, the other agent still remembers. They don't share the same context window, so they don't lose the same information at the same time. This is an unexpected architectural advantage. I'm thinking about building shared memory management across agents in future versions — so they can explicitly share what each has forgotten.&lt;br&gt;
Two AIs can happily agree on a bad design. Without domain judgment from a human, this is just two agents rubber-stamping each other. The human arbiter is not optional.&lt;br&gt;
This is not autonomous development. It is structured AI-assisted development. The distinction matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The deeper question
&lt;/h2&gt;

&lt;p&gt;The AI coding conversation is too focused on generation and not enough on review. Everyone's benchmarking how fast and how much code models can produce. Nobody's asking: who checks it?&lt;br&gt;
If AI code needs structured critique — the same way human code has always needed code review — then the question is: how do you build review discipline into AI workflows?&lt;/p&gt;

&lt;h2&gt;
  
  
  Just shipped v0.3.0
&lt;/h2&gt;

&lt;p&gt;I've incorporated what I learned from the AionUI PR process and released a new version. Key stuff:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;npm i -g ralph-lisa-loop&lt;/strong&gt;&lt;br&gt;
Works with Claude Code (Ralph) + Codex CLI (Lisa)&lt;br&gt;
Turn control, tag system, consensus protocol, policy checks&lt;br&gt;
Auto mode via tmux (experimental)&lt;br&gt;
Agent-agnostic in principle — any two CLI agents can fill the roles&lt;/p&gt;

&lt;p&gt;Early stage. Using daily for real work, not demos.&lt;br&gt;
Repo: &lt;a href="https://github.com/YW1975/Ralph-Lisa-Loop" rel="noopener noreferrer"&gt;&lt;/a&gt;&lt;br&gt;
If you've been doing AI coding and hitting that frustrating "almost right, but not quite" problem — you're not alone. This might help, or at least give you ideas for your own approach.&lt;br&gt;
Happy to discuss. The failure modes are more interesting than the successes.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>discuss</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
