Skip to content

Instantly share code, notes, and snippets.

@ZhangHanDong
Last active March 13, 2026 21:40
Show Gist options
  • Select an option

  • Save ZhangHanDong/a123f194fc0e68c9d408355bd10746c6 to your computer and use it in GitHub Desktop.

Select an option

Save ZhangHanDong/a123f194fc0e68c9d408355bd10746c6 to your computer and use it in GitHub Desktop.

Claude Code /btw Command Reverse Engineering

Package: @anthropic-ai/claude-code@2.1.73 (native Mach-O arm64 build, symlinked from ~/.local/share/claude/versions/2.1.73) Date: 2026-03-12 Methods: Static source analysis (prettier-formatted cli.js) + Dynamic wire capture (fetch monkeypatch) + Codex independent review

Overview

/btw is a "side question" command that forks the current conversation context, sends a single-turn query, and displays the response inline. The main conversation is not affected. Tool calls are blocked at the client level, but the full tool schema is still sent to the API.

Analysis Sources and Evidence Chain

Source Method Analyst Evidence Files
Static analysis prettier formatted cli.js (~571K lines), keyword search Claude Code /tmp/cc_cli.js (formatted from npm cache at /opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js)
Dynamic wire capture NODE_OPTIONS="--require hook.js" fetch monkeypatch Claude Code /tmp/btw-captures/{005_NORMAL,006_BTW,009_BTW,011_BTW,013_NORMAL,014_NORMAL}_full.json
Independent static review Binary string extraction from 2.1.73 native build Codex Separate report

Installation type: The claude binary at ~/.local/bin/claude is a native Mach-O arm64 executable (not a Node.js script), symlinked to ~/.local/share/claude/versions/2.1.73. However, it embeds a Node.js runtime — the NODE_OPTIONS="--require ..." hook successfully intercepted globalThis.fetch calls, confirming that the native build uses Node.js internally for API communication.

Wire capture validation: The fetch hook intercepts globalThis.fetch calls inside the process, logs the full JSON body to disk, then forwards the request to the real Anthropic API. Confirmed working: normal messages in the same session received real model responses, and later requests (013, 014) contain tool_use/tool_result blocks from actual agent interactions.

Two independent static artifacts: The prettified npm cli.js (/tmp/cc_cli.js) and the local native Mach-O binary (~/.local/share/claude/versions/2.1.73) serve as independent static evidence sources. The npm artifact confirms the full call chain (QpY → mpY → me4 → hv) with line-level tracing. The native binary independently corroborates key parameters via string extraction: querySource:"side_question", forkLabel:"side_question", maxTurns:1, skipCacheWrite:!0. Neither artifact alone would be sufficient — the npm source provides structural detail, while the native binary confirms the same parameters exist in the actual installed executable.

Reproduction caveat: This capture method is verified only for the native macOS arm64 build of 2.1.73 which embeds a Node.js runtime. It may not work for other distribution formats (e.g., if a future version ships without embedded Node.js). For proxy-based capture (distribution-agnostic), see the alternative method at the end of this document.

Source Code Excerpts

All excerpts below are verbatim from the prettier-formatted cli.js (line numbers refer to /tmp/cc_cli.js).

Entry chain: /btw regex → handler → component → API call

Step 1: Regex match (line 447806)

// cli.js:447806 — verbatim
BpY = /^\/btw\b/gi;

Step 2: Command handler (lines 447950-447954)

// cli.js:447950-447954 — verbatim
async function QpY(A, q, K) {
  let Y = K?.trim();
  if (!Y)
    return (A("Usage: /btw <your question>", { display: "system" }), null);
  return nw.createElement(mpY, { question: Y, context: q, onDone: A });
}

Step 3: React component calls API function (lines 447810, 447847)

// cli.js:447810 — verbatim (abridged, showing the me4 call)
function mpY(A) {
  // ... React state setup ...
  // line 447847:
              m = await me4({
                question: K,
                cacheSafeParams: {
                  systemPrompt: y,
                  userContext: u,
                  systemContext: S,
                  toolUseContext: Y,
                  forkContextMessages: Y.messages,  // ← passed to hv(), then filtered by VF1() before sending
                },
              });

Step 4: API call function (lines 447765-447801)

// cli.js:447765-447801 — verbatim
async function me4({ question: A, cacheSafeParams: q }) {
  let K = {
      ...q.toolUseContext,
      options: { ...q.toolUseContext.options, maxThinkingTokens: 0 },
    },
    Y = `<system-reminder>This is a side question from the user. You must answer this question directly in a single response.

CRITICAL CONSTRAINTS:
- You have NO tools available - you cannot read files, run commands, search, or take any actions
- This is a one-off response - there will be no follow-up turns
- You can ONLY provide information based on what you already know from the conversation context
- NEVER say things like "Let me try...", "I'll now...", "Let me check...", or promise to take any action
- If you don't know the answer, say so - do not offer to look it up or investigate

Simply answer the question with the information you have.</system-reminder>

${A}`,
    z = await hv({
      promptMessages: [g6({ content: Y })],
      cacheSafeParams: { ...q, toolUseContext: K },
      canUseTool: async () => ({
        behavior: "deny",
        message: "Side questions cannot use tools",
        decisionReason: { type: "other", reason: "side_question" },
      }),
      querySource: "side_question",
      forkLabel: "side_question",
      maxTurns: 1,
    }),
    H = z.messages
      .find((O) => O.type === "assistant")
      ?.message?.content?.find((O) => O.type === "text");
  return {
    response: H && H.type === "text" ? H.text.trim() : null,
    usage: z.totalUsage,
  };
}

Note on obfuscated names: me4, QpY, mpY, hv, g6 are minified identifiers from the bundled source. The call chain QpY → mpY → me4 → hv is traced by following the code path from the /btw regex match through each function reference. The variable names are minified but the string literals ("side_question", "Side questions cannot use tools", the system-reminder text) are unambiguous anchors.

skipCacheWrite: true is a direct parameter in the /btw call path, confirmed by two independent static artifacts: the raw npm bundle contains WR(... skipCacheWrite:!0) in the side-question helper (cli.js line ~2663), and the native binary's string extraction shows the equivalent qL(... skipCacheWrite:!0). It is not visible in the prettified me4 excerpt above (which does show the hv() call but not this parameter); the raw npm bundle's equivalent side-question helper includes it directly.

Dynamic Wire Capture Results

Captured via NODE_OPTIONS="--require /tmp/cc-fetch-hook.js" claude, which monkeypatches globalThis.fetch to log all Anthropic API request bodies to disk before forwarding them to the real API.

Capture session: User sent "hello" (normal), then /btw what is this project?. Both requests were captured. The normal request received a real model response, confirming the hook was active and passthrough was working.

Wire capture sample: BTW request (redacted)

From /tmp/btw-captures/006_BTW_full.json, redacted to key fields:

{
  "model": "claude-opus-4-6",
  "max_tokens": 32000,
  "stream": true,
  "thinking": { "type": "adaptive" },
  "tools": [
    { "name": "Agent" }, { "name": "Bash" }, { "name": "Glob" },
    { "name": "Grep" }, { "name": "Read" }, { "name": "Edit" },
    { "name": "Write" }, { "name": "Skill" }, { "name": "ToolSearch" }
  ],
  "system": "[array, 3 blocks, ~24446 chars]",
  "messages": [
    { "role": "user",      "content": "<available-deferred-tools>..." },
    { "role": "user",      "content": ["(5 text blocks: skills, hooks, CLAUDE.md, 'hello')"] },
    { "role": "assistant", "content": [{ "type": "text", "text": "\n\nHello! How can I help you today?" }] },
    { "role": "user",      "content": "<system-reminder>This is a side question...{constraints}...</system-reminder>\n\nwhat is this project?" }
  ]
}

Key observations:

  • "tool_choice" field is absent (not set to none, not present at all)
  • "thinking" is {"type":"adaptive"}not disabled despite source setting maxThinkingTokens: 0
  • All 9 tools are present with full schemas (only names shown above for brevity)
  • This capture has 4 messages (short conversation). A later BTW capture (011_BTW) has 9 messages but still only text blocks — no tool_use/tool_result blocks. See "Messages" section below for analysis.

Wire capture sample: NORMAL request (for comparison)

From /tmp/btw-captures/005_NORMAL_full.json:

{
  "model": "claude-opus-4-6",
  "max_tokens": 32000,
  "stream": true,
  "thinking": { "type": "adaptive" },
  "tools": [
    { "name": "Agent" }, { "name": "Bash" }, { "name": "Glob" },
    { "name": "Grep" }, { "name": "Read" }, { "name": "Edit" },
    { "name": "Write" }, { "name": "Skill" }, { "name": "ToolSearch" }
  ],
  "system": "[array, 3 blocks, ~24472 chars]",
  "messages": [
    { "role": "user", "content": "<available-deferred-tools>..." },
    { "role": "user", "content": ["(5 text blocks: skills, hooks, CLAUDE.md, 'hello')"] }
  ]
}

NORMAL vs BTW — field-by-field diff

Field NORMAL BTW Difference
URL https://api.anthropic.com/v1/messages?beta=true Same None
model claude-opus-4-6 claude-opus-4-6 None
max_tokens 32000 32000 None
stream true true None
tools 9 tools 9 tools (same names, same schemas) None
tool_choice absent absent None
thinking {"type":"adaptive"} {"type":"adaptive"} None
system ~24472 chars ~24446 chars Not byte-identical: diff in cache control ttl field
messages 2 4 BTW has conversation context + btw question appended
last message "hello" "<system-reminder>...side question...</system-reminder>\n\nwhat is this project?" BTW wraps in system-reminder

System prompt diff detail: The system prompts are semantically equivalent but not byte-identical. The difference is in cache control metadata: the NORMAL request includes "ttl": "1h" in a cache control block that the BTW request omits. This is an internal caching optimization, not a content difference.

Conclusion from wire data: The only meaningful differences between a NORMAL and BTW API request are:

  1. BTW includes the current conversation context as a fork (see Messages section for scope nuance)
  2. BTW appends the question wrapped in <system-reminder> tags as the last user message

Tools, thinking mode, model, and system prompt content are materially the same (system prompt has a minor cache control metadata difference).

Three Key Questions — Final Answers

1. Tools: How are they disabled?

Two layers of defense, but only one is visible on the wire:

Layer Mechanism Visible on wire? Evidence
Prompt injection <system-reminder> says "You have NO tools available" Yes — in last user message Wire capture
Client-side deny canUseTool callback returns {behavior: "deny"} No — client intercepts before execution Source (line 447785)
API-level restriction None — full tools array sent, no tool_choice: none Confirmed absent Wire capture

The model sees tool definitions and is told via prompt not to use them. If it tries anyway, the client blocks execution. This is a belt-and-suspenders approach, not an API-level restriction.

2. Messages: What's the relationship to the main conversation?

Confirmed by wire capture: context fork. The current conversation context is sent, with the btw question appended as a final user message. The response is not written back to the main conversation.

What the wire data shows:

  • BTW requests contain prior user and assistant messages from the current session
  • The BTW question is appended as the last user message
  • BTW captures (006, 009, 011) contain only text content blocks — no tool_use or tool_result blocks appear in any BTW capture

What the wire data does NOT prove:

  • Whether the full untruncated conversation history is sent (BTW captures were from short sessions with limited prior turns)
  • Whether tool_use/tool_result blocks would be included in a longer session (they are present in later NORMAL captures 013/014, but no BTW was captured after tool-heavy interaction)

What the source reveals — context assembly with filtering:

  1. forkContextMessages: Y.messages (line 447847) passes the current context object's messages array into me4
  2. Inside hv() (line 441766), forkContextMessages is extracted
  3. The final query messages are assembled at line 441769: Z = [...VF1(P), ...A] — where P is the fork context and A is the new prompt messages
  4. VF1 (line 380492) filters the context: it removes assistant tool_use blocks that have no matching tool_result. This means /btw does not send a raw copy of the conversation — it sends a filtered fork where orphaned tool calls are stripped

The VF1 filter proves that the fork context is normalized before sending — it is a known source-level mechanism that removes tool blocks. However, VF1 alone does not fully explain why the current wire samples contain no tool_use/tool_result at all: the BTW captures were taken from sessions with limited prior tool interaction, so the absence may also reflect that few or no tool exchanges existed in the context at capture time.

Fork isolation — response is not written back:

  • YR(z) (line 441770, defined at 265944) generates a separate agent ID in the format a${forkLabel}-${hex} (e.g., aside_question-a1b2c3)
  • The fork's messages are recorded to a separate transcript via h31(...) (lines 441773, 441804)
  • This is not just "the response isn't used" — the fork has its own identity and transcript from the start

Additional client-side behaviors (from source, not visible on wire):

  • forkLabel: "side_question" — tags the fork for internal tracking (source line 447791)
  • skipCacheWrite: true — direct parameter in the /btw call path, dual static evidence: npm bundle (WR(... skipCacheWrite:!0)) and native binary (qL(... skipCacheWrite:!0))
  • maxTurns: 1 — client stops after one assistant response (source line 447792)

3. System Reminder: Where is it injected?

Confirmed by wire capture: embedded in the last user message's content field as a plain string with <system-reminder> tags. Not in the system prompt array. The system prompt array is unchanged between NORMAL and BTW requests.

Command Registration

Verbatim from cli.js lines 447970-447984:

// cli.js:447970-447984 — verbatim
((UpY = {
    type: "local-jsx",
    name: "btw",
    description:
      "Ask a quick side question without interrupting the main conversation",
    isEnabled: () => !1,
    isHidden: !1,
    immediate: !0,
    argumentHint: "<question>",
    load: () => Promise.resolve().then(() => (Qe4(), Fe4)),
    userFacingName() {
      return "btw";
    },
  }),
    (fSA = UpY));

isEnabled: () => !1 (false): This does not mean the command is disabled. The isEnabled field controls whether the command appears in the command list used for autocomplete and /help (see line 506532: .filter((D) => D.isEnabled())). Commands with isEnabled: false can still be triggered by direct regex match. Cross-evidence: /files (line 492017) and /tag (line 500698) also have isEnabled: () => !1 and are confirmed functional via direct invocation.

immediate: !0 (true): Executes immediately when the user submits, no confirmation step.

Regex trigger: /^\/btw\b/gi (line 447806)

Call Flow

Traced from source with line references. All function names are minified identifiers.

User types "/btw xxx"
  │
  ├─ Regex /^\/btw\b/gi matches (line 447806)
  │
  ├─ QpY(onDone, context, args) — command handler (line 447950)
  │  └─ If empty args → "Usage: /btw <your question>"
  │  └─ Otherwise → createElement(mpY, {question, context, onDone})
  │
  ├─ mpY(props) — React (Ink) component (line 447810)
  │  │
  │  ├─ useEffect: Promise.all([
  │  │    qN(tools, model, [], mcpClients),  // build system prompt (line 447838)
  │  │    xO(),                               // user context (line 447844)
  │  │    IO(),                               // system context (line 447845)
  │  │  ])
  │  │
  │  └─ me4({ question, cacheSafeParams }) — API call (line 447847 → 447765)
  │     ├─ Wraps question in <system-reminder> user message
  │     ├─ Calls hv() — main inference loop (line 447765 → 441766), with:
  │     │   ├─ maxTurns: 1 (client-enforced)
  │     │   ├─ canUseTool: deny all (client-enforced)
  │     │   ├─ forkContextMessages → VF1() filters orphaned tool_use (line 441769, 380492)
  │     │   ├─ YR() generates fork agent ID: aside_question-{hex} (line 441770, 265944)
  │     │   ├─ h31() records to separate fork transcript (lines 441773, 441804)
  │     │   └─ Wire result: full tools, adaptive thinking, same system prompt
  │     └─ Extracts first text block from assistant response
  │
  ├─ Display response (or error)
  │
  └─ "Press Space, Enter, or Escape to dismiss" (key handler, line 447824)

UI (React/Ink)

The component mpY renders:

  1. /btw label (yellow/warning color, bold) + question text (dimmed)
  2. While loading: spinner animation + "Answering..." (warning color)
  3. On success: response text
  4. On error: error message (red)
  5. Dismiss hint: "Press Space, Enter, or Escape to dismiss"

Summary Table

Aspect Implementation Evidence
Tool definitions on wire All 9 tools sent (Agent, Bash, Glob, etc.) Wire: 006_BTW_full.json
tool_choice on wire Absent (not set to none) Wire: 006_BTW_full.json
Client tool blocking canUseTool callback returns deny Source: line 447785
Thinking on wire {"type":"adaptive"} (despite source setting maxThinkingTokens: 0) Wire: 006_BTW_full.json
Message context Filtered context fork (VF1 strips orphaned tool_use) + btw appended Source: lines 441769, 380492 + Wire: 006_BTW, 011_BTW
System prompt Semantically identical; minor cache control ttl metadata diff Wire: 005_NORMAL_full.json vs 006_BTW_full.json
System reminder <system-reminder> tag in last user message content Wire + Source: line 447770
Cache behavior skipCacheWrite: true — no cache pollution Dual static: npm bundle (WR(... skipCacheWrite:!0)) + native binary (qL(... skipCacheWrite:!0))
Turn limit maxTurns: 1 — client-enforced single response Source: line 447792
Side effects on main conversation None — fork gets separate agent ID (aside_question-{hex}) and transcript Source: lines 441770, 265944, 441773
Trigger Regex /^\/btw\b/gi Source: line 447806
Command type local-jsx, immediate: true Source: lines 447971, 447977
isEnabled: false Not disabled — only excluded from autocomplete/help listing Source: line 447975 + line 506532

Corrections to Initial Analysis

Initial claim Correction How discovered
"Thinking disabled (maxThinkingTokens: 0)" Wire shows thinking: {type: "adaptive"} — thinking is NOT disabled at API level Wire capture
"Tools disabled" (ambiguous) Tools are fully sent in the API request; blocking is client-side only Wire capture
"isEnabled: false means hidden from autocomplete" More precise: isEnabled controls inclusion in command list filter (line 506532), but does not prevent regex-triggered execution Source trace
Codex: "still not 100% sure about wire tools" Confirmed: all 9 tools present, no tool_choice restriction Wire capture
"Full/complete main conversation history" Context is filtered by VF1 (line 380492) before sending — orphaned assistant tool_use blocks without matching tool_result are stripped Source trace (Codex round 4)

Reproduction

Dynamic Wire Capture Method

# 1. Create fetch hook
cat > /tmp/cc-fetch-hook.js << 'EOF'
const fs = require("fs");
const path = require("path");
const CAPTURE_DIR = "/tmp/btw-captures";
require("fs").mkdirSync(CAPTURE_DIR, { recursive: true });
let reqCount = 0;
const origFetch = globalThis.fetch;
globalThis.fetch = async function(url, options) {
  if (options?.body && (String(url).includes("messages") || String(url).includes("anthropic"))) {
    reqCount++;
    try {
      const body = JSON.parse(options.body);
      const msgs = body.messages || [];
      const last = msgs[msgs.length - 1];
      let content = "";
      if (last) {
        const c = last.content;
        if (typeof c === "string") content = c;
        else if (Array.isArray(c)) content = c.filter(b => b.type === "text").map(b => b.text).join("\n");
      }
      const isBtw = content.toLowerCase().includes("side question");
      const label = isBtw ? "BTW" : "NORMAL";
      const n = String(reqCount).padStart(3, "0");
      fs.writeFileSync(path.join(CAPTURE_DIR, `${n}_${label}_full.json`), JSON.stringify(body, null, 2));
      fs.writeFileSync(path.join(CAPTURE_DIR, `${n}_${label}_summary.json`), JSON.stringify({
        request_number: reqCount, model: body.model, stream: body.stream,
        tools_count: (body.tools||[]).length, tool_choice: body.tool_choice,
        messages_count: msgs.length, thinking: body.thinking, is_btw: isBtw,
      }, null, 2));
    } catch(e) {}
  }
  return origFetch.call(this, url, options);
};
EOF

# 2. Launch Claude Code with hook
rm -rf /tmp/btw-captures && mkdir -p /tmp/btw-captures
NODE_OPTIONS="--require /tmp/cc-fetch-hook.js" claude

# 3. In Claude Code: send "hello", then "/btw what is this?"
# 4. Check captures:
ls /tmp/btw-captures/
cat /tmp/btw-captures/*_BTW_summary.json
# Compare with normal: cat /tmp/btw-captures/*_NORMAL_summary.json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment