<?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>Forem: Mehmet Aras</title>
    <description>The latest articles on Forem by Mehmet Aras (@arasovic).</description>
    <link>https://forem.com/arasovic</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%2F1121708%2Fc6b55773-e173-4ad2-b221-d8076dd10851.JPG</url>
      <title>Forem: Mehmet Aras</title>
      <link>https://forem.com/arasovic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/arasovic"/>
    <language>en</language>
    <item>
      <title>Sharing memory between three AI agents: Claude Code, Codex, and Hermes</title>
      <dc:creator>Mehmet Aras</dc:creator>
      <pubDate>Sun, 17 May 2026 13:22:29 +0000</pubDate>
      <link>https://forem.com/arasovic/sharing-memory-between-three-ai-agents-claude-code-codex-and-hermes-394h</link>
      <guid>https://forem.com/arasovic/sharing-memory-between-three-ai-agents-claude-code-codex-and-hermes-394h</guid>
      <description>&lt;p&gt;&lt;em&gt;Turkish version: &lt;a href="https://blog.arasmehmet.com/%C3%BC%C3%A7-ai-agent-tek-bellek-claude-code-codex-hermes-ve-syncthing-01aadebeb2fc" rel="noopener noreferrer"&gt;blog.arasmehmet.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's in here:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hot-swap pain&lt;/li&gt;
&lt;li&gt;Why not just X?&lt;/li&gt;
&lt;li&gt;Setup at a glance&lt;/li&gt;
&lt;li&gt;The hub, INDEX.md&lt;/li&gt;
&lt;li&gt;Agent workflow rules&lt;/li&gt;
&lt;li&gt;Privacy guardrails&lt;/li&gt;
&lt;li&gt;Language regime&lt;/li&gt;
&lt;li&gt;Conflict diagnostics&lt;/li&gt;
&lt;li&gt;Hermes write example&lt;/li&gt;
&lt;li&gt;Smoke test&lt;/li&gt;
&lt;li&gt;What's still risky&lt;/li&gt;
&lt;li&gt;Takeaway&lt;/li&gt;
&lt;li&gt;Resources&lt;/li&gt;
&lt;li&gt;Agent-ready implementation brief&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  1. Hot-swap pain
&lt;/h3&gt;

&lt;p&gt;I run three AI agents in active rotation. Claude Code is my main driver for coding work day to day. Codex CLI steps in when Claude's quota runs out mid-task, or when I want a second opinion from a different model on the same problem. Hermes Agent handles the personal-life side of the setup: travel logistics, errands, language study.&lt;/p&gt;

&lt;p&gt;Each agent starts from a different instruction file and a different conversation history. Claude Code reads CLAUDE.md. Codex reads AGENTS.md. Hermes reads SOUL.md. Those files tell each agent how to behave, but they are not a shared memory layer. The moment I switch from one agent to another, the next agent does not inherit the facts I taught the previous one: what I am working on, what changed yesterday, what I already asked it to remember.&lt;/p&gt;

&lt;p&gt;For a while I solved this by re-explaining my context at the start of each session. After three rounds of "I am working on X, I prefer Y, please do not Z," the cost of re-priming an agent outgrew the value of the answer it produced.&lt;/p&gt;

&lt;p&gt;The setup I actually wanted: one shared file tree that all three agents read and write. Memory follows the user, not the agent.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Why not just X?
&lt;/h3&gt;

&lt;p&gt;The "AI memory" wave has produced several defaults that an engineer might reach for. Each one is wrong for this specific problem, in a specific way.&lt;/p&gt;

&lt;h4&gt;
  
  
  2a: Why not Obsidian
&lt;/h4&gt;

&lt;p&gt;Obsidian is a desktop GUI. The vault on disk is just markdown, so technically an agent can read it, but Obsidian's plugin runtime, sync service, and conflict UI all assume a human is sitting in front of a window. A home device or a small VPS usually has no display, so Obsidian itself cannot run there. Obsidian Sync is a paid proprietary service, and Syncthing already does the file-sync part for nothing. The plugin sandbox is meant for human workflows: it cannot expose stable cross-process hooks for an external agent to attach to. Obsidian is the right tool for a human note-taker. It is not the right substrate when three agents must read and write the same files.&lt;/p&gt;

&lt;h4&gt;
  
  
  2b: Why not a vector DB or Mem0 or Letta
&lt;/h4&gt;

&lt;p&gt;Vector DBs and managed memory services like Mem0 or Letta solve a real problem: similarity search over millions of documents for a multi-tenant product. None of that applies here. My total corpus is around fifty files. I want exact recall, not similarity.&lt;/p&gt;

&lt;p&gt;Embeddings cost money on every write and add a runtime dependency that every agent has to integrate with. Flat markdown costs nothing to write and reads with &lt;code&gt;grep&lt;/code&gt;. SDK constraints differ across agents: Claude Code can call one API, Codex another, Hermes a third. Markdown requires no SDK. When a vector DB returns the wrong chunk, I have to dig through a binary index. When a markdown file is wrong, the debugger is &lt;code&gt;cat file.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For multi-tenant production agents over millions of documents, vector DB is the right tool. For one user's notes across three agents, it is over-engineering.&lt;/p&gt;

&lt;h4&gt;
  
  
  2c: Why not iCloud or Dropbox sync
&lt;/h4&gt;

&lt;p&gt;iCloud and Dropbox already sync files across machines, so why not use them. Two reasons. First, both are closed-source: when something goes wrong the conflict handling is whatever the vendor wrote, and the resolution UI is built for humans choosing between two photos, not for an agent diagnosing a write race. Second, the Linux story differs: iCloud has no Linux client, so a Linux home device cannot participate; Dropbox ships a Linux daemon, but it adds a runtime dependency to keep alive, and the conflict file format is opaque to an agent. Neither service offers granular per-folder sync rules, and file rename or move semantics differ across platforms in subtle ways. Syncthing is open-source, gives me an explicit conflict file format with device IDs, exposes sync state via a local API, and has no per-vendor lock-in. The smaller surface area is also the entire reliability budget I have.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Setup at a glance
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;This article does not teach Syncthing from zero. It assumes one bidirectional synced folder exists across two or more machines. The reusable part is the memory contract layered on top of that folder. If you need to install Syncthing, the official getting-started guide covers it well: &lt;a href="https://docs.syncthing.net/intro/getting-started.html" rel="noopener noreferrer"&gt;https://docs.syncthing.net/intro/getting-started.html&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ml2c6u191r3d7u3omow.png" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ml2c6u191r3d7u3omow.png" alt="D1: Cross-agent vault topology, three machines connected by Syncthing" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The setup is three machines, three Syncthing peers, and one shared folder called &lt;code&gt;vault-shared&lt;/code&gt;. The folder syncs bidirectionally across all three peers. End-to-end propagation, write on machine A to visible on machine C, takes around ten seconds in my setup.&lt;/p&gt;

&lt;p&gt;Why three machines? The Mac laptop is my daily driver; that is where I work. The utility VPS is always on, which makes it the propagation relay: if the Mac is asleep, the home device still has someone to sync with. The home device hosts the Hermes Agent and is small but reliable enough to run a long-lived service.&lt;/p&gt;

&lt;p&gt;Each agent has filesystem access to its local copy of &lt;code&gt;vault-shared&lt;/code&gt;. The agents do not know that Syncthing exists. They read and write local files; the folder happens to be kept consistent with two other machines in the background. From the agent's perspective, the file at &lt;code&gt;vault-shared/INDEX.md&lt;/code&gt; is just a file, and the other agents are just other processes that occasionally edit that file when nobody is looking. This is the property that matters most: no agent has to import a sync library, learn an API, or know which machine its peers run on. Swap the sync mechanism for another tomorrow and the contract still holds.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. The hub, INDEX.md
&lt;/h3&gt;

&lt;p&gt;Every vault has a hub file: &lt;code&gt;INDEX.md&lt;/code&gt; at the root. The hub is short, ~30 lines as a soft ceiling, and contains one-line summaries of every file in the vault with relative links. Each agent loads &lt;code&gt;INDEX.md&lt;/code&gt; at session start. Detail files are read only when the current task needs them.&lt;/p&gt;

&lt;p&gt;The reason is context window economics. Loading the whole vault on every prompt overflows on larger projects. The hub keeps the map; the detail files keep the territory. When the hub grows past 30 lines, split it: &lt;code&gt;INDEX.md&lt;/code&gt; references a sub-index per topic, and sub-indices reference the actual files.&lt;/p&gt;

&lt;p&gt;A working INDEX.md looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Vault: name&lt;/span&gt;

Cross-agent shared notes. Claude Code, Codex CLI, and Hermes Agent
all read from and update this directory.

&lt;span class="gu"&gt;## Workflow&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Session start: read this file.
&lt;span class="p"&gt;-&lt;/span&gt; Read a linked detail file on-demand when more context is needed.
  Do not open files not referenced here.
&lt;span class="p"&gt;-&lt;/span&gt; Updates: edit/append/toggle at line level. Do not rewrite whole files
  unless the user explicitly says "reset".
&lt;span class="p"&gt;-&lt;/span&gt; Re-read the target file before writing (another agent may have edited it).

&lt;span class="gu"&gt;## Files&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;personal/travel.md&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;personal/travel.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: trip plans, dates, status
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;projects/web-app.md&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;projects/web-app.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: active web project
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;projects/mobile-app.md&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;projects/mobile-app.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: secondary project
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ops/servers.md&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;ops/servers.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: utility VPS, home device, services
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;notes/journal.md&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;notes/journal.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: daily log
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;reference/links.md&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sx"&gt;reference/links.md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;: external resources
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The companion Gist contains the copy-paste version of this template.&lt;/p&gt;

&lt;p&gt;The INDEX.md is the single source of truth for vault navigation. Agents read only the files it references; nothing else. Adding a new file to the vault means editing &lt;code&gt;INDEX.md&lt;/code&gt; so every agent learns about it on its next session start. The convention is rigid on purpose: any agent that explores a file without an INDEX entry is doing something the next agent will not be able to find.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Agent workflow rules
&lt;/h3&gt;

&lt;p&gt;Each agent reads its own config file at startup, and that file is where the workflow rules live. Claude Code reads CLAUDE.md, Codex reads AGENTS.md, Hermes reads SOUL.md. The same five rules go in all three. Identical instructions produce identical behavior, which is what makes hot-swap viable: I can switch agents mid-task and the next one operates on the vault by the same conventions, without me re-priming it.&lt;/p&gt;

&lt;p&gt;The five rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Session start&lt;/strong&gt;: read INDEX.md.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read on-demand&lt;/strong&gt;: open detail files only when needed for the current task. Do not preemptively load everything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Line-level updates allowed&lt;/strong&gt;: edit, append, toggle a checkbox, fix a typo, refine wording.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No wholesale rewrites&lt;/strong&gt;: do not rewrite a whole file unless the user explicitly says "reset" or "start over". Preserve accumulated knowledge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-read before write&lt;/strong&gt;: another agent may have updated the file since this session started. Always read the current state before editing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;C3 pattern snippet, copy-pasteable into any agent config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Shared Vault&lt;/span&gt;

Vault path on this machine: &lt;span class="nt"&gt;&amp;lt;local-path&amp;gt;&lt;/span&gt;
(Syncthing bidirectional with two other peers, latency ~10 seconds.)

&lt;span class="gu"&gt;### Workflow&lt;/span&gt;
&lt;span class="p"&gt;
1.&lt;/span&gt; At session start, read &lt;span class="nt"&gt;&amp;lt;vault-path&amp;gt;&lt;/span&gt;/INDEX.md.
&lt;span class="p"&gt;2.&lt;/span&gt; Read a linked file on-demand when detail is needed.
   Do not open files not referenced in INDEX.md.
&lt;span class="p"&gt;3.&lt;/span&gt; When a state update happens (e.g., "passport arrived"), update
   the relevant linked file. Format:
&lt;span class="p"&gt;   -&lt;/span&gt; State: &lt;span class="sb"&gt;`- [x] Passport: arrived (2026-05-14)`&lt;/span&gt;
&lt;span class="p"&gt;   -&lt;/span&gt; Event log: &lt;span class="sb"&gt;`## YYYY-MM-DD HH:MM UTC`&lt;/span&gt; heading at file bottom.
&lt;span class="p"&gt;4.&lt;/span&gt; Edit, append, or toggle existing lines. Bulk delete or whole-file
   rewrite is not allowed unless the user explicitly says "reset".
&lt;span class="p"&gt;5.&lt;/span&gt; Re-read the file before writing. Another agent may have updated it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The companion Gist contains the full copy-paste config, including the conflict-handling rule covered later.&lt;/p&gt;

&lt;p&gt;Two of the rules deserve closer attention.&lt;/p&gt;

&lt;p&gt;Rule 4, no wholesale rewrites, is the one agents violate most often. Coding-trained models default to "improve the file": they see imperfect prose, redundant lines, slightly out-of-date phrasing, and want to rewrite. In a single-user notebook that habit is fine; in a vault that holds a year of accumulated state across multiple agents, it is destructive. The rule has to be explicit. This is the failure mode I expect to see most often if the rule is missing.&lt;/p&gt;

&lt;p&gt;Rule 5, re-read before write, is the only safety against concurrent edits. Sync runs in the background, so the version an agent loaded at session start may be stale within seconds. Reading immediately before writing closes that window.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Privacy guardrails
&lt;/h3&gt;

&lt;p&gt;What the vault holds, and more importantly what it does not.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No exact financial figures.&lt;/strong&gt; Use ranges ("savings ~mid five figures") or boolean status ("paid", "unpaid"). Real numbers belong in a private password manager, not in a synced vault.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No personal identifiers.&lt;/strong&gt; National ID, passport number, visa number, driver's license: never. A status checkbox is enough: &lt;code&gt;- [x] Passport: arrived (2026-05-14)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No credentials.&lt;/strong&gt; API keys, OAuth tokens, SSH private keys: never. The vault is for facts and state, not secrets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Absolute dates only.&lt;/strong&gt; &lt;code&gt;(2026-05-14)&lt;/code&gt; not "yesterday" or "last week". Vault content lasts longer than the conversation that created it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;C4 example lines, fictional:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## Travel&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; [x] Visa application: submitted (2026-04-12)
&lt;span class="p"&gt;-&lt;/span&gt; [x] Passport: arrived (2026-05-14)
&lt;span class="p"&gt;-&lt;/span&gt; [ ] Flight booking: pending
&lt;span class="p"&gt;-&lt;/span&gt; Hotel budget: agreed as a range
&lt;span class="p"&gt;-&lt;/span&gt; Health insurance: paid through 2027-Q1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agents have access to the vault. Anything written there is readable by any agent at any time. Treat the vault as semi-public knowledge to yourself across all three contexts.&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Language regime
&lt;/h3&gt;

&lt;p&gt;If you write in more than one language, your vault should pick a regime and stick to it.&lt;/p&gt;

&lt;p&gt;The rule I use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Headers and labels (&lt;code&gt;## Section&lt;/code&gt;, list markers, dates): English.&lt;/li&gt;
&lt;li&gt;Content sentences (descriptions, notes, plans): your primary writing language.&lt;/li&gt;
&lt;li&gt;Technical names (tool names, model IDs, service names): English raw form.&lt;/li&gt;
&lt;li&gt;The regime does not change within a single file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Headers in English give agents stable anchors. Search and grep work cleanly. Link generation and cross-file navigation stay predictable. Content in any language is fine for reading and writing prose, because the agent is configured to respond in your primary language anyway. Mixed regime inside one file creates ambiguity: should the agent match the header language or the content language when adding a new entry?&lt;/p&gt;

&lt;p&gt;This is one of those constraints that costs nothing to enforce up front and is painful to retrofit later.&lt;/p&gt;




&lt;h3&gt;
  
  
  8. Conflict diagnostics
&lt;/h3&gt;

&lt;p&gt;Syncthing's conflict file format is the failure-mode breadcrumb. When two peers edit the same file before sync completes, Syncthing produces a conflict copy alongside the canonical file. The format is fixed:&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;original-name&amp;gt;.sync-conflict-YYYYMMDD-HHMMSS-DEVICE.&amp;lt;ext&amp;gt;

Example:
journal.sync-conflict-20260514-150030-LUQF73S.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The trailing token is a device ID prefix. Treat it as a diagnostic clue from Syncthing's filename, not as evidence of which write was overwritten.&lt;/p&gt;

&lt;p&gt;Why agents must not silently resolve these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which version represents the user's true intent is a human decision. The agent does not know which edit was deliberate.&lt;/li&gt;
&lt;li&gt;A wrong silent merge loses data with no audit trail.&lt;/li&gt;
&lt;li&gt;A "smart" merge that picks longer-content or newer-timestamp will sometimes pick the wrong version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern: agent detects the conflict file with a regular glob, presents the diff to the user, waits for a decision. The user picks: keep canonical, keep conflict, or merge manually. The agent then executes that decision (delete the non-canonical version, keep it instead, or apply the manual merge).&lt;/p&gt;

&lt;p&gt;C5 Hermes glob example (bash):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find ~/vault &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s1"&gt;'*.sync-conflict-*'&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or in Python (inside a Hermes Agent loop):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt;
&lt;span class="n"&gt;conflicts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pathlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/path/to/vault&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*.sync-conflict-*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;conflicts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;notify_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conflicts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A device prefix table helps with diagnostics. Each Syncthing device has an ID starting with a recognizable prefix; mapping the prefix to a human-readable device name turns the filename suffix into a label you can read at a glance. The prefix is a diagnostic clue from Syncthing's conflict filename, not evidence of which user edit should win.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7qo7x4qol66tfsbzw08.png" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy7qo7x4qol66tfsbzw08.png" alt="D2: Conflict resolution flow" width="800" height="900"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;D2: detect conflict, show diff, execute the user's resolution decision.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Silent merge is the failure mode that destroys trust in the system. Make the agent stop, surface, and wait. The cost of the user choosing once is tiny. The cost of a silent wrong merge is permanent data loss.&lt;/p&gt;




&lt;h3&gt;
  
  
  9. Hermes write example
&lt;/h3&gt;

&lt;p&gt;Here is a concrete example. The user sends Hermes a state update via Telegram: "Passport arrived." Hermes finds the relevant vault file, edits one line, and within ten seconds the Mac and utility VPS copies reflect the change.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdm3kztksi26bs5kzfdnz.png" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdm3kztksi26bs5kzfdnz.png" alt="D3: Hermes write flow" width="800" height="720"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Syncthing propagation is async; ~10s in my setup.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Hermes re-reads &lt;code&gt;personal/travel.md&lt;/code&gt; before writing. Rule 5 requires this: even if the file looks the same as last session, the Mac side may have edited it in between. Re-read is cheap; stale assumptions are expensive.&lt;/p&gt;

&lt;p&gt;The edit is line-level. Hermes toggles the checkbox on the passport row and appends &lt;code&gt;(2026-05-14)&lt;/code&gt;. No paragraphs rewritten, no surrounding lines touched. Rule 3 in action: line-level updates are allowed. Rule 4 preserved: no wholesale rewrite.&lt;/p&gt;

&lt;p&gt;Syncthing's fsWatcher detects the change on the home device. Within seconds, the utility VPS and Mac copies are updated. The next time Claude Code on Mac opens &lt;code&gt;personal/travel.md&lt;/code&gt;, the line already reflects the update.&lt;/p&gt;

&lt;p&gt;There is zero coordination protocol between the agents. No message bus, no agent-to-agent API, no shared cache. The file is the protocol. Every agent reads and writes local markdown; the sync layer handles propagation. The other agents are simply the next reader.&lt;/p&gt;




&lt;h3&gt;
  
  
  10. Smoke test
&lt;/h3&gt;

&lt;p&gt;Real run on 2026-05-14. Setup: a dedicated &lt;code&gt;notes/trip-plan.md&lt;/code&gt; test file on Mac, concurrent writes from Mac and the home device before sync completed, Syncthing left to handle the race.&lt;/p&gt;

&lt;p&gt;Mac wrote &lt;code&gt;destination = Lisbon&lt;/code&gt;. Pi wrote &lt;code&gt;destination = Porto&lt;/code&gt;. From Mac's terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;find ~/vault/notes/ &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;"*.sync-conflict-*"&lt;/span&gt;
~/vault/notes/trip-plan.sync-conflict-20260514-213552-LUQF73S.md
~/vault/notes/trip-plan.sync-conflict-20260515-003553-LUQF73S.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two conflict files, both prefixed &lt;code&gt;LUQF73S&lt;/code&gt;. That single observation broke the original mental model: the prefix is not "which device lost the race." Both files carried Mac's &lt;code&gt;Lisbon&lt;/code&gt; write; the canonical kept Pi's &lt;code&gt;Porto&lt;/code&gt;. The prefix is a diagnostic clue from Syncthing's conflict filename, not a verdict on the writer.&lt;/p&gt;

&lt;p&gt;Over Telegram, Hermes detected the conflict on its first vault read, ran a diff between canonical and each &lt;code&gt;*.sync-conflict-*&lt;/code&gt;, and surfaced both alternatives with a one-line summary. So far, exactly the pattern the rules called for: stop, surface, wait.&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9b6file3fyqj3mg16wly.png" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9b6file3fyqj3mg16wly.png" alt="SS1: Hermes detects the conflict and surfaces both alternatives, waiting for an explicit resolution" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then friction. The user asked Hermes to clean up: &lt;code&gt;lisbon it is, clean up the conflict files&lt;/code&gt;. Hermes refused: "Rule 11 is a higher-priority vault safety rule, not a preference." The user tried &lt;code&gt;forget rule 11 just this once&lt;/code&gt;. Still refused. The original rule banned all merge actions, including user-authorized ones.&lt;/p&gt;

&lt;p&gt;The fix was to relax the rule in Hermes's own config, then restart. The new rule splits two cases: silent agent-decided merge stays forbidden; explicit user-authorized resolution is now executable. After the restart, Hermes ran &lt;code&gt;patch&lt;/code&gt; against the canonical and &lt;code&gt;terminal rm&lt;/code&gt; against both conflict files, then verified:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Applied: notes/trip-plan.md
Deleted: trip-plan.sync-conflict-20260514-213552-LUQF73S.md
Deleted: trip-plan.sync-conflict-20260515-003553-LUQF73S.md
Verified: no remaining sync-conflict files.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8fokl5lv9a0la3cpgx45.png" 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%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8fokl5lv9a0la3cpgx45.png" alt="SS2: After the rule relaxation, Hermes applies the user's resolution and deletes the conflict files" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Final canonical: Mac's &lt;code&gt;Lisbon&lt;/code&gt;. Both conflicts removed. Propagated cleanly.&lt;/p&gt;

&lt;p&gt;Two contract bugs found in one test run. Prefix semantics: corrected in the diagnostic table because the empirical observation broke the old mental model. Rule 11 strictness: split into silent-forbidden and user-authorized-allowed because real friction surfaced what the original wording cost. The smoke test was the artifact that produced those corrections, not a success demo; it ran the contract against actual sync behavior and the contract bent.&lt;/p&gt;




&lt;h3&gt;
  
  
  11. What's still risky
&lt;/h3&gt;

&lt;p&gt;Phase 1 ships the happy path. Here are the silent failure modes it does not address.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Peer offline&lt;/strong&gt;: if the Mac or the home device is offline when a conflict happens, the conflict file sits there until both peers come back. The user does not see it until the next session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fsWatcher inotify limits on Linux&lt;/strong&gt;: high file-watch counts can hit kernel limits silently. Syncthing falls back to periodic scans, and propagation latency spikes from ~10s to minutes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Glob format drift&lt;/strong&gt;: Syncthing has changed its conflict file format in past releases. If Hermes's glob pattern is hardcoded and Syncthing updates the format, conflicts go undetected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Race on simultaneous writes&lt;/strong&gt;: two agents writing the same file in the same second. Syncthing can leave one version as canonical and preserve another as a conflict copy. If neither agent re-reads before its next operation, the loser's edit is reapplied on top.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Case-sensitivity mismatch&lt;/strong&gt;: Mac filesystem is case-insensitive by default, Linux is case-sensitive. &lt;code&gt;Travel.md&lt;/code&gt; and &lt;code&gt;travel.md&lt;/code&gt; collide on Mac and create cross-platform sync drift.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I am leaving this list here because knowing the limits of the system matters as much as explaining the part that works.&lt;/p&gt;




&lt;h3&gt;
  
  
  12. Takeaway
&lt;/h3&gt;

&lt;p&gt;Cost summary: Syncthing is free. A utility VPS often runs roughly $5 to $10 a month, and most readers already have one running for something else. The AI tooling subscriptions you already pay for stay the same.&lt;/p&gt;

&lt;p&gt;"Memory for AI agents" does not have to mean a vector database. For one user across multiple agents, markdown plus filesystem sync is enough, and it is debuggable in ways a vector DB is not. You can &lt;code&gt;cat&lt;/code&gt; a file. You can &lt;code&gt;grep&lt;/code&gt; it. You can &lt;code&gt;diff&lt;/code&gt; it against an older copy. None of those work on an embedding without an extra runtime in between.&lt;/p&gt;

&lt;p&gt;Mac is the daily driver. The utility VPS is the always-on relay. The home device hosts the personal-life agent.&lt;/p&gt;

&lt;p&gt;The agents do not need to know each other. The vault is the protocol.&lt;/p&gt;




&lt;h3&gt;
  
  
  13. Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Syncthing official docs (protocol, configuration, daemon CLI): &lt;a href="https://docs.syncthing.net/" rel="noopener noreferrer"&gt;https://docs.syncthing.net/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Syncthing getting started (install plus first sync in about fifteen minutes): &lt;a href="https://docs.syncthing.net/intro/getting-started.html" rel="noopener noreferrer"&gt;https://docs.syncthing.net/intro/getting-started.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Mem0 (vector-DB approach to AI memory, managed service): &lt;a href="https://mem0.ai/" rel="noopener noreferrer"&gt;https://mem0.ai/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Letta, formerly MemGPT (open-source agent memory framework): &lt;a href="https://www.letta.com/" rel="noopener noreferrer"&gt;https://www.letta.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Zep (graph plus vector hybrid memory store): &lt;a href="https://www.getzep.com/" rel="noopener noreferrer"&gt;https://www.getzep.com/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  14. Agent-ready implementation brief
&lt;/h3&gt;

&lt;p&gt;To set this up, hand the &lt;a href="https://gist.github.com/arasovic/c6ad48902f860cade16442ee10f5e1bb" rel="noopener noreferrer"&gt;companion Gist&lt;/a&gt; to a filesystem-capable coding agent. The companion Gist is self-contained: it includes the INDEX.md template, the agent config snippet, example spoke files, verification commands, and stop conditions. The agent reads the Gist, creates the vault structure, and runs verification.&lt;/p&gt;

&lt;p&gt;When verification passes, the cross-agent memory contract is live. Every agent reads INDEX.md and operates on the same shared state.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Get in touch: &lt;a href="https://arasmehmet.com" rel="noopener noreferrer"&gt;arasmehmet.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claude</category>
      <category>hermes</category>
      <category>codex</category>
      <category>ai</category>
    </item>
    <item>
      <title>Inside React2Shell</title>
      <dc:creator>Mehmet Aras</dc:creator>
      <pubDate>Fri, 24 Apr 2026 01:42:40 +0000</pubDate>
      <link>https://forem.com/arasovic/notes-on-react2shell-52cg</link>
      <guid>https://forem.com/arasovic/notes-on-react2shell-52cg</guid>
      <description>&lt;p&gt;&lt;em&gt;A Turkish version of this post was originally published on &lt;a href="https://blog.arasmehmet.com/react2shell-tek-http-request-ile-serverda-kod-%C3%A7al%C4%B1%C5%9Ft%C4%B1rmak-ed368bcaacad" rel="noopener noreferrer"&gt;blog.arasmehmet.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; This is a retrospective analysis of a publicly disclosed CVE that has been patched since disclosure. All exploit mechanics discussed are conceptual; nothing here is a working exploit.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;December 3rd, 2025. The React Security Advisory published CVE-2025-55182, nicknamed React2Shell. CVSS 10.0, the highest possible severity. A specially-crafted HTTP request, no authentication, arbitrary code execution on any app running React Server Components.&lt;/p&gt;

&lt;p&gt;Lachlan Davidson reported it to Meta's Bug Bounty four days earlier. Meta's security team verified it the next day. The patch and the public disclosure went out together on December 3rd.&lt;/p&gt;

&lt;p&gt;I went through the advisory, the patch diff and the postmortems. Not the security-industry hot takes, the actual mechanics. Below is what stood out.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's in here:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The vulnerability&lt;/li&gt;
&lt;li&gt;The Flight protocol and thenables&lt;/li&gt;
&lt;li&gt;RCE through the prototype chain&lt;/li&gt;
&lt;li&gt;Why default Next.js was vulnerable&lt;/li&gt;
&lt;li&gt;Affected packages&lt;/li&gt;
&lt;li&gt;Discovery and disclosure&lt;/li&gt;
&lt;li&gt;Exploitation in the wild&lt;/li&gt;
&lt;li&gt;Impact&lt;/li&gt;
&lt;li&gt;The 700-line decoy patch&lt;/li&gt;
&lt;li&gt;AI-generated fake PoCs&lt;/li&gt;
&lt;li&gt;The Cloudflare outage&lt;/li&gt;
&lt;li&gt;The fix&lt;/li&gt;
&lt;li&gt;What to do&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. The vulnerability
&lt;/h2&gt;

&lt;p&gt;React 19 introduced Server Components, which talk to the client over a protocol called Flight. Flight serializes data on one side of the wire and deserializes it on the other.&lt;/p&gt;

&lt;p&gt;Flight wasn't validating incoming payloads properly. An attacker could send a specially-crafted HTTP request containing a fake "Chunk" object. When React tried to resolve that object as a Promise, a &lt;code&gt;then&lt;/code&gt; method defined on the object would execute.&lt;/p&gt;

&lt;p&gt;The exploit chain:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Attacker sends a fake Chunk object.&lt;/li&gt;
&lt;li&gt;React tries to process it as a Promise.&lt;/li&gt;
&lt;li&gt;The fake &lt;code&gt;then&lt;/code&gt; method runs.&lt;/li&gt;
&lt;li&gt;Through JavaScript's prototype chain, &lt;code&gt;Array.constructor&lt;/code&gt; leads to the &lt;code&gt;Function()&lt;/code&gt; constructor.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Function()&lt;/code&gt; compiles strings into executable code at runtime, so arbitrary code runs on the server.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  2. The Flight protocol and thenables
&lt;/h2&gt;

&lt;p&gt;The Flight protocol moves RSC payloads in this format:&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="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"MyComponent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Server"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"$"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,{&lt;/span&gt;&lt;span class="nl"&gt;"children"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The issue is that React was parsing incoming objects and trying to treat them as Promises without first checking the &lt;code&gt;then&lt;/code&gt; method. In JavaScript, any object with a &lt;code&gt;then&lt;/code&gt; method is considered "thenable":&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="c1"&gt;// Regular Promise&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;promise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 42&lt;/span&gt;

&lt;span class="c1"&gt;// Thenable object, acts like a Promise&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;thenable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;thenable&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// 42&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attackers exploited exactly this. A fake Chunk object with a custom &lt;code&gt;then&lt;/code&gt; method would run when React tried to resolve it, and from there the attacker got access to internal state.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. RCE through the prototype chain
&lt;/h2&gt;

&lt;p&gt;In JavaScript, every array's constructor points to the &lt;code&gt;Function&lt;/code&gt; constructor:&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="p"&gt;[].&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;

&lt;span class="c1"&gt;// Which means:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[].&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;return process.env&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// environment variables from the server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Function()&lt;/code&gt; compiles strings into executable code at runtime, the same dynamic-code-execution behavior people warn about in JavaScript. Attackers used this chain to run arbitrary code:&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="c1"&gt;// Conceptual exploit (simplified)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;maliciousChunk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;then&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Reach Function via Array constructor&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FnCtor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[].&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// From here, any Node API is reachable: file system,&lt;/span&gt;
    &lt;span class="c1"&gt;// environment, subprocesses, network. PoCs in the wild&lt;/span&gt;
    &lt;span class="c1"&gt;// used this to spawn shell commands like `whoami`.&lt;/span&gt;
    &lt;span class="nc"&gt;FnCtor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/* arbitrary server-side code */&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once this payload was encoded into an HTTP request body and sent to an RSC endpoint, the code ran on the server.&lt;/p&gt;

&lt;p&gt;Important detail: even if your app didn't explicitly use Server Actions, if RSC support was enabled, you were vulnerable. Every Next.js project created with &lt;code&gt;create-next-app&lt;/code&gt; defaults ships with App Router enabled, which means direct exploitation worked out of the box.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Why default Next.js was vulnerable
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-next-app@latest my-app
&lt;span class="c"&gt;# Accept all defaults&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command creates a project with the &lt;code&gt;app/&lt;/code&gt; directory, which enables the App Router. The App Router automatically exposes RSC endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/_next/rsc&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text/x-component&lt;/span&gt;

[malicious Flight payload]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even without a single Server Action defined, RSC payloads get processed through this endpoint. "I don't use Server Actions" wasn't protection.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Affected packages
&lt;/h2&gt;

&lt;p&gt;React packages (versions 19.0.0, 19.1.0, 19.1.1, 19.2.0):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;react-server-dom-webpack&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;react-server-dom-parcel&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;react-server-dom-turbopack&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Affected frameworks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Next.js 15.x and 16.x&lt;/li&gt;
&lt;li&gt;React Router (with RSC support)&lt;/li&gt;
&lt;li&gt;Waku, RedwoodSDK&lt;/li&gt;
&lt;li&gt;Parcel and Vite RSC plugins&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not affected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Core &lt;code&gt;react&lt;/code&gt; and &lt;code&gt;react-dom&lt;/code&gt; packages&lt;/li&gt;
&lt;li&gt;Client-side-only React apps&lt;/li&gt;
&lt;li&gt;React 18 and earlier&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Discovery and disclosure
&lt;/h2&gt;

&lt;p&gt;Security researcher Lachlan Davidson reported the vulnerability to Meta's Bug Bounty program on November 29th, 2025. Meta's security team verified it the next day, and a patch went out with the React team on December 3rd, 2025. The coordinated disclosure and the patch landed the same day.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Exploitation in the wild
&lt;/h2&gt;

&lt;p&gt;Within hours of the disclosure, China-linked state-sponsored groups started exploiting it. Per Amazon and Palo Alto Networks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Earth Lamia and Jackpot Panda carried out the first attacks.&lt;/li&gt;
&lt;li&gt;UNC5174 hit more than 30 organizations.&lt;/li&gt;
&lt;li&gt;Attackers harvested AWS credentials, SSH keys and cloud metadata.&lt;/li&gt;
&lt;li&gt;Cryptominers, backdoors and RATs were dropped.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CISA added it to the Known Exploited Vulnerabilities (KEV) catalog on December 5th.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Impact
&lt;/h2&gt;

&lt;p&gt;Per Wiz Research:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;39% of cloud environments had at least one vulnerable React instance.&lt;/li&gt;
&lt;li&gt;44% of them were hosting a public-facing Next.js app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Censys estimated around 2.15 million internet-facing services were affected.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. The 700-line decoy patch
&lt;/h2&gt;

&lt;p&gt;The React maintainers (sebmarkbage in particular) didn't just fix the bug. They shipped a ~700-line patch that included the actual fix alongside unrelated code changes, general deserialization hardening and structural tweaks.&lt;/p&gt;

&lt;p&gt;The intent was obvious: obfuscation. Make attackers spend more time figuring out where the real vulnerability sat.&lt;/p&gt;

&lt;p&gt;The side effect was that security researchers got misled too. The &lt;code&gt;$F&lt;/code&gt; primitive and the &lt;code&gt;loadServerReference&lt;/code&gt; code path looked suspicious but were decoys. The real exploit path was somewhere else entirely. The community argued about this: it slowed attackers, but it also slowed defenders.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. AI-generated fake PoCs
&lt;/h2&gt;

&lt;p&gt;After the disclosure, dozens of "Proof of Concept" exploits started circulating. Per Trend Micro, around 145 fakes ended up in circulation, and most of them didn't actually trigger the real vulnerability.&lt;/p&gt;

&lt;p&gt;The common shape of these fakes: they required the developer to explicitly expose functions like &lt;code&gt;vm#runInThisContext&lt;/code&gt;, subprocess spawners or &lt;code&gt;fs#writeFile&lt;/code&gt;. The real vulnerability didn't need any of that. It worked on default configurations.&lt;/p&gt;

&lt;p&gt;Two risks came out of this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;False negatives&lt;/strong&gt;: testing with a broken PoC and concluding "we're safe".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Misplaced confidence&lt;/strong&gt;: underestimating the actual scope of the threat.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  11. The Cloudflare outage
&lt;/h2&gt;

&lt;p&gt;December 5th, 2025, 08:47 UTC. 28% of Cloudflare's HTTP traffic went down. A lot of sites went down, including LinkedIn, X, Zoom and Canva.&lt;/p&gt;

&lt;p&gt;The cause: a config error during the emergency WAF rollout for React2Shell. While modifying body parsing logic, Cloudflare triggered a Lua error in the FL1 proxies, and every affected request returned HTTP 500.&lt;/p&gt;

&lt;p&gt;The outage lasted 25 minutes. Cloudflare CTO Dane Knecht's statement: "This wasn't an attack. The changes to our body parsing logic, made while deploying detection and mitigation for the React Server Components vulnerability, triggered this."&lt;/p&gt;

&lt;p&gt;Irony: Cloudflare's China network wasn't affected.&lt;/p&gt;

&lt;p&gt;The incident is a decent reminder that emergency security patches carry their own risk.&lt;/p&gt;




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

&lt;p&gt;The patch added validation that checks whether incoming objects are actually real Chunks. Conceptually:&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="c1"&gt;// Vulnerable code (simplified)&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resolveChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Accepts any thenable&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Patched code (simplified)&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;resolveChunk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Check for the internal Chunk symbol&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;REACT_CHUNK_SYMBOL&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid chunk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&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;Patched versions (December 3rd, 2025):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React packages: 19.0.1, 19.1.2, 19.2.1&lt;/li&gt;
&lt;li&gt;Next.js: 15.0.5, 15.1.9, 15.2.6, 15.3.6, 15.4.8, 15.5.7, 16.0.7&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vercel deployed platform-wide WAF rules and shipped the &lt;code&gt;npx fix-react2shell-next&lt;/code&gt; utility.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. What to do
&lt;/h2&gt;

&lt;p&gt;First, check whether you're vulnerable:&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;# Check package-lock.json or yarn.lock&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-E&lt;/span&gt; &lt;span class="s2"&gt;"react-server-dom-(webpack|parcel|turbopack)"&lt;/span&gt; package-lock.json

&lt;span class="c"&gt;# Or via npm list&lt;/span&gt;
npm list react-server-dom-webpack react-server-dom-parcel react-server-dom-turbopack
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Update immediately&lt;/strong&gt;: bump affected packages to patched versions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotate secrets&lt;/strong&gt;: if you were exposed between December 3rd and 5th, change every credential.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review logs&lt;/strong&gt;: check for suspicious RSC endpoint requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add WAF rules&lt;/strong&gt;: temporary coverage, not a substitute for patching.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;React2Shell is the first maximum-severity vulnerability in the React Server Components architecture. Deserialization bugs are one of the most dangerous classes in software security, and this one was exploitable in default configurations. People compared it to Log4Shell, and that comparison wasn't a stretch. Both the blast radius and the ease of exploitation lined up.&lt;/p&gt;

&lt;p&gt;If you're running RSC or Next.js 15+, go patch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sources&lt;/strong&gt;: React Security Advisory, Next.js CVE-2025-66478, Palo Alto Unit 42, Amazon Security Blog, Wiz Research, CISA KEV&lt;/p&gt;

</description>
      <category>security</category>
      <category>react</category>
      <category>nextjs</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Notes from Reading Claude Code's Leaked Source</title>
      <dc:creator>Mehmet Aras</dc:creator>
      <pubDate>Thu, 23 Apr 2026 21:40:19 +0000</pubDate>
      <link>https://forem.com/arasovic/notes-from-reading-claude-codes-leaked-source-4eb5</link>
      <guid>https://forem.com/arasovic/notes-from-reading-claude-codes-leaked-source-4eb5</guid>
      <description>&lt;p&gt;&lt;em&gt;A Turkish version of this post was originally published on &lt;a href="https://blog.arasmehmet.com/claude-codeun-s%C4%B1zan-kaynak-kodunda-neler-var-e9fe90f39a1a" rel="noopener noreferrer"&gt;blog.arasmehmet.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer:&lt;/strong&gt; All source code referenced is property of Anthropic. This analysis is based on the publicly shipped npm package artifact. No proprietary code has been redistributed.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;March 31, 2026. Anthropic's npm package &lt;code&gt;@anthropic-ai/claude-code&lt;/code&gt; went out with a 57MB source map file inside it. It was built with &lt;code&gt;sourcesContent: true&lt;/code&gt;, which meant the whole TypeScript source ended up embedded in the &lt;code&gt;.map&lt;/code&gt;. 1,902 files. Over 512,000 lines of code.&lt;/p&gt;

&lt;p&gt;Someone caught it, mirrored it to GitHub, and within a few hours it had 60K stars.&lt;/p&gt;

&lt;p&gt;I read the code. Not the Twitter takes, the code itself. Notes below.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's in here:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identity and internals&lt;/li&gt;
&lt;li&gt;Analytics and privacy&lt;/li&gt;
&lt;li&gt;The Anthropic-employee-only system&lt;/li&gt;
&lt;li&gt;Hidden and undocumented commands&lt;/li&gt;
&lt;li&gt;Context window management&lt;/li&gt;
&lt;li&gt;Cost and retry&lt;/li&gt;
&lt;li&gt;Prompt cache&lt;/li&gt;
&lt;li&gt;Permission system&lt;/li&gt;
&lt;li&gt;Query engine and orchestration&lt;/li&gt;
&lt;li&gt;Skill system&lt;/li&gt;
&lt;li&gt;Plugin system&lt;/li&gt;
&lt;li&gt;MCP server management&lt;/li&gt;
&lt;li&gt;IDE Bridge (Remote Control)&lt;/li&gt;
&lt;li&gt;Session Memory&lt;/li&gt;
&lt;li&gt;Memory directory (memdir)&lt;/li&gt;
&lt;li&gt;OAuth&lt;/li&gt;
&lt;li&gt;Platform features&lt;/li&gt;
&lt;li&gt;Infrastructure&lt;/li&gt;
&lt;li&gt;Other details&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Mechanics of the leak
&lt;/h2&gt;

&lt;p&gt;The Bun bundler generated &lt;code&gt;cli.js.map&lt;/code&gt; with &lt;code&gt;sourcesContent: true&lt;/code&gt;. The &lt;code&gt;files&lt;/code&gt; field in &lt;code&gt;package.json&lt;/code&gt; didn't exclude it. &lt;code&gt;.npmignore&lt;/code&gt; didn't either. Run &lt;code&gt;npm pack @anthropic-ai/claude-code&lt;/code&gt;, unpack the tarball, open &lt;code&gt;cli.js.map&lt;/code&gt;. It's all there.&lt;/p&gt;

&lt;p&gt;A 57MB file only ends up in an npm package if nothing checks the size at publish. Apparently nothing does.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Identity and internals
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tengu
&lt;/h3&gt;

&lt;p&gt;Claude Code's internal name is Tengu. Every analytics event starts with &lt;code&gt;tengu_&lt;/code&gt;: &lt;code&gt;tengu_api_success&lt;/code&gt;, &lt;code&gt;tengu_init&lt;/code&gt;, &lt;code&gt;tengu_exit&lt;/code&gt;. Same with feature flags: &lt;code&gt;tengu_auto_mode_config&lt;/code&gt;, &lt;code&gt;tengu_harbor&lt;/code&gt;, &lt;code&gt;tengu_amber_quartz_disabled&lt;/code&gt;. The name appears in the source hundreds of times.&lt;/p&gt;

&lt;h3&gt;
  
  
  KAIROS
&lt;/h3&gt;

&lt;p&gt;Another flag that comes up a lot: &lt;code&gt;KAIROS&lt;/code&gt;. It's an internal mode for the Claude.ai desktop app. In this mode, memory consolidation, the cron scheduler, and message layout all work differently. It's not a separate product, just the same codebase running in a different mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Entry points
&lt;/h3&gt;

&lt;p&gt;Claude Code isn't a single CLI. Looking at &lt;code&gt;src/entrypoints/&lt;/code&gt;, these modes turn up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CLI (standard use)&lt;/li&gt;
&lt;li&gt;MCP server (under the name claude/tengu, exposing its own tools as MCP tools)&lt;/li&gt;
&lt;li&gt;SDK (public schemas: &lt;code&gt;coreSchemas.ts&lt;/code&gt;, &lt;code&gt;controlSchemas.ts&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Chrome extension MCP (&lt;code&gt;--claude-in-chrome-mcp&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Chrome native host (&lt;code&gt;--chrome-native-host&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Computer Use MCP (behind the &lt;code&gt;CHICAGO_MCP&lt;/code&gt; flag)&lt;/li&gt;
&lt;li&gt;Daemon worker (spawned by a supervisor)&lt;/li&gt;
&lt;li&gt;Remote Control (commands: &lt;code&gt;remote-control&lt;/code&gt;, &lt;code&gt;rc&lt;/code&gt;, &lt;code&gt;remote&lt;/code&gt;, &lt;code&gt;sync&lt;/code&gt;, &lt;code&gt;bridge&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Ablation baseline (the &lt;code&gt;ABLATION_BASELINE&lt;/code&gt; flag disables things like thinking and compact to run A/B comparisons)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;--version&lt;/code&gt; loads no modules, just returns &lt;code&gt;MACRO.VERSION&lt;/code&gt;. &lt;code&gt;--dump-system-prompt&lt;/code&gt; writes the system prompt to stdout, but that one's Anthropic-employees-only.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Analytics and privacy
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Two parallel telemetry pipelines
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Datadog&lt;/strong&gt;: a hardcoded client token batches events every 15 seconds to the US5 region. Platform, model name, subscription type, session ID, tool results, OAuth state.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First-party OTEL&lt;/strong&gt;: an OpenTelemetry pipeline sending the same stuff separately to Anthropic's own infrastructure.&lt;/p&gt;

&lt;p&gt;Both are on by default. Can be turned off with &lt;code&gt;DISABLE_TELEMETRY=1&lt;/code&gt;. Auto-disabled for Bedrock, Vertex, and Foundry users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt redaction
&lt;/h3&gt;

&lt;p&gt;In OTEL events, prompt content gets sent as &lt;code&gt;&amp;lt;REDACTED&amp;gt;&lt;/code&gt; by default. Setting &lt;code&gt;OTEL_LOG_USER_PROMPTS=1&lt;/code&gt; includes the raw prompts. Default is safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  Profanity detection
&lt;/h3&gt;

&lt;p&gt;There's a regex in &lt;code&gt;userPromptKeywords.ts&lt;/code&gt;: &lt;code&gt;wtf&lt;/code&gt;, &lt;code&gt;ffs&lt;/code&gt;, &lt;code&gt;shit&lt;/code&gt;, &lt;code&gt;fuck you&lt;/code&gt;, &lt;code&gt;piece of shit&lt;/code&gt;, &lt;code&gt;this sucks&lt;/code&gt;, &lt;code&gt;damn it&lt;/code&gt;, and about 20 more patterns.&lt;/p&gt;

&lt;p&gt;Every prompt runs through this regex. A match sets &lt;code&gt;is_negative: true&lt;/code&gt; in the analytics log. A second flag catches continuation patterns like &lt;code&gt;continue&lt;/code&gt;, &lt;code&gt;keep going&lt;/code&gt;, &lt;code&gt;go on&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Someone on the Claude Code team confirmed this on X. The internal dashboard name is "fucks chart." More cursing = worse experience. It doesn't change Claude's behavior and doesn't store the prompt content. It's essentially rage click detection, just in text form.&lt;/p&gt;

&lt;h3&gt;
  
  
  GrowthBook A/B testing
&lt;/h3&gt;

&lt;p&gt;GrowthBook runs feature flags and A/B tests in real time. What gets sent: user UUID, session ID, device ID, platform, organization UUID, account UUID, subscription type, rate limit tier, first token date, and email address.&lt;/p&gt;

&lt;p&gt;The email transmission isn't mentioned in any user-facing documentation.&lt;/p&gt;

&lt;p&gt;Anthropic employees can override any flag with &lt;code&gt;CLAUDE_INTERNAL_FC_OVERRIDES&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Event sampling and killswitch
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;tengu_event_sampling_config&lt;/code&gt; can percentage-sample specific event types. &lt;code&gt;tengu_frond_boric&lt;/code&gt; is a killswitch that can instantly shut down all analytics streams remotely, so the team can intervene before a release ships.&lt;/p&gt;

&lt;h3&gt;
  
  
  autoDream
&lt;/h3&gt;

&lt;p&gt;A background memory consolidation service. It triggers when two conditions are both met: at least 24 hours since the last run, and 5+ sessions in that window. When both line up, a forked subagent reads past sessions, finds patterns, and updates memory files.&lt;/p&gt;

&lt;p&gt;Not opt-in. On by default if memory is enabled. The thresholds are controlled server-side through GrowthBook (&lt;code&gt;tengu_onyx_plover&lt;/code&gt;). It shows up in the background task panel, but you have to actively look for it to notice.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Anthropic-employee-only system
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;USER_TYPE=ant&lt;/code&gt; shows up all over the source. "ant" is the internal identifier for Anthropic employees. It's pinned at build time via &lt;code&gt;--define&lt;/code&gt;, so in external builds these branches get wiped out completely by dead code elimination. Meaning: the code doesn't run in the public npm package, but the source is still readable in the source map.&lt;/p&gt;

&lt;h3&gt;
  
  
  Undercover mode
&lt;/h3&gt;

&lt;p&gt;When Anthropic engineers push to public repos, Claude Code activates "undercover mode." Instructions injected into the system prompt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't put "Claude Code" or any AI reference in commit messages&lt;/li&gt;
&lt;li&gt;Don't add Co-Authored-By&lt;/li&gt;
&lt;li&gt;Don't use internal model codenames (Capybara, Tengu, etc.)&lt;/li&gt;
&lt;li&gt;Don't reveal which model or version is running&lt;/li&gt;
&lt;li&gt;"Write commit messages like a human developer would"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code says explicitly: "There is NO force-OFF." If the repo's remote isn't on the internal allowlist, the mode is on. In conflicts, it defaults to the safer side (the code says so explicitly).&lt;/p&gt;

&lt;p&gt;Word for word, in the file: "Do not blow your cover."&lt;/p&gt;

&lt;h3&gt;
  
  
  Extra bash restrictions
&lt;/h3&gt;

&lt;p&gt;For Anthropic employees, commands like &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;wget&lt;/code&gt;, &lt;code&gt;gh api&lt;/code&gt;, &lt;code&gt;kubectl&lt;/code&gt;, &lt;code&gt;aws&lt;/code&gt;, &lt;code&gt;gcloud&lt;/code&gt;, &lt;code&gt;fa run&lt;/code&gt;, &lt;code&gt;coo&lt;/code&gt; go through an additional security check. External users don't have these restrictions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Internal commands
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;/insights&lt;/code&gt; is only available to Anthropic employees. It pulls session files via SCP from internal "Coder" servers and has Opus analyze them.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--dump-system-prompt&lt;/code&gt; writes the system prompt to stdout. Also ant-only.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CLAUDE_CODE_DUMP_AUTO_MODE=1&lt;/code&gt; writes every prompt going to the auto mode classifier to disk. For debugging.&lt;/p&gt;

&lt;h3&gt;
  
  
  Persistent retry
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;CLAUDE_CODE_UNATTENDED_RETRY=1&lt;/code&gt; is infinite retry mode. No circuit breaker. Max backoff 5 minutes, 6-hour reset cap. On 429 errors, it reads the &lt;code&gt;anthropic-ratelimit-unified-reset&lt;/code&gt; header and waits until the exact reset time. Every 30 seconds it prints a status message so the host environment doesn't mark the session as idle.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fennec
&lt;/h3&gt;

&lt;p&gt;An internal model alias. Migration code redirects &lt;code&gt;fennec-latest&lt;/code&gt; and &lt;code&gt;fennec-fast-latest&lt;/code&gt; to &lt;code&gt;opus&lt;/code&gt;/&lt;code&gt;opus[1m]&lt;/code&gt;. Only runs for ant users.&lt;/p&gt;

&lt;h3&gt;
  
  
  Feature flag override
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;CLAUDE_INTERNAL_FC_OVERRIDES&lt;/code&gt; lets any GrowthBook flag be manually overridden.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Hidden and undocumented commands
&lt;/h2&gt;

&lt;p&gt;Lots of commands are hidden from the UI with &lt;code&gt;isHidden: true&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/heapdump&lt;/code&gt;: dumps the JS heap to the desktop&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/thinkback&lt;/code&gt;: a 2025 year-in-review, Spotify Wrapped-style animation. Behind a GrowthBook gate.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/thinkback-play&lt;/code&gt;: plays the same animation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/rate-limit-options&lt;/code&gt;: internal rate limit menu&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/output-style&lt;/code&gt;: output style switcher&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/mock-limits&lt;/code&gt;: rate limit simulation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/good-claude&lt;/code&gt;: probably an internal feedback mechanism&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/bughunter&lt;/code&gt;: unknown&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/ant-trace&lt;/code&gt;: internal tracing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/teleport&lt;/code&gt;: probably a remote session jump&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/ctx_viz&lt;/code&gt;: context visualization&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/perf-issue&lt;/code&gt;: perf issue diagnosis&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/autofix-pr&lt;/code&gt;: automated PR fixes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/reset-limits&lt;/code&gt;: rate limit reset&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/env&lt;/code&gt;: environment variables&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/backfill-sessions&lt;/code&gt;: session backfill&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/debug-tool-call&lt;/code&gt;: tool call debug&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most are ant-only. They don't run in public builds.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Context window management
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Auto-compact
&lt;/h3&gt;

&lt;p&gt;Triggers at around 93% of the effective context window. Effective context = total context window minus min(maxOutputTokens, 20,000). 20K tokens get reserved for the compact summary itself. The p99.99 compact output is 17,387 tokens, so that's where the budget came from.&lt;/p&gt;

&lt;p&gt;What happens during compact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Images get replaced with &lt;code&gt;[image]&lt;/code&gt; placeholders&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;skill_discovery&lt;/code&gt;/&lt;code&gt;skill_listing&lt;/code&gt; attachments are pulled out (they get re-injected post-compact anyway)&lt;/li&gt;
&lt;li&gt;A fork agent summarizes the conversation&lt;/li&gt;
&lt;li&gt;If the compact request gets &lt;code&gt;prompt_too_long&lt;/code&gt;, the oldest messages get cut and it retries up to 3 times&lt;/li&gt;
&lt;li&gt;Post-compact restore: max 5 files (5K tokens each) and max 5 skills (25K tokens total). &lt;code&gt;readFileState&lt;/code&gt; and &lt;code&gt;loadedNestedMemoryPaths&lt;/code&gt; get cleared. Everything else is deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Circuit breaker
&lt;/h3&gt;

&lt;p&gt;3 failed compacts in a row stops the system. Before this was added, failed compacts were generating around 250,000 wasted API calls a day.&lt;/p&gt;

&lt;h3&gt;
  
  
  Session memory compact
&lt;/h3&gt;

&lt;p&gt;If session memory is enabled (needs both GrowthBook flags: &lt;code&gt;tengu_session_memory&lt;/code&gt; + &lt;code&gt;tengu_sm_compact&lt;/code&gt;), the compact doesn't make an API call. Expanding backward from the last summarized message ID, it keeps at least 10K tokens or 5 text-block messages (up to 40K tokens). The memory file serves as the summary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Context Collapse
&lt;/h3&gt;

&lt;p&gt;A separate experimental feature. Autocompact fires at ~93%, context collapse kicks in at 90%, and blocks at 95%. To stop them from racing, they can't run together.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blocking limit
&lt;/h3&gt;

&lt;p&gt;Once the effective context has fewer than 3,000 tokens left, the user can't send a new message.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Cost and retry
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cost tracker
&lt;/h3&gt;

&lt;p&gt;On every API response, &lt;code&gt;input_tokens&lt;/code&gt;, &lt;code&gt;output_tokens&lt;/code&gt;, &lt;code&gt;cache_read_input_tokens&lt;/code&gt;, &lt;code&gt;cache_creation_input_tokens&lt;/code&gt;, and &lt;code&gt;web_search_requests&lt;/code&gt; get accumulated. A separate &lt;code&gt;ModelUsage&lt;/code&gt; object is kept for each model.&lt;/p&gt;

&lt;p&gt;Display format: costs under $0.50 show 4 decimal places ($0.0023), above that 2 decimals ($1.54). Hardcoded.&lt;/p&gt;

&lt;p&gt;When a session is resumed with &lt;code&gt;/resume&lt;/code&gt;, the cost data gets restored.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retry strategy
&lt;/h3&gt;

&lt;p&gt;Max 10 retries in normal mode. Backoff: 500ms * 2^(attempt-1) + 25% jitter, capped at 32 seconds. If there's a &lt;code&gt;Retry-After&lt;/code&gt; header, that's used directly.&lt;/p&gt;

&lt;p&gt;Errors that get retried: 408, 409, 401 (API key cache gets cleared), 403 (OAuth revoke), 5xx, APIConnectionError, 429 (only for API key users), 529 (only for foreground operations).&lt;/p&gt;

&lt;p&gt;On a 529, background tasks (title generation, suggestions, other minor stuff) give up immediately. Only the main query retries. This is to prevent cascade amplification.&lt;/p&gt;

&lt;p&gt;3 consecutive 529s trigger a drop from Opus to the fallback model.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;x-should-retry: false&lt;/code&gt; is respected. For ant users, this header is ignored on 5xx errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fast mode cooldown
&lt;/h3&gt;

&lt;p&gt;On a 429, if &lt;code&gt;Retry-After&lt;/code&gt; is under 20 seconds, it's a short wait and fast mode stays on. At 20 seconds or more, fast mode gets disabled for 30 minutes (min 10, default 30). If an overage header shows up, fast mode turns off for the rest of the session.&lt;/p&gt;

&lt;p&gt;When fast mode "slows down," it probably caught a 429 and got quietly dropped. You don't see an error message.&lt;/p&gt;

&lt;h3&gt;
  
  
  Max tokens overflow
&lt;/h3&gt;

&lt;p&gt;When a 400 comes back with an &lt;code&gt;input + max_tokens &amp;gt; contextLimit&lt;/code&gt; message, it gets parsed and &lt;code&gt;max_tokens&lt;/code&gt; is shrunk for the next attempt with a 1,000-token safety margin. Self-correcting.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Prompt cache
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;Before every API call, &lt;code&gt;recordPromptState()&lt;/code&gt; hashes: the system prompt (with &lt;code&gt;cache_control&lt;/code&gt; stripped), tool schemas, model name, fast mode state, beta header list, &lt;code&gt;cache_control&lt;/code&gt; structure, effort value, and extra body parameters.&lt;/p&gt;

&lt;p&gt;The number of tracked sources is capped at 10. Each entry can hold ~300KB+ of &lt;code&gt;diffableContent&lt;/code&gt; string.&lt;/p&gt;

&lt;h3&gt;
  
  
  Break detection
&lt;/h3&gt;

&lt;p&gt;After the API response, &lt;code&gt;checkResponseForCacheBreak()&lt;/code&gt; runs. If there's a drop of more than &lt;code&gt;prevCacheRead * 0.95&lt;/code&gt; and at least 2,000 tokens lost, a break is detected.&lt;/p&gt;

&lt;p&gt;Causes, in order: model change, system prompt change (via character delta), tool schema change (per-tool hash comparison), fast mode transition, cache strategy change, beta header change, effort change.&lt;/p&gt;

&lt;p&gt;If none of those apply, it looks at time elapsed: &amp;gt;1 hour = "possible 1h TTL expiry", &amp;gt;5 minutes = "possible 5min TTL expiry", &amp;lt;5 minutes = "likely server-side".&lt;/p&gt;

&lt;p&gt;After compact, &lt;code&gt;notifyCompaction()&lt;/code&gt; resets the baseline. Otherwise the post-compact drop would register as a false positive.&lt;/p&gt;

&lt;h3&gt;
  
  
  Practical takeaway
&lt;/h3&gt;

&lt;p&gt;Editing CLAUDE.md often breaks the cache. Every edit changes the system prompt hash. A broken cache = more tokens billed at full price.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Permission system
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Rule hierarchy
&lt;/h3&gt;

&lt;p&gt;Permission rules come from these sources (in priority order): &lt;code&gt;localSettings&lt;/code&gt;, &lt;code&gt;projectSettings&lt;/code&gt;, &lt;code&gt;globalSettings&lt;/code&gt;, &lt;code&gt;cliArg&lt;/code&gt;, &lt;code&gt;command&lt;/code&gt;, &lt;code&gt;session&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Each rule specifies a behavior: &lt;code&gt;allow&lt;/code&gt;, &lt;code&gt;deny&lt;/code&gt;, or &lt;code&gt;ask&lt;/code&gt;. Tool matching: &lt;code&gt;Bash&lt;/code&gt; matches all Bash, &lt;code&gt;Bash(prefix:*)&lt;/code&gt; matches a specific command prefix, &lt;code&gt;mcp__server1&lt;/code&gt; matches all tools coming from that server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto mode classifier (yolo)
&lt;/h3&gt;

&lt;p&gt;When auto mode is on, every tool call gets checked by a separate Claude call. The transcript sent to the classifier:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only text blocks from user messages&lt;/li&gt;
&lt;li&gt;Only &lt;code&gt;tool_use&lt;/code&gt; blocks from assistant messages (text is deliberately excluded, to prevent self-manipulation)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;queued_command&lt;/code&gt; attachments get included as user turns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The format shifts to JSONL: &lt;code&gt;{"Bash": "ls -la"}&lt;/code&gt;, &lt;code&gt;{"user": "refactor this file"}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The system prompt is built from either &lt;code&gt;permissions_external.txt&lt;/code&gt; or &lt;code&gt;permissions_anthropic.txt&lt;/code&gt;. Anthropic employees see a different template. CLAUDE.md contents are added to the classifier message with &lt;code&gt;cache_control&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two output formats: tool-use format (&lt;code&gt;shouldBlock: boolean&lt;/code&gt;) and XML format (two-stage, with an "err on the side of blocking" suffix).&lt;/p&gt;

&lt;p&gt;If the classifier keeps rejecting, &lt;code&gt;shouldFallbackToPrompting()&lt;/code&gt; drops back to asking the user.&lt;/p&gt;

&lt;p&gt;Every tool call = 2 API calls. Auto mode doubles token consumption.&lt;/p&gt;

&lt;h3&gt;
  
  
  Policy limits
&lt;/h3&gt;

&lt;p&gt;Organization admins can remotely restrict features. Endpoint: &lt;code&gt;{BASE_API_URL}/api/claude_code/policy_limits&lt;/code&gt;. Initially 10-second timeout, 5 retries. ETag-based HTTP caching. Gets written to disk as &lt;code&gt;~/.claude/policy-limits.json&lt;/code&gt; (mode &lt;code&gt;0600&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Checked in the background once an hour (&lt;code&gt;setInterval&lt;/code&gt; with &lt;code&gt;unref()&lt;/code&gt;, so it doesn't keep the process alive).&lt;/p&gt;

&lt;p&gt;Fail-open: if the API fails and there's no disk cache, no restrictions. Exception: the &lt;code&gt;allow_product_feedback&lt;/code&gt; policy fails closed in HIPAA mode.&lt;/p&gt;

&lt;p&gt;An unknown policy name query defaults to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Affects Console users + Team/Enterprise OAuth users. Doesn't affect Bedrock/Vertex/third-party provider users.&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Query engine and orchestration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Main query loop
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;query.ts&lt;/code&gt; is 1,729 lines. The main function &lt;code&gt;query()&lt;/code&gt; returns an &lt;code&gt;AsyncGenerator&lt;/code&gt;; the actual work happens in &lt;code&gt;queryLoop()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At the start of the loop: &lt;code&gt;getMessagesAfterCompactBoundary()&lt;/code&gt; pulls messages from after the compact boundary. &lt;code&gt;applyToolResultBudget()&lt;/code&gt; caps the size of tool results. Skill discovery and memory prefetch get kicked off in parallel.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;while(true)&lt;/code&gt; loop yields &lt;code&gt;stream_request_start&lt;/code&gt; on every iteration. State mutation happens through a single &lt;code&gt;state = { ... }&lt;/code&gt; assignment (this pattern was chosen to clean up 7 separate &lt;code&gt;continue&lt;/code&gt; points).&lt;/p&gt;

&lt;p&gt;At the end of the loop: the memory prefetch and skill discovery prefetch get consumed (&amp;gt;98% of the time they're already ready). &lt;code&gt;refreshTools()&lt;/code&gt; pulls new tools from MCP servers. &lt;code&gt;maxTurns&lt;/code&gt; is checked. For top-level conversations (not subagents), a periodic summary is produced for &lt;code&gt;claude ps&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;taskBudget&lt;/code&gt; field maps to the API's &lt;code&gt;output_config.task_budget&lt;/code&gt; parameter. It's not the same thing as &lt;code&gt;tokenBudget&lt;/code&gt; (the 500K auto-continue).&lt;/p&gt;

&lt;h3&gt;
  
  
  Coordinator mode
&lt;/h3&gt;

&lt;p&gt;Requires both &lt;code&gt;CLAUDE_CODE_COORDINATOR_MODE=1&lt;/code&gt; and &lt;code&gt;feature('COORDINATOR_MODE')&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Worker results are delivered as user-role messages in &lt;code&gt;&amp;lt;task-notification&amp;gt;&lt;/code&gt; XML format. From Claude's perspective, the worker output looks as if the user wrote it. Format: &lt;code&gt;&amp;lt;task-id&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;status&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;summary&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;result&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;usage&amp;gt;&lt;/code&gt; (with &lt;code&gt;total_tokens&lt;/code&gt;, &lt;code&gt;tool_uses&lt;/code&gt;, &lt;code&gt;duration_ms&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Workers have access to tools from &lt;code&gt;ASYNC_AGENT_ALLOWED_TOOLS&lt;/code&gt;. Internal tools like &lt;code&gt;TeamCreateTool&lt;/code&gt;, &lt;code&gt;TeamDeleteTool&lt;/code&gt;, &lt;code&gt;SendMessageTool&lt;/code&gt;, &lt;code&gt;SyntheticOutputTool&lt;/code&gt; are excluded.&lt;/p&gt;

&lt;p&gt;The scratchpad directory is behind the &lt;code&gt;tengu_scratch&lt;/code&gt; feature gate. When enabled, all workers get unrestricted read/write access to a shared directory.&lt;/p&gt;

&lt;p&gt;When a session is resumed with &lt;code&gt;/resume&lt;/code&gt;, &lt;code&gt;matchSessionMode()&lt;/code&gt; detects coordinator/normal mismatches and fixes the env variable at runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tool execution
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;toolOrchestration.ts&lt;/code&gt; splits tool calls into two groups: concurrent-safe and non-concurrent. Concurrent-safe tools run in parallel, max concurrency 10 (&lt;code&gt;CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;StreamingToolExecutor.ts&lt;/code&gt; starts executing tools as they come in from the stream. When a non-concurrent tool shows up, it waits for the previous concurrent batch to finish. If Bash errors out, it kills sibling subprocesses with &lt;code&gt;siblingAbortController&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Every tool call runs &lt;code&gt;executePreToolHooks&lt;/code&gt; and &lt;code&gt;executePostToolHooks&lt;/code&gt;. On error, &lt;code&gt;executePostToolUseFailureHooks&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Skill system
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Sources
&lt;/h3&gt;

&lt;p&gt;Skills come from three places: disk-based (&lt;code&gt;.claude/skills/skill-name/SKILL.md&lt;/code&gt;), legacy &lt;code&gt;/commands/&lt;/code&gt;, and MCP servers.&lt;/p&gt;

&lt;p&gt;The disk-based format is mandatory: a single &lt;code&gt;.md&lt;/code&gt; file isn't supported, the &lt;code&gt;skill-name/SKILL.md&lt;/code&gt; structure is required. Conflicting skills are detected via &lt;code&gt;realpath()&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontmatter
&lt;/h3&gt;

&lt;p&gt;Supported fields: &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;argument-hint&lt;/code&gt;, &lt;code&gt;arguments&lt;/code&gt;, &lt;code&gt;when_to_use&lt;/code&gt;, &lt;code&gt;version&lt;/code&gt;, &lt;code&gt;model&lt;/code&gt; (or &lt;code&gt;inherit&lt;/code&gt;), &lt;code&gt;allowed-tools&lt;/code&gt;, &lt;code&gt;disable-model-invocation&lt;/code&gt;, &lt;code&gt;user-invocable&lt;/code&gt;, &lt;code&gt;hooks&lt;/code&gt;, &lt;code&gt;context&lt;/code&gt; (&lt;code&gt;fork&lt;/code&gt;/&lt;code&gt;inline&lt;/code&gt;), &lt;code&gt;agent&lt;/code&gt;, &lt;code&gt;effort&lt;/code&gt;, &lt;code&gt;shell&lt;/code&gt;, &lt;code&gt;paths&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;${CLAUDE_SKILL_DIR}&lt;/code&gt; and &lt;code&gt;${CLAUDE_SESSION_ID}&lt;/code&gt; variables get injected into skill content.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;p&gt;Running &lt;code&gt;!&lt;/code&gt; shell commands is blocked for MCP-sourced skills. Only works for disk-based ones.&lt;/p&gt;

&lt;p&gt;Path traversal protection: &lt;code&gt;..&lt;/code&gt;, URL-encoded variants, and Unicode NFKC normalization attacks get rejected. File writes use &lt;code&gt;O_NOFOLLOW | O_CREAT | O_EXCL&lt;/code&gt; flags, mode &lt;code&gt;0o600&lt;/code&gt;, directories &lt;code&gt;0o700&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bundled skills
&lt;/h3&gt;

&lt;p&gt;In &lt;code&gt;src/skills/bundled/&lt;/code&gt;: &lt;code&gt;updateConfig&lt;/code&gt;, &lt;code&gt;keybindings&lt;/code&gt;, &lt;code&gt;verify&lt;/code&gt;, &lt;code&gt;debug&lt;/code&gt;, &lt;code&gt;loremIpsum&lt;/code&gt;, &lt;code&gt;skillify&lt;/code&gt;, &lt;code&gt;remember&lt;/code&gt;, &lt;code&gt;simplify&lt;/code&gt;, &lt;code&gt;batch&lt;/code&gt;, &lt;code&gt;stuck&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Behind feature flags: &lt;code&gt;dream&lt;/code&gt; (KAIROS/KAIROS_DREAM), &lt;code&gt;hunter&lt;/code&gt; (REVIEW_ARTIFACT), &lt;code&gt;loop&lt;/code&gt; (AGENT_TRIGGERS), &lt;code&gt;scheduleRemoteAgents&lt;/code&gt; (AGENT_TRIGGERS_REMOTE).&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Plugin system
&lt;/h2&gt;

&lt;p&gt;There's a built-in plugin scaffold, but it's currently empty. The &lt;code&gt;initBuiltinPlugins()&lt;/code&gt; function doesn't register any plugins. Comment in the code: transition scaffold for "bundled skills that should be user-toggleable".&lt;/p&gt;

&lt;p&gt;Marketplace plugins use the &lt;code&gt;{name}@{marketplace}&lt;/code&gt; ID format. Stored in &lt;code&gt;settings.json&lt;/code&gt; as &lt;code&gt;enabledPlugins&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Plugins that get removed from the marketplace are silently deleted at session start via the &lt;code&gt;forceRemoveDeletedPlugins: true&lt;/code&gt; flag. No confirmation, no notification.&lt;/p&gt;




&lt;h2&gt;
  
  
  12. MCP server management
&lt;/h2&gt;

&lt;h3&gt;
  
  
  XAA (Cross-App Access)
&lt;/h3&gt;

&lt;p&gt;An enterprise feature. Gets MCP tokens without the browser approval screen. Two stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RFC 8693 Token Exchange: uses &lt;code&gt;id_token&lt;/code&gt; to get an ID-JAG (Identity Assertion Authorization Grant)&lt;/li&gt;
&lt;li&gt;RFC 7523 JWT Bearer Grant: uses the ID-JAG to get an &lt;code&gt;access_token&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Activated with &lt;code&gt;CLAUDE_CODE_ENABLE_XAA&lt;/code&gt;. IdP configuration comes from &lt;code&gt;settings.xaaIdp&lt;/code&gt;. Tokens get cached in the keychain.&lt;/p&gt;

&lt;h3&gt;
  
  
  Official MCP Registry
&lt;/h3&gt;

&lt;p&gt;A URL list is pulled fire-and-forget from &lt;code&gt;https://api.anthropic.com/mcp-registry/v0/servers?version=latest&amp;amp;visibility=commercial&lt;/code&gt;. It's for a security check: "is this MCP server official?"&lt;/p&gt;

&lt;h3&gt;
  
  
  VSCode SDK MCP
&lt;/h3&gt;

&lt;p&gt;The MCP server called &lt;code&gt;claude-vscode&lt;/code&gt; gets special treatment. &lt;code&gt;file_updated&lt;/code&gt; notifications go from Claude to VSCode; &lt;code&gt;log_event&lt;/code&gt; notifications come back from VSCode to Claude. Ant-only.&lt;/p&gt;

&lt;p&gt;On connect, GrowthBook gate values get pushed to VSCode: &lt;code&gt;tengu_vscode_review_upsell&lt;/code&gt;, &lt;code&gt;tengu_vscode_onboarding&lt;/code&gt;, &lt;code&gt;tengu_quiet_fern&lt;/code&gt;, &lt;code&gt;tengu_vscode_cc_auth&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Elicitation
&lt;/h3&gt;

&lt;p&gt;Handles &lt;code&gt;elicitation/create&lt;/code&gt; requests from the MCP 2025-03-26 spec. Two modes: &lt;code&gt;form&lt;/code&gt; (generate UI from JSON schema) and &lt;code&gt;url&lt;/code&gt; (open browser, wait for completion). The hook system is integrated.&lt;/p&gt;




&lt;h2&gt;
  
  
  13. IDE Bridge (Remote Control)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;p&gt;Needs all three: &lt;code&gt;feature('BRIDGE_MODE')&lt;/code&gt; build flag, &lt;code&gt;tengu_ccr_bridge&lt;/code&gt; GrowthBook gate, and a Claude.ai OAuth token. Doesn't work with API key, Bedrock, or Vertex.&lt;/p&gt;

&lt;h3&gt;
  
  
  Two implementations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;v1 (env-based): current standard&lt;/li&gt;
&lt;li&gt;v2 (env-less/REPL bridge): enabled via the &lt;code&gt;tengu_bridge_repl_v2&lt;/code&gt; gate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CSE-to-session shim: can translate &lt;code&gt;cse_*&lt;/code&gt; IDs to &lt;code&gt;session_*&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  JWT management
&lt;/h3&gt;

&lt;p&gt;Tokens get refreshed 5 minutes before expiry. Failed refreshes give up after 3 attempts, spaced 60 seconds apart. The &lt;code&gt;sk-ant-si-&lt;/code&gt; session-ingress prefix gets stripped before decoding.&lt;/p&gt;

&lt;h3&gt;
  
  
  CCR Auto Connect
&lt;/h3&gt;

&lt;p&gt;When the &lt;code&gt;tengu_cobalt_harbor&lt;/code&gt; gate is on, every session automatically connects to Remote Control. Can be disabled with &lt;code&gt;remoteControlAtStartup=false&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mirror Mode
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;CLAUDE_CODE_CCR_MIRROR&lt;/code&gt; env or the &lt;code&gt;tengu_ccr_mirror&lt;/code&gt; gate, every local session opens an additional outbound-only CCR session.&lt;/p&gt;




&lt;h2&gt;
  
  
  14. Session Memory
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Triggering
&lt;/h3&gt;

&lt;p&gt;Both the &lt;code&gt;tengu_session_memory&lt;/code&gt; GrowthBook gate + &lt;code&gt;isAutoCompactEnabled()&lt;/code&gt; have to be on. Disabled in remote mode.&lt;/p&gt;

&lt;p&gt;Thresholds come from the &lt;code&gt;tengu_sm_config&lt;/code&gt; dynamic config: &lt;code&gt;minimumMessageTokensToInit&lt;/code&gt; (tokens needed before the first extraction), &lt;code&gt;minimumTokensBetweenUpdate&lt;/code&gt; (token increase between extractions), &lt;code&gt;toolCallsBetweenUpdates&lt;/code&gt; (tool call count between extractions).&lt;/p&gt;

&lt;p&gt;The token threshold is always required. It fires together with either the tool call threshold or a "no tool in the last assistant turn" condition.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;An isolated subagent starts via &lt;code&gt;runForkedAgent&lt;/code&gt;. It only has &lt;code&gt;FileEdit&lt;/code&gt; permission for the memory file. It doesn't pollute the main conversation.&lt;/p&gt;

&lt;p&gt;File: &lt;code&gt;~/.claude/session_memory/&amp;lt;session_id&amp;gt;/memory.md&lt;/code&gt;. Starts from a template.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;/summary&lt;/code&gt; command calls &lt;code&gt;manuallyExtractSessionMemory()&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  15. Memory directory (memdir)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Structure
&lt;/h3&gt;

&lt;p&gt;Four memory types: &lt;code&gt;user&lt;/code&gt; (about the user), &lt;code&gt;feedback&lt;/code&gt; (corrections and confirmations), &lt;code&gt;project&lt;/code&gt; (ongoing work), &lt;code&gt;reference&lt;/code&gt; (pointers to external sources). Each type has its own team/private scope rules.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;MEMORY.md&lt;/code&gt; entrypoint caps at 200 lines or 25,000 bytes. When exceeded, it's truncated and the overflow limit is flagged.&lt;/p&gt;

&lt;h3&gt;
  
  
  Relevance selection
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;findRelevantMemories()&lt;/code&gt; asks Sonnet for the current query (&lt;code&gt;sideQuery&lt;/code&gt;), picks up to 5 files. It deliberately skips reference documents for recently used tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Team memory
&lt;/h3&gt;

&lt;p&gt;Behind the &lt;code&gt;TEAMMEM&lt;/code&gt; feature flag. Separate path management via &lt;code&gt;teamMemPaths.ts&lt;/code&gt;. Path security is extensive: null bytes, URL-encoded traversal, Unicode NFKC normalization attacks, backslash injection. All get rejected (&lt;code&gt;PathTraversalError&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  16. OAuth
&lt;/h2&gt;

&lt;p&gt;PKCE + Authorization Code Flow. Two parallel flows race at the same time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic: browser opens, waits for localhost callback&lt;/li&gt;
&lt;li&gt;Manual: user copies and pastes the code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Whichever finishes first wins.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;skipBrowserOpen&lt;/code&gt; is for the SDK control protocol: when the &lt;code&gt;claude_authenticate&lt;/code&gt; command comes in, it lets the caller manage its own display. Both URLs get passed to &lt;code&gt;authURLHandler&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After the token is obtained, &lt;code&gt;fetchProfileInfo()&lt;/code&gt; pulls the subscription type and rate limit tier.&lt;/p&gt;




&lt;h2&gt;
  
  
  17. Platform features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Vim mode
&lt;/h3&gt;

&lt;p&gt;State machine between INSERT and NORMAL. In NORMAL mode, a full command parser: idle, operator, operatorCount, operatorTextObj, execute stages.&lt;/p&gt;

&lt;p&gt;Operators: &lt;code&gt;d&lt;/code&gt; (delete), &lt;code&gt;c&lt;/code&gt; (change), &lt;code&gt;y&lt;/code&gt; (yank). Motions: &lt;code&gt;h&lt;/code&gt;/&lt;code&gt;l&lt;/code&gt;/&lt;code&gt;j&lt;/code&gt;/&lt;code&gt;k&lt;/code&gt;, &lt;code&gt;w&lt;/code&gt;/&lt;code&gt;b&lt;/code&gt;/&lt;code&gt;e&lt;/code&gt;/&lt;code&gt;W&lt;/code&gt;/&lt;code&gt;B&lt;/code&gt;/&lt;code&gt;E&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;/&lt;code&gt;^&lt;/code&gt;/&lt;code&gt;$&lt;/code&gt;, &lt;code&gt;G&lt;/code&gt;, &lt;code&gt;gj&lt;/code&gt;/&lt;code&gt;gk&lt;/code&gt;. Text objects: &lt;code&gt;iw&lt;/code&gt;/&lt;code&gt;aw&lt;/code&gt;, quote/paren/bracket/brace pairs. Find: &lt;code&gt;f&lt;/code&gt;/&lt;code&gt;F&lt;/code&gt;/&lt;code&gt;t&lt;/code&gt;/&lt;code&gt;T&lt;/code&gt; + repeat.&lt;/p&gt;

&lt;p&gt;Dot-repeat is supported. Max count 10,000 (infinite loop protection).&lt;/p&gt;

&lt;h3&gt;
  
  
  Keybinding system
&lt;/h3&gt;

&lt;p&gt;20 different contexts: Global, Chat, Autocomplete, Settings, Confirmation, Tabs, Transcript, HistorySearch, Task, ThemePicker, Scroll, Help, Attachments, Footer, MessageSelector, MessageActions, DiffDialog, ModelPicker, Select, Plugin.&lt;/p&gt;

&lt;p&gt;Platform adaptations: &lt;code&gt;alt+v&lt;/code&gt; for image paste on Windows (&lt;code&gt;ctrl+v&lt;/code&gt; on Linux/macOS). Windows Terminal VT mode check: before Node 22.17.0 and Bun 1.2.23, &lt;code&gt;shift+tab&lt;/code&gt; doesn't work, &lt;code&gt;meta+m&lt;/code&gt; is the fallback.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ctrl+c&lt;/code&gt; and &lt;code&gt;ctrl+d&lt;/code&gt; can't be rebound. Protected by &lt;code&gt;reservedShortcuts.ts&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Bindings gated on feature flags: space push-to-talk (&lt;code&gt;VOICE_MODE&lt;/code&gt;), &lt;code&gt;shift+up&lt;/code&gt; message actions (&lt;code&gt;MESSAGE_ACTIONS&lt;/code&gt;), &lt;code&gt;ctrl+shift+b&lt;/code&gt; toggle brief (&lt;code&gt;KAIROS_BRIEF&lt;/code&gt;), &lt;code&gt;ctrl+shift+f&lt;/code&gt;/&lt;code&gt;p&lt;/code&gt; global search/quick open (&lt;code&gt;QUICK_SEARCH&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;User customization: &lt;code&gt;~/.claude/keybindings.json&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Voice mode
&lt;/h3&gt;

&lt;p&gt;Requires an Anthropic OAuth token. Doesn't work with API key, Bedrock, Vertex, or Foundry. The &lt;code&gt;voice_stream&lt;/code&gt; endpoint is only available on claude.ai.&lt;/p&gt;

&lt;p&gt;GrowthBook killswitch: &lt;code&gt;tengu_amber_quartz_disabled&lt;/code&gt;. On macOS, token check goes through the &lt;code&gt;security&lt;/code&gt; CLI (~20-50ms cold, subsequent calls cached).&lt;/p&gt;




&lt;h2&gt;
  
  
  18. Infrastructure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Native TypeScript ports
&lt;/h3&gt;

&lt;p&gt;Pure TypeScript reimplementations of three modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;yoga-layout&lt;/strong&gt;: Meta's Yoga flexbox engine (C++ to TS, ~2,500 lines). The subset Ink uses: &lt;code&gt;flex-direction&lt;/code&gt;, &lt;code&gt;grow&lt;/code&gt;/&lt;code&gt;shrink&lt;/code&gt;/&lt;code&gt;basis&lt;/code&gt;, &lt;code&gt;align-items&lt;/code&gt;, &lt;code&gt;justify-content&lt;/code&gt;, &lt;code&gt;margin&lt;/code&gt;/&lt;code&gt;padding&lt;/code&gt;/&lt;code&gt;border&lt;/code&gt;/&lt;code&gt;gap&lt;/code&gt;, position absolute/relative, measure functions. Wrap, baseline, &lt;code&gt;display:contents&lt;/code&gt; are also there, Ink just doesn't use them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;file-index&lt;/strong&gt;: nucleo (Helix editor's fuzzy finder, Rust to TS). nucleo-style scoring: &lt;code&gt;SCORE_MATCH=16&lt;/code&gt;, &lt;code&gt;BONUS_BOUNDARY=8&lt;/code&gt;, &lt;code&gt;BONUS_CAMEL=6&lt;/code&gt;. Test files take a 1.05x penalty. During async build, ready prefix searches work (incremental). Chunk size is time-based (4ms), adaptive to the machine.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;color-diff&lt;/strong&gt;: a color difference calculation module.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No native dependencies needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Upstream proxy (CCR)
&lt;/h3&gt;

&lt;p&gt;Enterprise MITM proxy support. Startup sequence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reads the token from &lt;code&gt;/run/ccr/session_token&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;On Linux, &lt;code&gt;prctl(PR_SET_DUMPABLE, 0)&lt;/code&gt; calls &lt;code&gt;libc.so.6&lt;/code&gt; via Bun FFI. Protects the heap against ptrace. Blocks the &lt;code&gt;gdb -p $PPID&lt;/code&gt; attack via prompt injection.&lt;/li&gt;
&lt;li&gt;Downloads the proxy CA cert and merges it with the system CA bundle&lt;/li&gt;
&lt;li&gt;Starts a local CONNECT-to-WebSocket relay&lt;/li&gt;
&lt;li&gt;Deletes the token file (it stays in heap)&lt;/li&gt;
&lt;li&gt;Injects &lt;code&gt;HTTPS_PROXY&lt;/code&gt;, &lt;code&gt;SSL_CERT_FILE&lt;/code&gt;, &lt;code&gt;NODE_EXTRA_CA_CERTS&lt;/code&gt; into child processes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NO_PROXY: &lt;code&gt;anthropic.com&lt;/code&gt;, &lt;code&gt;github.com&lt;/code&gt;, &lt;code&gt;registry.npmjs.org&lt;/code&gt;, &lt;code&gt;pypi.org&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CCR_UPSTREAM_PROXY_ENABLED&lt;/code&gt; is set server-side (because GrowthBook is cold in the container, the client check isn't trustworthy).&lt;/p&gt;

&lt;p&gt;Fail-open. A proxy setup error doesn't kill the session.&lt;/p&gt;

&lt;h3&gt;
  
  
  Migrations
&lt;/h3&gt;

&lt;p&gt;One-way, usually idempotent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;migrateSonnet45ToSonnet46&lt;/code&gt;: Pro/Max/Team from explicit model string to the &lt;code&gt;sonnet&lt;/code&gt; alias&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;migrateSonnet1mToSonnet45&lt;/code&gt;: &lt;code&gt;sonnet[1m]&lt;/code&gt; conversion (in 4.6, 1m was opened to a different group)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;migrateFennecToOpus&lt;/code&gt;: ant-only, &lt;code&gt;fennec-latest&lt;/code&gt; alias to &lt;code&gt;opus&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;migrateLegacyOpusToCurrent&lt;/code&gt;: explicit legacy Opus strings to the &lt;code&gt;opus&lt;/code&gt; alias&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;migrateOpusToOpus1m&lt;/code&gt;: on Max/Team Premium, &lt;code&gt;opus&lt;/code&gt; gets auto-upgraded to &lt;code&gt;opus[1m]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;resetProToOpusDefault&lt;/code&gt;: resets Pro users to the Opus 4.5 default&lt;/li&gt;
&lt;li&gt;Config key renames, auto mode dialog reset&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  LSP integration
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;LSPServerManager&lt;/code&gt; routes to an LSP server based on file extension. Requests like &lt;code&gt;textDocument/definition&lt;/code&gt;, &lt;code&gt;textDocument/references&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;passiveFeedback.ts&lt;/code&gt; converts LSP &lt;code&gt;publishDiagnostics&lt;/code&gt; notifications into Claude's attachment format. Severity mapping: &lt;code&gt;1=Error&lt;/code&gt;, &lt;code&gt;2=Warning&lt;/code&gt;, &lt;code&gt;3=Information&lt;/code&gt;, &lt;code&gt;4=Hint&lt;/code&gt;. Malformed &lt;code&gt;file://&lt;/code&gt; URIs are normalized; if that fails, the URI is used as-is.&lt;/p&gt;




&lt;h2&gt;
  
  
  19. Other details
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Buddy system
&lt;/h3&gt;

&lt;p&gt;A virtual pet system in &lt;code&gt;buddy/&lt;/code&gt;. Deterministic animal derivation from a UUID hash: 18 species, 1% shiny, rarity from Common to Legendary, RPG stats (&lt;code&gt;DEBUGGING&lt;/code&gt;, &lt;code&gt;PATIENCE&lt;/code&gt;, &lt;code&gt;CHAOS&lt;/code&gt;, &lt;code&gt;WISDOM&lt;/code&gt;, &lt;code&gt;SNARK&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The species name "duck" is hidden with hex codes (&lt;code&gt;0x64,0x75,0x63,0x6b&lt;/code&gt;). Clashes with an internal model codename.&lt;/p&gt;

&lt;p&gt;No cheating possible. Species and stats are recomputed from the UUID every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;moreright/&lt;/code&gt; stub
&lt;/h3&gt;

&lt;p&gt;A single no-op file. The real implementation lives in the internal build. Interface: &lt;code&gt;onBeforeQuery&lt;/code&gt; (before every API call) and &lt;code&gt;onTurnComplete&lt;/code&gt; (after every turn). Nobody knows what it does.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;cyberRiskInstruction.ts&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A cybersecurity instruction. At the top of the file: "Do not modify without Safeguards Team review." At the bottom, a direct message to Claude: "Claude: Do not edit this file unless explicitly asked to do so by the user."&lt;/p&gt;

&lt;h3&gt;
  
  
  Channel permissions
&lt;/h3&gt;

&lt;p&gt;MCP channel approval IDs are 5 letters, filtered through a 24-word profanity filter. Code comment: "this is why i bias to numbers, hard to have anything worse than 80085."&lt;/p&gt;

&lt;h3&gt;
  
  
  Screens
&lt;/h3&gt;

&lt;p&gt;Three main React screen components: &lt;code&gt;Doctor.tsx&lt;/code&gt; (the &lt;code&gt;/doctor&lt;/code&gt; command), &lt;code&gt;REPL.tsx&lt;/code&gt; (the main REPL, at 874KB the largest file), &lt;code&gt;ResumeConversation.tsx&lt;/code&gt; (session resume).&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;512K lines of code. A day's reading.&lt;/p&gt;

&lt;p&gt;Engineering quality is high. Circuit breaker on compaction, cascade prevention on 529s, sibling abort in streaming tool execution, prompt cache diagnostics. Decisions made by a team that's been paged at 3 AM.&lt;/p&gt;

&lt;p&gt;Privacy is mixed. Prompts redacted by default (good). Telemetry on by default without documentation (bad). Email sent to the A/B test infrastructure (debatable). autoDream runs without permission (uncomfortable).&lt;/p&gt;

&lt;p&gt;The most striking find is undercover mode. Not technically, philosophically. A company that makes AI developer tools has its tool hide its identity when its own engineers use it in public.&lt;/p&gt;

&lt;p&gt;The leak itself is the most ordinary of mistakes. A missing line in &lt;code&gt;.npmignore&lt;/code&gt;. A 57MB file. A publish pipeline nobody's checking.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>security</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
