<?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: Waclaw Kusnierczyk</title>
    <description>The latest articles on DEV Community by Waclaw Kusnierczyk (@wkusnierczyk).</description>
    <link>https://dev.to/wkusnierczyk</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%2F3759808%2F2a13a184-2309-4eec-87f9-55c31f3f0fcb.jpeg</url>
      <title>DEV Community: Waclaw Kusnierczyk</title>
      <link>https://dev.to/wkusnierczyk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wkusnierczyk"/>
    <language>en</language>
    <item>
      <title>Claude Code Plugin Cache</title>
      <dc:creator>Waclaw Kusnierczyk</dc:creator>
      <pubDate>Wed, 25 Feb 2026 11:52:35 +0000</pubDate>
      <link>https://dev.to/wkusnierczyk/claude-code-plugin-cache-1dn</link>
      <guid>https://dev.to/wkusnierczyk/claude-code-plugin-cache-1dn</guid>
      <description>&lt;h1&gt;
  
  
  Claude Code Plugin Cache Gotcha: Why Your Edits Aren't Taking Effect
&lt;/h1&gt;

&lt;p&gt;If you're developing a Claude Code plugin and your changes aren't showing up after restarting the session, you're probably hitting the plugin cache.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;You edit a command, agent, or skill file in your plugin directory. You restart Claude Code. The old version still runs. You restart again. Same thing. You stare at the file, confirm it's correct on disk, question your sanity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cause
&lt;/h2&gt;

&lt;p&gt;Claude Code caches plugin files under &lt;code&gt;~/.claude/plugins/cache/&lt;/code&gt;, keyed by plugin name and version from your &lt;code&gt;plugin.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;~/.claude/plugins/cache/local-plugins/your-plugin/0.1.0/commands/your-command.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you start a session, Claude Code checks the cache first. If it finds a match for your plugin name and version, it serves the cached copy — &lt;strong&gt;without checking whether the source files have changed&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Option A&lt;/strong&gt;: Bump the version in &lt;code&gt;.claude-plugin/plugin.json&lt;/code&gt; after making changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-plugin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.1.0"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Change it to &lt;code&gt;0.1.1&lt;/code&gt;, &lt;code&gt;0.2.0&lt;/code&gt;, whatever — just make it different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option B&lt;/strong&gt;: Delete the cache manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; ~/.claude/plugins/cache/local-plugins/your-plugin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Either way, restart the session afterward.&lt;/p&gt;

&lt;h2&gt;
  
  
  During active development
&lt;/h2&gt;

&lt;p&gt;If you're iterating on a plugin, you'll hit this constantly. A quick workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# after editing plugin files&lt;/span&gt;
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; ~/.claude/plugins/cache/local-plugins/my-plugin
&lt;span class="c"&gt;# then restart your Claude Code session&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or adopt a habit of bumping the patch version with every edit cycle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is this a bug?
&lt;/h2&gt;

&lt;p&gt;It looks like one. A local plugin's cache should be invalidated when the source files change — checking file modification timestamps would be sufficient. Caching by version alone makes sense for published/marketplace plugins, but for local development it creates a confusing experience where your edits silently don't apply.&lt;/p&gt;

&lt;p&gt;This has been reported multiple times:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/anthropics/claude-code/issues/14061" rel="noopener noreferrer"&gt;#14061&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/anthropics/claude-code/issues/15621" rel="noopener noreferrer"&gt;#15621&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/anthropics/claude-code/issues/17361" rel="noopener noreferrer"&gt;#17361&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/anthropics/claude-code/issues/28492" rel="noopener noreferrer"&gt;#28492&lt;/a&gt; (filed by us after encountering this firsthand)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As of February 2025, the issue remains unresolved.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tooling</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>aigent: toolchain for AI agent skills</title>
      <dc:creator>Waclaw Kusnierczyk</dc:creator>
      <pubDate>Mon, 23 Feb 2026 10:06:34 +0000</pubDate>
      <link>https://dev.to/wkusnierczyk/aigent-toolchain-for-ai-agent-skills-3hib</link>
      <guid>https://dev.to/wkusnierczyk/aigent-toolchain-for-ai-agent-skills-3hib</guid>
      <description>&lt;p&gt;AI agents are getting good at following instructions. The bottleneck has shifted: it's no longer about what the model can do, but about how well you package what it should do. That packaging layer is &lt;strong&gt;agent skills&lt;/strong&gt; — structured documents that tell an agent what a capability does, when to activate it, and how to use it.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://agentskills.io" rel="noopener noreferrer"&gt;Agent Skills open standard&lt;/a&gt;, originally defined by Anthropic for Claude Code, codifies this into a simple format: a &lt;code&gt;SKILL.md&lt;/code&gt; file with YAML frontmatter (name, description, compatibility, allowed tools) and a Markdown body with detailed instructions. The metadata is indexed at session start for fast discovery; the body is loaded on demand, following a progressive-disclosure pattern that keeps the context window lean.&lt;/p&gt;

&lt;p&gt;The format is simple. Getting it right at scale is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;When you have a handful of skills, you can eyeball them. When you have dozens — across teams, repositories, and CI pipelines — you need tooling. Names drift from conventions. Descriptions become vague. Activation patterns go untested. Formatting diverges. Nobody catches the skill that fails to trigger because its description doesn't match the query patterns users actually type.&lt;/p&gt;

&lt;p&gt;The specification defines &lt;em&gt;what&lt;/em&gt; a valid skill looks like. The &lt;a href="https://github.com/agentskills/agentskills" rel="noopener noreferrer"&gt;Python reference implementation&lt;/a&gt; provides basic validation, but it's a library — not a toolchain you can drop into CI, pre-commit hooks, or a plugin build pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  What &lt;code&gt;aigent&lt;/code&gt; does
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/wkusnierczyk/aigent" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;code&gt;aigent&lt;/code&gt;&lt;/strong&gt;&lt;/a&gt; is a Rust library, native CLI, and Claude Code plugin that implements the Agent Skills specification as a proper toolchain — the same way a formatter, linter, and test runner enforce conventions in any mature language ecosystem.&lt;/p&gt;

&lt;p&gt;It covers the full skill lifecycle:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create&lt;/strong&gt; — Generate skills from natural language with &lt;code&gt;aigent new&lt;/code&gt;. Deterministic by default, with optional LLM enhancement (Anthropic, OpenAI, Google, Ollama backends). Produces a complete &lt;code&gt;SKILL.md&lt;/code&gt; directory ready for validation and refinement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Validate&lt;/strong&gt; — Check skills against the specification with typed diagnostics, error codes, and JSON output. Run &lt;code&gt;aigent validate&lt;/code&gt; in CI to catch problems before they reach production. Three severity levels (error, warning, info) and a &lt;code&gt;--format json&lt;/code&gt; flag for machine consumption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Format&lt;/strong&gt; — Canonical YAML key ordering, consistent whitespace, idempotent output. &lt;code&gt;aigent format --check&lt;/code&gt; returns non-zero when files need formatting — drop it into a pre-commit hook.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Score&lt;/strong&gt; — A weighted 0-100 quality score against a best-practices checklist. Structural checks (60 points) verify specification conformance; quality checks (40 points) evaluate description clarity, trigger phrases, naming conventions, and detail. Use it as a CI gate: &lt;code&gt;aigent score my-skill/ || exit 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test&lt;/strong&gt; — Fixture-based testing from &lt;code&gt;tests.yml&lt;/code&gt;. Define input queries, expected match/no-match results, and minimum score thresholds. &lt;code&gt;aigent test&lt;/code&gt; runs the suite and reports pass/fail. &lt;code&gt;aigent probe&lt;/code&gt; does single-query dry-runs: "if a user said &lt;em&gt;this&lt;/em&gt;, would the agent pick up &lt;em&gt;that&lt;/em&gt; skill?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build&lt;/strong&gt; — Assemble skills into Claude Code plugins with &lt;code&gt;aigent build&lt;/code&gt;. Validate entire plugin directories — manifest, hooks, agents, commands, skills, and cross-component consistency — with &lt;code&gt;aigent validate-plugin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fwkusnierczyk%2Faigent%2Fraw%2Fmain%2Fgraphics%2Fhello.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fwkusnierczyk%2Faigent%2Fraw%2Fmain%2Fgraphics%2Fhello.gif" alt="aigent demo" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Specification compliance
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;aigent&lt;/code&gt; implements every validation rule from the &lt;a href="https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices" rel="noopener noreferrer"&gt;Anthropic specification&lt;/a&gt;, plus additional checks that go beyond both the specification and the Python reference implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All name constraints (length, casing, reserved words, XML tags, Unicode NFKC normalization)&lt;/li&gt;
&lt;li&gt;All description constraints (length, non-empty, no XML/HTML)&lt;/li&gt;
&lt;li&gt;Frontmatter structure, delimiter matching, compatibility field limits&lt;/li&gt;
&lt;li&gt;Body length warnings&lt;/li&gt;
&lt;li&gt;Path canonicalization and symlink safety&lt;/li&gt;
&lt;li&gt;Post-build validation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where the specification and the reference implementation diverge, &lt;code&gt;aigent&lt;/code&gt; reconciles them and documents the decision. &lt;/p&gt;

&lt;p&gt;See the &lt;a href="https://github.com/wkusnierczyk/aigent#compliance" rel="noopener noreferrer"&gt;compliance section&lt;/a&gt; in the README for a detailed three-way comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond individual skills
&lt;/h2&gt;

&lt;p&gt;Skills don't exist in isolation. As your collection grows, new problems emerge: name collisions, overlapping descriptions that confuse activation, token budgets that exceed context limits. &lt;code&gt;aigent&lt;/code&gt; handles this with cross-skill conflict detection, token budget estimation, and batch validation across directories.&lt;/p&gt;

&lt;p&gt;And skills are just one part of a Claude Code plugin. &lt;code&gt;aigent&lt;/code&gt;'s &lt;code&gt;validate-plugin&lt;/code&gt; command checks the full plugin ecosystem: the &lt;code&gt;plugin.json&lt;/code&gt; manifest, &lt;code&gt;hooks.json&lt;/code&gt; configuration, agent files, command files, skill subdirectories, and cross-component consistency. Typed diagnostics with error codes (P001-P010 for manifests, H001-H007 for hooks, X001-X008 for cross-component) give you the same deterministic enforcement across the entire plugin structure.&lt;/p&gt;

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

&lt;p&gt;The agentic AI ecosystem is moving fast. Tools like Anthropic's &lt;code&gt;plugin-dev&lt;/code&gt; teach you the patterns; &lt;code&gt;aigent&lt;/code&gt; enforces them. Think of it as the difference between a language tutorial and a linter — you need both, but they serve different purposes.&lt;/p&gt;

&lt;p&gt;Other tools in the space, like &lt;a href="https://github.com/skillcreatorai/Ai-Agent-Skills" rel="noopener noreferrer"&gt;AI Agent Skills&lt;/a&gt;, focus on distribution — installing pre-built skills across multiple agents. &lt;code&gt;aigent&lt;/code&gt; focuses on authoring quality: making sure what you publish is correct, consistent, and well-tested before it reaches any distribution channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
cargo &lt;span class="nb"&gt;install &lt;/span&gt;aigent                        &lt;span class="c"&gt;# official distribution at crates.io&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;wkusnierczyk/aigent/aigent     &lt;span class="c"&gt;# homebrew tap&lt;/span&gt;

&lt;span class="c"&gt;# Create, validate, score&lt;/span&gt;
aigent new &lt;span class="s2"&gt;"process PDF files and extract text"&lt;/span&gt; &lt;span class="nt"&gt;--no-llm&lt;/span&gt;
aigent check extracting-text-pdf-files/
aigent score extracting-text-pdf-files/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  About
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ aigent --about
aigent: Rust AI Agent Skills Tool
├─ version:    0.6.3
├─ author:     Wacław Kuśnierczyk
├─ developer:  mailto:waclaw.kusnierczyk@gmail.com
├─ source:     https://github.com/wkusnierczyk/aigent
└─ licence:    Apache-2.0 https://www.apache.org/licenses/LICENSE-2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://www.apache.org/licenses/LICENSE-2.0" rel="noopener noreferrer"&gt;Apache 2.0&lt;/a&gt; — see &lt;a href="https://www.apache.org/licenses/LICENSE-2.0" rel="noopener noreferrer"&gt;apache.org/licenses/LICENSE-2.0&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reference&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Sources&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wkusnierczyk/aigent" rel="noopener noreferrer"&gt;https://github.com/wkusnierczyk/aigent&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Releases&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wkusnierczyk/aigent/releases" rel="noopener noreferrer"&gt;https://github.com/wkusnierczyk/aigent/releases&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Crates&lt;/td&gt;
&lt;td&gt;&lt;a href="https://crates.io/crates/aigent" rel="noopener noreferrer"&gt;https://crates.io/crates/aigent&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docs&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.rs/aigent" rel="noopener noreferrer"&gt;https://docs.rs/aigent&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

</description>
      <category>ai</category>
      <category>rust</category>
      <category>opensource</category>
      <category>cli</category>
    </item>
    <item>
      <title>Auto Memory, Auto Forget</title>
      <dc:creator>Waclaw Kusnierczyk</dc:creator>
      <pubDate>Sun, 08 Feb 2026 11:55:42 +0000</pubDate>
      <link>https://dev.to/wkusnierczyk/auto-memory-auto-forget-g05</link>
      <guid>https://dev.to/wkusnierczyk/auto-memory-auto-forget-g05</guid>
      <description>&lt;h2&gt;
  
  
  Claude Code's Auto Memory Isn't Thread-Safe
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; 2026-02-08&lt;/p&gt;

&lt;p&gt;Claude Code has a handy auto memory feature: a project-scoped &lt;code&gt;MEMORY.md&lt;/code&gt; file that persists across conversations. Agents read it at startup and write to it when they learn something worth remembering — patterns, gotchas, user preferences. It works great for single-agent sessions.&lt;/p&gt;

&lt;p&gt;Then Anthropic ships agent teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Agent teams let multiple Claude Code instances work on the same project simultaneously. A lead agent coordinates, teammates work independently in their own context windows, and they share a task list. They also share the same project-scoped memory directory.&lt;/p&gt;

&lt;p&gt;The memory file (&lt;code&gt;~/.claude/projects/&amp;lt;project&amp;gt;/memory/MEMORY.md&lt;/code&gt;) is a plain file on disk. The &lt;code&gt;Edit&lt;/code&gt; tool reads the file, does a string match-and-replace, and writes the whole file back. No locking, no compare-and-swap, no merge strategy.&lt;/p&gt;

&lt;p&gt;If two agents discover something worth recording at roughly the same time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Agent A reads &lt;code&gt;MEMORY.md&lt;/code&gt; (version 1)&lt;/li&gt;
&lt;li&gt;Agent B reads &lt;code&gt;MEMORY.md&lt;/code&gt; (version 1)&lt;/li&gt;
&lt;li&gt;Agent A writes its update (version 2a)&lt;/li&gt;
&lt;li&gt;Agent B writes its update (version 2b, based on version 1)&lt;/li&gt;
&lt;li&gt;Agent A's update is silently lost&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a classic lost-update race condition. It also affects background agents launched via &lt;code&gt;Task&lt;/code&gt; with &lt;code&gt;run_in_background&lt;/code&gt;, which have been around longer than agent teams.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters
&lt;/h2&gt;

&lt;p&gt;Memory writes are infrequent in single-agent sessions, so the window for collision is small. But agent teams change the calculus:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multiple agents are running concurrently by design&lt;/li&gt;
&lt;li&gt;They're working on the same project, so they share the same memory file&lt;/li&gt;
&lt;li&gt;Complex tasks (the kind you'd use teams for) are exactly the kind that generate new insights worth recording&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You won't know something was lost until a future session acts on stale or missing knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Possible fixes
&lt;/h2&gt;

&lt;p&gt;We filed &lt;a href="https://github.com/anthropics/claude-code/issues/24130" rel="noopener noreferrer"&gt;anthropics/claude-code#24130&lt;/a&gt; with four suggested approaches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;File locking&lt;/strong&gt; — &lt;code&gt;flock&lt;/code&gt; or equivalent around reads and writes. Simple, proven, platform-dependent.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Append-only log&lt;/strong&gt; — agents only append; a periodic compaction pass deduplicates. This eliminates the &lt;em&gt;lost-update race&lt;/em&gt;, but the file grows, needs garbage collection, and contradictory entries can coexist until compaction resolves them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Per-agent memory files&lt;/strong&gt; — each agent writes to &lt;code&gt;memory/&amp;lt;agent-id&amp;gt;.md&lt;/code&gt;, and a merged view is assembled for the system prompt. Clean separation, but needs a merge strategy for contradictory entries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Compare-and-swap&lt;/strong&gt; — re-read the file before writing, retry if it changed since the last read. Works well for low-contention scenarios (which memory writes are).&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Workaround
&lt;/h2&gt;

&lt;p&gt;For now, if you're using agent teams or background agents, be aware that concurrent memory writes can lose data. You can mitigate by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Having only the lead agent write to memory&lt;/li&gt;
&lt;li&gt;Using separate memory files per topic (reduces collision surface)&lt;/li&gt;
&lt;li&gt;Reviewing &lt;code&gt;MEMORY.md&lt;/code&gt; after parallel sessions to catch any gaps&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Bun's Coverage Threshold</title>
      <dc:creator>Waclaw Kusnierczyk</dc:creator>
      <pubDate>Sun, 08 Feb 2026 11:40:26 +0000</pubDate>
      <link>https://dev.to/wkusnierczyk/buns-coverage-threshold-5gkj</link>
      <guid>https://dev.to/wkusnierczyk/buns-coverage-threshold-5gkj</guid>
      <description>&lt;h2&gt;
  
  
  Bun's Coverage Threshold: Three Undocumented Behaviors That Will Waste Your Afternoon
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Date:&lt;/strong&gt; 2026-02-08&lt;/p&gt;

&lt;p&gt;On a private project, we spent a full debugging session chasing down why &lt;code&gt;bun test --coverage&lt;/code&gt; exits with code 1 despite all 757 tests passing and overall line coverage sitting at 80% — comfortably above our 80% threshold. There was no error message and no indication of what failed. Just a silent exit code 1 annoyingly breaking the CI.&lt;/p&gt;

&lt;p&gt;Here's what we found.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;bunfig.toml&lt;/code&gt; looked perfectly reasonable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[test]&lt;/span&gt;
&lt;span class="py"&gt;coverageThreshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;lines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="py"&gt;coverageSkipTestFiles&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;coveragePathIgnorePatterns&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="s"&gt;"src/app/**"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s"&gt;"src/components/**"&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;Coverage output showed everything green:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;All files  |   63.40 |   80.03 |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;80.03% lines, threshold is 80%. Should pass. Doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 1: The threshold is per-file, not overall
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://bun.sh/docs/test/code-coverage" rel="noopener noreferrer"&gt;docs&lt;/a&gt; say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;To require 90% line-level and function-level coverage: &lt;code&gt;coverageThreshold = 0.9&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This implies it checks the "All files" aggregate. &lt;strong&gt;It doesn't.&lt;/strong&gt; Bun checks every single file individually against the threshold. If you have a utility module at 19% line coverage and your threshold is 80%, the build fails — even if your overall coverage is well above 80%.&lt;/p&gt;

&lt;p&gt;In our project, files like &lt;code&gt;rag-lookup.ts&lt;/code&gt; (2.7% lines) and &lt;code&gt;ollama-client.ts&lt;/code&gt; (19.7% lines) were dragging things down per-file while the overall sat at 80%.&lt;/p&gt;

&lt;p&gt;This is &lt;a href="https://github.com/oven-sh/bun/issues/17028" rel="noopener noreferrer"&gt;oven-sh/bun#17028&lt;/a&gt;, filed Feb 2025, labeled &lt;code&gt;bug&lt;/code&gt;, still open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 2: Specifying &lt;code&gt;lines&lt;/code&gt; implicitly enforces a function threshold
&lt;/h2&gt;

&lt;p&gt;Even after we dropped the threshold to &lt;code&gt;{ lines = 0.5 }&lt;/code&gt; (way below our actual coverage), it still failed. We swept through threshold values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;functions=0.0   EXIT=0   &amp;lt;-- only this passes
functions=0.05  EXIT=1
functions=0.1   EXIT=1
functions=0.3   EXIT=1
functions=0.5   EXIT=1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Specifying &lt;em&gt;any&lt;/em&gt; &lt;code&gt;lines&lt;/code&gt; value in the threshold object causes bun to also enforce a function coverage check with some implicit default. Many of our files have 0% function coverage (bun counts top-level code as "line" coverage but not "function" coverage), so any non-zero function threshold fails them.&lt;/p&gt;

&lt;p&gt;The fix: &lt;code&gt;coverageThreshold = { lines = 0.8, functions = 0 }&lt;/code&gt;. But since gotcha #1 makes this per-file anyway, it still doesn't do what we want.&lt;/p&gt;

&lt;p&gt;This behavior is completely undocumented. The docs show &lt;code&gt;{ lines = 0.9, functions = 0.9 }&lt;/code&gt; as an example but never mention what happens to unspecified metrics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha 3: No error output whatsoever
&lt;/h2&gt;

&lt;p&gt;When the threshold check fails, bun prints the normal coverage table and then exits with code 1. No message saying "file X has Y% coverage, below threshold Z%". No indication that the &lt;em&gt;threshold&lt;/em&gt; is what failed vs. a test failure. Just a silent non-zero exit.&lt;/p&gt;

&lt;p&gt;Combined with gotchas 1 and 2, this means you see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All tests pass&lt;/li&gt;
&lt;li&gt;Overall coverage above threshold&lt;/li&gt;
&lt;li&gt;Exit code 1&lt;/li&gt;
&lt;li&gt;No explanation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Our Workaround
&lt;/h2&gt;

&lt;p&gt;We removed &lt;code&gt;coverageThreshold&lt;/code&gt; from &lt;code&gt;bunfig.toml&lt;/code&gt; entirely and enforce the threshold in a custom script that parses the "All files" line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// scripts/update-coverage-report.ts&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;REQUIRED_LINE_PCT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allLines&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;REQUIRED_LINE_PCT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`\n✗ Overall line coverage &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;allLines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFixed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;% is below the &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;REQUIRED_LINE_PCT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;% threshold`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script runs &lt;code&gt;bun test --coverage&lt;/code&gt;, parses the text output, checks the overall aggregate, and prints a clear error message if it fails. CI calls this script instead of relying on bun's built-in threshold.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="c"&gt;# bunfig.toml&lt;/span&gt;
&lt;span class="nn"&gt;[test]&lt;/span&gt;
&lt;span class="c"&gt;# NOTE: We do NOT use coverageThreshold here because bun enforces it&lt;/span&gt;
&lt;span class="c"&gt;# per-file (not overall), and many utility files have low coverage.&lt;/span&gt;
&lt;span class="c"&gt;# Overall threshold is enforced in scripts/update-coverage-report.ts instead.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Bonus: &lt;code&gt;[test].exclude&lt;/code&gt; doesn't exist either
&lt;/h2&gt;

&lt;p&gt;While debugging this, we also discovered that &lt;code&gt;[test].exclude&lt;/code&gt; in &lt;code&gt;bunfig.toml&lt;/code&gt; is silently ignored — it's not a real option. Bun's test runner has no built-in way to exclude test files from discovery. The workaround is &lt;code&gt;describe.skipIf()&lt;/code&gt; at runtime. Tracked in &lt;a href="https://github.com/oven-sh/bun/issues/21395" rel="noopener noreferrer"&gt;oven-sh/bun#21395&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Upstream Issues
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/oven-sh/bun/issues/17028" rel="noopener noreferrer"&gt;oven-sh/bun#17028&lt;/a&gt; — &lt;code&gt;coverageThreshold&lt;/code&gt; is per-file, not overall (labeled &lt;code&gt;bug&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/oven-sh/bun/issues/5099" rel="noopener noreferrer"&gt;oven-sh/bun#5099&lt;/a&gt; — Feature request for per-folder threshold control&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/oven-sh/bun/issues/21395" rel="noopener noreferrer"&gt;oven-sh/bun#21395&lt;/a&gt; — Feature request for test file exclusion&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;If &lt;code&gt;bun test --coverage&lt;/code&gt; exits 1 with all tests passing, the culprit is almost certainly a per-file coverage check on a low-coverage file.&lt;/li&gt;
&lt;li&gt;Always specify &lt;code&gt;functions = 0&lt;/code&gt; if you only care about line coverage — otherwise bun applies a phantom function threshold.&lt;/li&gt;
&lt;li&gt;Parse the coverage output yourself if you need overall (not per-file) threshold enforcement.&lt;/li&gt;
&lt;li&gt;Bun is still young. Its test runner has rough edges. When something doesn't work, check whether the config option actually exists before assuming your config is wrong.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>codequality</category>
      <category>javascript</category>
      <category>testing</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Two Bugs, One Symptom</title>
      <dc:creator>Waclaw Kusnierczyk</dc:creator>
      <pubDate>Sun, 08 Feb 2026 10:38:28 +0000</pubDate>
      <link>https://dev.to/wkusnierczyk/two-bugs-one-symptom-24h3</link>
      <guid>https://dev.to/wkusnierczyk/two-bugs-one-symptom-24h3</guid>
      <description>&lt;p&gt;A debugging war story from implementing SSE client transport in Raku MCP SDK.&lt;/p&gt;




&lt;p&gt;The task seemed straightforward: add legacy SSE transport to &lt;a href="https://github.com/anthropics/model-context-protocol" rel="noopener noreferrer"&gt;Raku MCP SDK&lt;/a&gt;. The server side went smoothly — &lt;code&gt;Cro&lt;/code&gt; makes it easy to push &lt;code&gt;text/event-stream&lt;/code&gt; responses. The client side destroyed an afternoon.&lt;/p&gt;

&lt;p&gt;The symptom was simple. &lt;code&gt;is-connected&lt;/code&gt; stays &lt;code&gt;False&lt;/code&gt;. Forever. No error, no exception, no timeout message. Just... nothing happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hunt
&lt;/h2&gt;

&lt;p&gt;The maddening part about debugging concurrency issues is how many hypotheses you generate per hour.&lt;/p&gt;

&lt;p&gt;The SSE client needed to: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;open a GET to &lt;code&gt;/sse&lt;/code&gt;, &lt;/li&gt;
&lt;li&gt;parse the &lt;code&gt;event: endpoint&lt;/code&gt; line to learn the POST URL, &lt;/li&gt;
&lt;li&gt;report itself as connected. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 1 wasn't completing. Or was it? Hard to tell when your debug prints don't appear either.&lt;/p&gt;

&lt;p&gt;Here's what we tried, in roughly this order:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;start { await $client.get(...) }&lt;/code&gt; — GET takes 5–10 seconds to resolve&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;$client.get(...).then(-&amp;gt; $p { ... })&lt;/code&gt; — &lt;code&gt;.then&lt;/code&gt; callback also delayed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;react { whenever $resp.body-byte-stream }&lt;/code&gt; — &lt;code&gt;whenever&lt;/code&gt; doesn't fire&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Supply.tap(...)&lt;/code&gt; — tap callback delayed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RAKUDO_MAX_THREADS=128&lt;/code&gt; — no help&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each approach had the same shape: code that works fine in isolation, fails when a &lt;code&gt;Cro&lt;/code&gt; HTTP server is running in the same process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 1: Thread pool starvation
&lt;/h2&gt;

&lt;p&gt;Raku's &lt;code&gt;start&lt;/code&gt; blocks, &lt;code&gt;.then&lt;/code&gt; callbacks, and &lt;code&gt;react/whenever&lt;/code&gt; all share a single &lt;code&gt;ThreadPoolScheduler&lt;/code&gt;. &lt;code&gt;Cro&lt;/code&gt; uses these same primitives. When a &lt;code&gt;Cro&lt;/code&gt; server holds open long-lived SSE streams — which are &lt;code&gt;Supply&lt;/code&gt; pipelines sitting in &lt;code&gt;whenever&lt;/code&gt; blocks — and a &lt;code&gt;Cro&lt;/code&gt; client in the same process needs scheduler slots to resolve its HTTP response pipeline, they compete for the same pool.&lt;/p&gt;

&lt;p&gt;Neither side is doing anything wrong. The starvation is emergent.&lt;/p&gt;

&lt;p&gt;Debug output told the story:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SSE-CLIENT: before get
connected=False
connected=False
connected=False
SSE-CLIENT: after get, status=200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The GET resolves. Just 10 seconds too late, after the test's polling loop has already given up.&lt;/p&gt;

&lt;p&gt;The fix: escape the shared pool entirely. &lt;code&gt;Thread.start&lt;/code&gt; creates a real OS thread outside Raku's scheduler. But there's a wrinkle — &lt;code&gt;await&lt;/code&gt; doesn't work inside &lt;code&gt;Thread.start&lt;/code&gt;. It silently returns &lt;code&gt;Nil&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;THREAD: entering sse-loop
SSE-LOOP: before get
THREAD: exited sse-loop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No error. No exception. &lt;code&gt;await&lt;/code&gt; just... doesn't block. The solution is &lt;code&gt;.result&lt;/code&gt;, which is a synchronous wait on a Promise and works correctly outside the scheduler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;method !connect-sse() {
    my $self := self;
    my $url  := $!url;

    Thread.start: {
        my $client = (require ::('Cro::HTTP::Client')).new;
        my $resp = $client.get($url,
            headers =&amp;gt; [Accept =&amp;gt; 'text/event-stream']).result;

        react whenever $resp.body-byte-stream -&amp;gt; $chunk {
            $self.handle-sse-chunk($chunk);
        }

        CATCH { default {} }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connection established. Data flowing. Chunks arriving. And &lt;code&gt;is-connected&lt;/code&gt; stays &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug 2: The invisible space
&lt;/h2&gt;

&lt;p&gt;Same symptom. Completely different cause.&lt;/p&gt;

&lt;p&gt;The SSE parser receives &lt;code&gt;"event: endpoint\ndata: http://...\n\n"&lt;/code&gt;. It splits each line on &lt;code&gt;:&lt;/code&gt;, getting field &lt;code&gt;"event"&lt;/code&gt; and value &lt;code&gt;" endpoint"&lt;/code&gt;. Per the SSE spec, a single leading space after the colon should be stripped. The code did this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$value = $value.subst(/^ /, '') if $value.defined;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks right. Does nothing.&lt;/p&gt;

&lt;p&gt;In Raku regexes, whitespace is insignificant by default. The regex &lt;code&gt;/^ /&lt;/code&gt; means "anchor to start of string." The space is formatting — syntactic sugar for readability of complex patterns. It is not a literal space character. So &lt;code&gt;subst&lt;/code&gt; matches a zero-width position at index 0, replaces nothing, and returns the original string unchanged.&lt;/p&gt;

&lt;p&gt;The event type becomes &lt;code&gt;" endpoint"&lt;/code&gt; (with a leading space). The check &lt;code&gt;$!sse-event-type eq 'endpoint'&lt;/code&gt; fails. The POST endpoint is never set. &lt;code&gt;is-connected&lt;/code&gt; stays &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Debug output, once we added it in the right place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HANDLE-CHUNK: empty line, event-type=[ endpoint] data=[ http://127.0.0.1:39652/message]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That leading space in &lt;code&gt;[ endpoint]&lt;/code&gt; is the entire bug. The fix avoids regex entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$value = $value.substr(1) if $value.defined &amp;amp;&amp;amp; $value.starts-with(' ');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why the combination was brutal
&lt;/h2&gt;

&lt;p&gt;Bug 1 prevented data from arriving in time, so bug 2 was invisible. You can't debug a parser that never receives input.&lt;/p&gt;

&lt;p&gt;Once bug 1 was fixed, the symptom was identical: &lt;code&gt;is-connected&lt;/code&gt; stays &lt;code&gt;False&lt;/code&gt;. No error. No exception. Same silent wrongness, completely unrelated root cause. There was no moment where one bug was fixed and the system partially worked — it went from "broken for reason A" to "broken for reason B" with no observable change in behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reflections
&lt;/h2&gt;

&lt;p&gt;The regex design choice is defensible. When you write complex patterns — and Raku grammars get genuinely complex — insignificant whitespace is a gift:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/ &amp;lt;ident&amp;gt; \s* '=' \s* &amp;lt;value&amp;gt; /
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is easier to read than:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/\w+\s*=\s*\S+/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But &lt;code&gt;/ ^ /&lt;/code&gt; silently meaning something different from every other regex flavor on earth is a trap. It doesn't warn. It doesn't fail. It matches, successfully, matching nothing. A language where &lt;code&gt;/foo bar/&lt;/code&gt; doesn't match &lt;code&gt;"foo bar"&lt;/code&gt; has an onboarding cost, and that cost is paid in debugging sessions like this one.&lt;/p&gt;

&lt;p&gt;The thread pool issue is subtler and arguably more interesting. It's not a bug in Raku or &lt;code&gt;Cro&lt;/code&gt;. It's an emergent property of running a server and client in the same process when both rely on cooperative scheduling. The kind of thing that works fine in production (separate processes) and fails in tests (same process). The fix — &lt;code&gt;Thread.start&lt;/code&gt; with &lt;code&gt;.result&lt;/code&gt; instead of &lt;code&gt;start&lt;/code&gt; with &lt;code&gt;await&lt;/code&gt; — is the sort of incantation you'd never guess from the documentation.&lt;/p&gt;




&lt;p&gt;Thanks to &lt;a class="mentioned-user" href="https://dev.to/lizmat"&gt;@lizmat&lt;/a&gt; for motivating this post.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>networking</category>
      <category>rakulang</category>
    </item>
  </channel>
</rss>
