* feat(viewer): explain prompt cache misses in the turn detail A turn that misses the prompt cache shows Cache Read 0 with a large Cache Create, but the token row alone cannot say why: a cold start and a turn that idled past the 5-minute cache TTL look identical. Reviewers had to cross-check timestamps by hand to tell them apart. Add a diagnostic card above the detail sections that names the reason. Cold start reads "Initial prompt cache creation"; a gap longer than the TTL reads "Cache expired (idle longer than 5 min)". Turns that extend the cache normally render no card, so the card's presence is itself a signal. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(viewer): cover the cache diagnostic card in the contract corpus Viewer CSS and JS coverage are measured by sweeping the entries produced by _contract_cases(), and none of those cases reported a cold cache write. The diagnostic card therefore never rendered during the sweep, so all five of its new selectors counted as unexercised and viewer_css_diff came out at 0%. Add a cache_diagnostic_card case with three cold-write entries covering the initial, TTL-expired, and unidentifiable-cause paths. The last one renders the low-confidence variant, so .cache-diag-card.low-confidence is exercised too. _cache_diag_record moves above _contract_cases() because the parametrize decorator calls it at import time. viewer_css_diff: 0% -> 100% (5/5). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(viewer): only claim a cache miss cause the payloads support Anthropic writes a small delta to the cache on nearly every turn, so cache_creation > 0 was not the evidence the diagnosis treated it as: only a write with no accompanying read means the prefix was genuinely cold. Follow the prompt hash chain when attributing the miss. The prompt is hashed as an ordered chain -- tools, then system, then messages -- so cache_read == 0 proves the first segment missed and an edit in a later segment cannot be the cause. Compare only the region a breakpoint actually covers, so appending a message beyond the last breakpoint stays the normal path instead of being reported as an invalidating edit, and name the earliest changed segment rather than a later one that would send the reader looking in the wrong place. Read the TTL tier from the request's cache_control, falling back to the response's cache_creation tier, and return 0 when neither names one. The previous response-only read made the 5-minute default the normal path and turned unknown lifetimes into confident expiry claims. Pick the predecessor from the unfiltered history: the sidebar filters are a viewing choice and must not change what caused a miss. Candidates have to share the model and conversation and have taken part in caching themselves, and a shared session confirms a predecessor whose earlier messages were rewritten -- which the message-prefix test alone rejects. Sweep renderCacheDiagnostic while the full corpus is loaded in the coverage harness; live mode narrows entries to two websocket records before the detail loop, so no cache-bearing entry was ever rendered. * fix(viewer): address Codex review on predecessor selection and full content normalization * fix(viewer): address follow-up review on path-encoded models, structural exactness, and non-cold guard * fix(viewer): support Bedrock Converse cache points and prefix-bounded diffs * fix(viewer): wire up the cache prefix bounds and rank expiry first The previous round added consumers for `prevScopes.toolCount`, `prevScopes.systemCount`, `prevScopes.bps` and `bp.blockIndex`, but nothing ever produced them, so every prefix bound degraded to `Infinity`/`undefined` and the comparisons still spanned whole scopes. - `cachedScopes` now reports `toolCount`, `systemCount` and the breakpoint list, so tool and system comparison stop at the cached breakpoint instead of running to the end of the scope. - `cacheBreakpoints` records `blockIndex` and recognizes Bedrock Converse `cachePoint` markers, which cache what precedes them. - Without any breakpoint the extent of each segment is unknown, so no scope is claimed as cached. - Expiry is evaluated before the structural verdicts and requires an exact predecessor, so an edit made after the lifetime elapsed is no longer named as the cause. - A missing predecessor only reads as the initial turn when the trace shows the conversation starting. - In remote dashboard mode the card renders a pending state and upgrades once the predecessor payload loads. * fix(viewer): close four cache-diagnosis gaps from review Read Converse tool specs from their own list. `getRequestTools` is a display helper: it deduplicates by name, merges in tools observed only in responses, drops the standalone `cachePoint` marker, and never looks at `body.toolConfig`. A cache comparison needs the bytes the request sent in the order it sent them, and the marker is the only thing declaring how far the cached tool prefix reaches, so `cacheToolList` reads Converse's nested list directly and attributes the marker to the spec ahead of it. Without it a tool-scoped Converse cache reads as unknown extent and a later history change gets blamed for a miss the tool edit caused. Take the longest lifetime either source names. A turn reusing a 1-hour prefix while appending a 5-minute tail bills only the 5-minute tokens it wrote, so reading `cache_creation` alone makes the 1-hour prefix look expired after six minutes -- suppressing an edit inside a still-live prefix and reporting expiry instead. Report a cached prefix that got shorter. Iterating only the common portion never compares the dropped tail, so truncation went unreported. A predecessor that also declared a breakpoint at the shorter boundary left a live entry there, so that case is excluded rather than blamed; growing the prefix is the normal incremental path and is not a cause at all. Re-ask the exactness question after fetching the predecessor. A dashboard stub carries no session header and no message bodies, so the search can only record `exact: false`; every structural and TTL verdict is gated on that flag, which left multi-message Claude turns permanently unknown however complete the fetched payload was. The stub's positive verdict is kept as a floor, since the entry being diagnosed may itself be a stub. * fix(viewer): close five remaining cache-diagnosis review gaps Compare empty tool lists when a later breakpoint covers that segment, so adding the first tool or removing the last one is a tool miss. Confirm predecessors with full normalized messages instead of the 500-character hash. Continue the existing dashboard walk after an inexact fetched fallback, replace the pending card when the records API fails, and ignore marker-only Bedrock cachePoint messages when detecting a conversation start. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(viewer): prefer exact predecessors and avoid blaming later edits Headerless scans keep walking past an inexact neighbor, a leading message cache point still covers tools and system, and an unchanged earlier breakpoint makes a later edit an unknown miss rather than a confident structural cause. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(viewer): read the model out of nested Vertex paths The cache-diagnostics model check required `/v1/models/<name>` directly, but Vertex nests the name under the publisher: /v1/projects/p/locations/l/publishers/anthropic/models/claude-opus-4-7:rawPredict Every Vertex turn therefore reported no model at all, so a model switch looked like a single cache chain and the cold write collected a structural or TTL diagnosis it had not earned. Match any `/models/` segment, the way the backend's `_model_from_path` already does. * fix(viewer): compare the tool list the request actually sent `cacheToolList` fell through to `getRequestTools`, which ends in `uniqueToolsByDisplayName` for display. Two definitions can share a name, and dropping every one after the first meant editing or removing a later duplicate inside the cached prefix left `toolsChanged` false -- the cold write came back unknown, or was blamed on a later segment. The cache is keyed on the bytes sent, so use `body.tools` verbatim when it is there and keep `getRequestTools` only for the shapes that hold tools elsewhere. * fix(viewer): treat a shortened system prefix as a system change `diffCachedRegion` bounded the system comparison to the shorter of the two block lists, so a predecessor that cached [A, B] against a request sending [A] compared only the surviving block, found it equal, and reported no change -- when the shorter prefix was the very reason the lookup went cold. The message path already had this branch; mirror it for system blocks, with the same guard: shrinking is a cause only when the predecessor did not also declare a breakpoint at the surviving boundary, since such an entry would still be hit. * fix(viewer): find the session start in the trace, not the message count A first cache write is not necessarily a first turn. A session stays under the provider's minimum cacheable size while its prompt is short, so the opening turns write nothing and are rightly skipped as predecessors -- but by the time one turn crosses the threshold its own message list already holds the whole replayed exchange. Asking only about that count called the capture mid-session and the card fell back to unknown. Look for the opening turn among the earlier same-session entries instead: if the capture saw the session start and no earlier turn established a cache, there is no unseen predecessor and this is the initial write. * fix(viewer): treat a shortened tool prefix as a tool change The cached tool segment can break by getting shorter, exactly as the system and message segments can. Bounding the comparison to the shorter extent walks only the surviving specs, so a predecessor that cached [Read, Write] against a request caching [Read] found every compared spec equal and reported no change -- though the dropped spec is why the lookup went cold. Shrinking is a cause only when the shorter prefix was not itself an entry: a predecessor that also declared a breakpoint at the surviving boundary left one there that this request would still hit. * fix(viewer): report a breakpoint moved back inside one message A breakpoint that moves from a message's second content block to its first truncates the cached prefix inside that message. The bounded comparison walked only the surviving block, found it equal, and left the miss unexplained. Same rule as for whole messages: it is a cause unless the predecessor declared a breakpoint at the surviving block too, which left an entry there this request would still hit. * fix(viewer): honour an unchanged earlier message checkpoint The tool and system segments already declined to name a cause the trace contradicts; the messages need the same argument one segment further in. When both sides declared a breakpoint at message A and the edit landed at a later checkpointed message B, the entry at A should still have matched and produced reads. Zero reads say it did not, so naming B would state a definite cause the capture refutes. diffCachedRegion now reports which message first differs, which is what lets the diagnosis ask whether anything checkpointed sits ahead of it. * fix(viewer): strip cache markers only where the protocol puts them cache_control and cachePoint are metadata at three positions: a tool spec, a system block, and a message content block. Erasing the names recursively also erased ordinary payload that happens to share them -- a tool schema property called cache_control, or the same key inside a cached tool_use input -- so an edited payload compared equal to its predecessor and the invalidation it caused fell through to the unknown diagnosis. The markers still have to go from those three positions, or moving one would read as a content edit rather than the truncation it is. * fix(viewer): let the trace prove a first write past an inexact predecessor findCachePredecessor keeps a headerless cache-bearing neighbor as a fallback because it cannot rule the turn out. Nothing gated on prevIsExact can speak about such a candidate, but the absence of evidence in it is not the absence of evidence: the capture can still hold this session opening with no same-session turn having cached anything ahead of it, which is what an initial write is. Without this the diagnosis depended on whether an unrelated conversation happened to be interleaved in the capture -- merely having one turned a provable cache_miss_initial into unknown. * fix(viewer): drop standalone cachePoint elements when comparing content Bedrock states a cache breakpoint as its own array element rather than as a property on a content block. Stripping the marker key in place left an empty `{}` occupying that slot, so relocating an intermediate breakpoint shifted the placeholder and compared unequal -- reporting a *definite* tool, system or history edit against two identical prompts. Where the breakpoints sit is already `cachedScopes`' job; this comparison is only about content, so marker-only elements are dropped outright. A block that carries a marker alongside real content keeps its place, since that content still has to be compared. The regression test relocates an intermediate marker while a tail breakpoint holds the cached extent equal on both sides, which is the only shape where just the placeholder moved -- shortening the extent instead takes an earlier exit in `diffCachedRegion` and would not reach normalization. * refactor(viewer): state the cache-miss guard once instead of three times The tools, system and history branches each carried their own copy of the same argument: a checkpoint ahead of the change, declared on both sides and itself unchanged, should have produced cache reads, so zero reads contradict blaming the later change. Three spellings of one invariant meant every adjustment had to be made three times and could drift apart. The branches become a `SEGMENTS` list in prompt-hash order, so "ahead" is just the slice before the changed entry. Messages additionally carry `insideHit`, the same test applied *within* a segment at block granularity -- tools and system are compared whole, so a change there has nothing of its own ahead of it and gets no such test. Behaviour is unchanged: the tools branch still names directly, system still defers to an unchanged declared tools checkpoint, and history still defers to either of those or to a shared message breakpoint before the edit. * refactor(viewer): only name a cache miss when both sides cached the same extent The diagnosis used to attribute a miss whenever the cached region got shorter -- a tool prefix that lost entries, a system prefix that lost entries, a breakpoint that moved back inside a message. Naming a cause there requires knowing which of several declared breakpoints the provider actually matched, and the trace does not record that. A request may declare four breakpoints and hit on any of them; a shorter extent is consistent with an edit, with expiry, and with eviction alike. Compare the two extents instead, and only single out a cause when they agree. When they differ -- by message count, by segment bound, or by which block of a message carries the marker -- report `cache_miss_unknown`, which already reads as a description of the evidence rather than a finding. Edits inside an identically bounded region are still named, so the confident verdicts that were actually supported survive. Two cleanups fell out of the review: `cachedScopes` wrote an `extentUnknown` field nothing read (dead before this branch too), and its `usage` parameter went unused once that left; and four sites hand-built the same verdict literal, now routed through `cacheDiagnosticUnknown()` and `cacheDiagnosticNamed()`. Verified: embedded JS unit block exits clean, 38 viewer tests pass, ruff check and format clean, and all four coverage gates pass (viewer_js_diff 86.11%). --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
claude-tap
claude-tap is a local proxy and trace viewer for AI coding agents. Run your CLI through it, or listen to local app transcripts, then inspect the real API traffic and agent context: system prompts, conversation history, tool schemas, tool calls, streaming responses, token usage, and request diffs.
Website: Local AI Agent Trace Viewer · Guide: How to view agent traces locally
It works with Claude Code, Codex CLI, Codex App, Gemini CLI, Grok Build CLI, Kimi CLI, MiMo Code, OpenCode, OpenClaw, Pi, Hermes Agent, Cursor CLI, Qoder CLI, Antigravity CLI, and CodeBuddy CLI.
Open a real agent run, inspect every request, and compare how context changes between turns.
Light viewer overview |
Dark mode for long review sessions |
Structured diff across adjacent requests |
Built with claude-tap
|
Phistory archives versioned system prompt snapshots from agent CLIs such as Claude Code, Codex, Kimi, opencode, and Pi. It uses claude-tap's capture-only prompt export to preserve raw HTTP trace evidence and generate comparison-friendly prompt snapshots.
Open the prompt diff viewer · View repository |
|
Why use it
- 👀 See the exact context: inspect prompts, messages, tool definitions, tool calls, tool results, reconstructed streaming responses, and token usage.
- 🔎 Debug behavior with evidence: compare adjacent requests and pinpoint which prompt, message, tool, or parameter changed.
- 📦 Share one portable artifact: each run writes a local trace session that can be exported to a self-contained HTML viewer for review or archiving.
- 🔒 Keep traces on your machine: no hosted dashboard is required, and common auth headers are redacted before recording.
- 🧩 Use one workflow across clients: trace Claude Code, Codex CLI, Codex App, Gemini CLI, Grok Build CLI, DeepSeek Harness, Kimi CLI, MiMo Code, OpenCode, OpenClaw, Pi, Hermes Agent, Cursor CLI, Qoder CLI, and CodeBuddy.
Supported Clients
| Client | Typical use |
|---|---|
| Claude Code | Anthropic API, AWS Bedrock, Claude-compatible gateways such as DeepSeek / GLM, or local proxy upstreams such as CC Switch |
| Codex CLI | OpenAI API key mode or ChatGPT subscription OAuth |
| Codex App | Desktop app launched through forward proxy mode so backend HTTP/WebSocket request bodies are captured |
| Gemini CLI | Google OAuth / Code Assist traffic |
| Grok Build CLI | Grok subscription OAuth sessions through the official CLI chat proxy |
| DeepSeek Harness | dsh headless tasks and custom profiles using DeepSeek or compatible gateways |
| Kimi CLI | Legacy kimi-cli and the newer Kimi Code CLI |
| MiMo Code | MiMo Code sessions (OpenCode fork with multi-provider support) |
| OpenCode | Multi-provider OpenCode sessions |
| OpenClaw | Multi-provider OpenClaw sessions |
| Pi | Pi sessions, including OpenAI Codex OAuth providers |
| Hermes Agent | Multi-provider Hermes TUI or gateway sessions |
| Cursor CLI / IDE Agent | Launch cursor-agent + live transcript watch (claude-tap --tap-client cursor) |
| Qoder CLI | Qoder Agent sessions through forward proxy mode |
| Antigravity CLI | Antigravity Agent sessions through forward proxy mode |
| CodeBuddy CLI | Tencent CodeBuddy SaaS or internal Copilot endpoint |
Install
Requires Python 3.11+ and the client you want to trace.
# Recommended
uv tool install claude-tap
# Or with pip
pip install claude-tap
Upgrade: claude-tap update, uv tool upgrade claude-tap, or pip install --upgrade claude-tap
Quick Start
Run the client you want to inspect through claude-tap. Flags after -- are passed to the selected client.
# Claude Code with the live browser viewer enabled by default
claude-tap
# Restore pre-v0.1.75 behavior: no live viewer server
claude-tap --tap-no-live
# Codex CLI
claude-tap --tap-client codex
# Codex App backend request capture
claude-tap --tap-client codexapp
# Gemini CLI
claude-tap --tap-client gemini -- -p "hello"
# Grok Build CLI
claude-tap --tap-client grok -- -p "hello"
# DeepSeek Harness headless task
claude-tap --tap-client dsh -- --profile headless "Reply OK"
# Kimi CLI
claude-tap --tap-client kimi
# New Kimi Code CLI
claude-tap --tap-client kimi-code
# MiMo Code (OpenCode fork)
claude-tap --tap-client mimo
# Pi
claude-tap --tap-client pi -- --model openai-codex/gpt-5.3-codex-spark -p "hello"
# Cursor: launch cursor-agent + live transcript watch + dashboard
claude-tap --tap-client cursor
# Watch IDE Agent transcripts only (no CLI launch)
claude-tap --tap-client cursor --tap-no-launch
# Qoder CLI
claude-tap --tap-client qoder -- -p "hello" --permission-mode dont_ask
# Antigravity CLI
claude-tap --tap-client agy
# CodeBuddy CLI
claude-tap --tap-client codebuddy
Claude Code examples
# Pass flags through to Claude Code
claude-tap -- --model claude-opus-4-6
claude-tap -c # continue last conversation
# Skip all permission prompts (auto-accept tool calls)
claude-tap -- --dangerously-skip-permissions
# Live viewer is on by default; pass Claude flags after --
claude-tap -- --dangerously-skip-permissions --model claude-sonnet-4-6
claude-tap auto-detects custom Claude Code upstreams from ANTHROPIC_BASE_URL,
ANTHROPIC_BEDROCK_BASE_URL, or ANTHROPIC_VERTEX_BASE_URL in your environment
or Claude settings. Use --tap-target only when you want to override that
detected target.
Local proxy upstreams are supported too: if a tool such as CC Switch points Claude Code at a local ANTHROPIC_BASE_URL, claude-tap detects that value from Claude settings and records the traffic before forwarding it upstream. Use claude-tap in place of claude, such as claude-tap -- <claude-args>; no separate --tap-client value is needed.
For the Claude Code VS Code extension, set Claude Code: Claude Process Wrapper to claude-tap; on Windows, use the full claude-tap.exe path if VS Code cannot find it.
Claude Code with DeepSeek API
Full English guide: Claude Code with DeepSeek API. Simplified Chinese version: Claude Code 搭配 DeepSeek API.
export ANTHROPIC_AUTH_TOKEN="<your DeepSeek API key>"
unset ANTHROPIC_API_KEY
export ANTHROPIC_MODEL="deepseek-v4-pro[1m]"
export ANTHROPIC_DEFAULT_OPUS_MODEL="deepseek-v4-pro[1m]"
export ANTHROPIC_DEFAULT_SONNET_MODEL="deepseek-v4-pro[1m]"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="deepseek-v4-flash"
export CLAUDE_CODE_SUBAGENT_MODEL="deepseek-v4-flash"
export CLAUDE_CODE_EFFORT_LEVEL=max
export ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic
claude-tap -- --permission-mode bypassPermissions
claude-tap reads the DeepSeek upstream from ANTHROPIC_BASE_URL, then launches Claude Code against the local proxy. Use --tap-target https://api.deepseek.com/anthropic only as a manual override.
Claude Code with AWS Bedrock
claude-tap supports three Bedrock scenarios and auto-detects which applies:
Anthropic-compatible Bedrock gateway (New API or similar, no SigV4 in Claude Code)
export ANTHROPIC_AUTH_TOKEN="<your gateway token>"
unset ANTHROPIC_API_KEY
export ANTHROPIC_BASE_URL="https://new-api.example.com"
export ANTHROPIC_MODEL="bedrock/claude-opus-4-6"
export ANTHROPIC_DEFAULT_OPUS_MODEL="bedrock/claude-opus-4-6"
export ANTHROPIC_DEFAULT_SONNET_MODEL="bedrock/claude-opus-4-6"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="bedrock/claude-opus-4-6"
claude-tap -- --model bedrock/claude-opus-4-6
claude-tap records the normal Claude Code /v1/messages HTTP/SSE traffic, then
forwards it to the gateway. For model names prefixed with bedrock/, it removes
Claude Code beta-only request options that AWS Bedrock rejects while preserving
the captured trace.
Custom Bedrock gateway (company proxy, no SigV4)
export CLAUDE_CODE_USE_BEDROCK=1
export ANTHROPIC_BEDROCK_BASE_URL="https://your-gateway.company.com/bedrock"
claude-tap
claude-tap detects the non-AWS host, redirects both ANTHROPIC_BASE_URL and ANTHROPIC_BEDROCK_BASE_URL to the local proxy, and decodes the AWS EventStream binary response format to extract token usage and model info.
AWS native Bedrock (SigV4-signed requests)
export CLAUDE_CODE_USE_BEDROCK=1
export ANTHROPIC_BEDROCK_BASE_URL="https://bedrock-runtime.us-east-1.amazonaws.com"
export AWS_REGION="us-east-1"
claude-tap --tap-proxy-mode forward
When the endpoint is a real AWS domain (*.amazonaws.com), claude-tap does not rewrite ANTHROPIC_BEDROCK_BASE_URL to localhost — doing so would break AWS SigV4 signature validation. Use forward proxy mode (--tap-proxy-mode forward) to capture this traffic without modifying the signed request.
Use --tap-target only as a manual override when auto-detection does not apply.
Claude Code with Google Vertex AI
claude-tap supports Claude Code Vertex pass-through gateways that expose the
Vertex rawPredict, streamRawPredict, and count-tokens:rawPredict paths.
export CLAUDE_CODE_USE_VERTEX=1
export CLOUD_ML_REGION="us-east5"
export ANTHROPIC_VERTEX_PROJECT_ID="your-project-id"
export ANTHROPIC_VERTEX_BASE_URL="https://your-gateway.company.com/vertex"
export CLAUDE_CODE_SKIP_VERTEX_AUTH=1 # when your gateway handles auth
claude-tap
When CLAUDE_CODE_USE_VERTEX=1 and ANTHROPIC_VERTEX_BASE_URL is set,
claude-tap detects that upstream, redirects both ANTHROPIC_BASE_URL and
ANTHROPIC_VERTEX_BASE_URL to the local proxy, and records Vertex rawPredict
HTTP/SSE traffic. If Claude Code uses native Google Vertex without
ANTHROPIC_VERTEX_BASE_URL, use forward proxy mode or set the base URL
explicitly so reverse mode has a single target to forward to.
Codex CLI auth modes and examples
Codex CLI supports two authentication modes with different upstream targets:
| Auth Mode | How to authenticate | Upstream target | Notes |
|---|---|---|---|
| OAuth (ChatGPT subscription) | codex login |
https://chatgpt.com/backend-api/codex |
Default for ChatGPT Plus/Pro/Team users |
| API Key | Set OPENAI_API_KEY |
https://api.openai.com (default) |
Pay-per-use via OpenAI Platform |
claude-tap auto-detects the Codex target from your auth state when possible.
In the default reverse-proxy mode, it launches Codex with a temporary sibling
provider whose supports_websockets setting is disabled. This produces one
HTTP/SSE trace record per request with the complete request context and does
not modify ~/.codex/config.toml.
# OAuth users (ChatGPT Plus/Pro/Team) — auto-detected after `codex login`
claude-tap --tap-client codex
# If auto-detection cannot read your Codex auth file, specify the target explicitly
claude-tap --tap-client codex --tap-target https://chatgpt.com/backend-api/codex
# API Key users — default OpenAI API target works out of the box
claude-tap --tap-client codex
# With specific model
claude-tap --tap-client codex -- --model codex-mini-latest
# Full auto-approval (skip all permission prompts)
claude-tap --tap-client codex -- --full-auto
# OAuth + full auto; live viewer is enabled by default
claude-tap --tap-client codex -- --full-auto
Codex App backend capture examples
Codex App is launched through claude-tap's forward proxy so the final /backend-api/codex/responses HTTP and WebSocket request bodies can be captured in the same trace viewer as other clients. Current macOS installs ship as ChatGPT.app (bundle id com.openai.codex); older standalone Codex.app installs are still recognized. Non-model product traffic is relayed but not persisted as trace rows. On macOS, claude-tap trusts its local CA in the current user's login keychain when needed so the bundled app-server can connect through the proxy.
# Launch Codex App (ChatGPT.app or Codex.app) and inspect captured backend requests
claude-tap --tap-client codexapp
# Keep raw WebSocket/SSE event arrays in the trace
claude-tap --tap-client codexapp --tap-store-stream-events
# Override the executable when the app is installed outside the default locations
CODEX_APP_EXECUTABLE=/path/to/ChatGPT.app/Contents/MacOS/ChatGPT claude-tap --tap-client codexapp
If Codex/ChatGPT App is already running, claude-tap launches an isolated second instance with a dedicated --user-data-dir under ~/.claude-tap/codex-app-profiles/tap so your current window keeps working. You may need to sign in again in the tapped window. Override the profile with CODEX_APP_USER_DATA_DIR. This mode records live backend traffic instead of importing local session JSONL transcripts.
Kimi CLI examples
Use --tap-client kimi for legacy kimi-cli, or --tap-client kimi-code for the newer Kimi Code CLI. Both use reverse proxy mode by default.
claude-tap --tap-client kimi
claude-tap --tap-client kimi -- --thinking
claude-tap --tap-client kimi --tap-target https://api.moonshot.ai/v1
claude-tap --tap-client kimi-code
claude-tap --tap-client kimi-code -- --thinking
claude-tap --tap-client kimi-code --tap-target https://api.moonshot.ai/v1
Gemini CLI examples
Gemini CLI uses forward proxy mode by default. Google OAuth / Code Assist traffic goes to several Google endpoints, so forward proxy capture is the safest default. Reverse mode remains available for API-key or Vertex-style flows that honor GOOGLE_GEMINI_BASE_URL or GOOGLE_VERTEX_BASE_URL.
# Google OAuth / Code Assist
claude-tap --tap-client gemini -- -p "hello"
# Live viewer is enabled by default
claude-tap --tap-client gemini -- -p "hello"
# Reverse mode for compatible API-key / Vertex flows
claude-tap --tap-client gemini --tap-proxy-mode reverse -- -p "hello"
OpenCode examples
OpenCode is a multi-provider terminal AI assistant. Because it can talk to many providers, claude-tap defaults to forward proxy mode for opencode: it injects HTTPS_PROXY plus the local CA into the child process so traffic to any provider is captured.
# Forward proxy mode — captures every provider opencode talks to (default)
claude-tap --tap-client opencode
# Live viewer is enabled by default
claude-tap --tap-client opencode
# Reverse mode — only works when using Anthropic provider (single ANTHROPIC_BASE_URL)
claude-tap --tap-client opencode --tap-proxy-mode reverse
MiMo Code examples
MiMo Code is an OpenCode fork with persistent memory, subagent orchestration, and Xiaomi MiMo platform integration. claude-tap defaults to forward proxy mode for mimocode: it injects HTTPS_PROXY plus the local CA into the child process so traffic to any provider is captured.
# Forward proxy mode — captures every provider MiMo Code talks to (default)
claude-tap --tap-client mimo
# Live viewer is enabled by default
claude-tap --tap-client mimo
# Reverse mode — single Anthropic provider with mimo-only disabled
claude-tap --tap-client mimo --tap-proxy-mode reverse
Pi examples
Pi is a multi-provider coding agent. claude-tap defaults to forward proxy mode for Pi because Pi can use subscription OAuth providers such as openai-codex and custom API-key providers from its model registry.
# OpenAI Codex OAuth via Pi's openai-codex provider
claude-tap --tap-client pi -- --model openai-codex/gpt-5.3-codex-spark -p "hello"
# Live viewer is enabled by default
claude-tap --tap-client pi -- --model openai-codex/gpt-5.3-codex-spark -p "hello"
# Read-only tool capture
claude-tap --tap-client pi -- --model openai-codex/gpt-5.3-codex-spark --tools bash -p "Run pwd"
Pi stores OAuth credentials in ~/.pi/agent/auth.json after /login. If you keep Pi credentials in another directory, set PI_CODING_AGENT_DIR before launching claude-tap.
Hermes Agent examples
Hermes Agent is a multi-provider Python AI agent (Nous Portal, OpenRouter, NVIDIA NIM, Xiaomi MiMo, GLM, Kimi, MiniMax, Hugging Face, OpenAI, Anthropic, custom). Because it can talk to any of these providers — and httpx / requests both honor HTTPS_PROXY natively — claude-tap defaults to forward proxy mode for hermes: it injects HTTPS_PROXY plus the local CA into the child process so any provider is captured.
# Interactive TUI — the recommended way for local trace capture.
claude-tap --tap-client hermes
# Gateway mode — captures LLM calls triggered by incoming platform messages (Slack, Telegram, etc.).
# Requires a messaging platform configured in ~/.hermes/.env.
# claude-tap auto-rewrites `gateway start` → `gateway run` so the gateway runs in the
# foreground and inherits HTTPS_PROXY; without this, the daemon spawned by systemd/launchd
# would not go through the proxy and no traces would be recorded.
claude-tap --tap-client hermes -- gateway start
# Reverse mode is opt-in and only useful when ~/.hermes is configured with an
# OpenAI-compatible provider that reads OPENAI_BASE_URL.
claude-tap --tap-client hermes --tap-proxy-mode reverse
Note: Gateway mode only produces traces when a configured messaging platform (Slack, Telegram, etc.) delivers a message to the bot. Without an active platform integration, the gateway makes no LLM calls and no traces are recorded.
Cursor CLI / IDE Agent examples
Cursor is transcript-only (neither reverse nor forward proxy): claude-tap launches cursor-agent, watches ~/.cursor/projects/*/agent-transcripts/*.jsonl, and writes one dashboard session per Cursor conversation JSONL. It does not MITM api2.cursor.sh.
# Launch agent + live watch + dashboard (default)
claude-tap --tap-client cursor
# Pass args through to cursor-agent
claude-tap --tap-client cursor -- -p --trust --model auto "hello"
# IDE Agent only (no CLI launch)
claude-tap --tap-client cursor --tap-no-launch
Guides and Integrations
- OpenClaw setup guide for integrating
claude-tapwith OpenClaw. Simplified Chinese version: OpenClaw 设置指南. - Claude Code with DeepSeek API for routing Claude Code through DeepSeek's Anthropic-compatible API. Simplified Chinese version: Claude Code 搭配 DeepSeek API.
- Client support matrix for exact environment variables, proxy modes, and URL rewrite rules.
Qoder CLI examples
Qoder CLI talks to multiple Qoder endpoints, so claude-tap defaults to forward proxy mode for --tap-client qoder.
# Browser login, PAT, or job token must be configured before launch.
qodercli login
claude-tap --tap-client qoder -- -p "hello" --permission-mode dont_ask
Antigravity CLI examples
Antigravity CLI talks to multiple Google/Antigravity endpoints, so claude-tap defaults to forward proxy mode for --tap-client agy. Its Code Assist model API also honors CLOUD_CODE_URL; claude-tap injects that automatically so model requests such as /v1internal:streamGenerateContent are captured by the same local proxy.
On macOS, Antigravity may not honor per-process CA environment variables. claude-tap automatically trusts the local CA in your current user's login keychain on first agy launch. This does not use sudo or the System keychain, though macOS may prompt to unlock the login keychain.
claude-tap --tap-client agy --tap-live
# Optional: trust the CA separately before launching a forward-proxy client.
claude-tap trust-ca
Grok Build CLI examples
Grok Build uses reverse proxy mode by default. claude-tap temporarily points the official GROK_CLI_CHAT_PROXY_BASE_URL at the local proxy, captures the OpenAI Responses HTTP/SSE stream plus Grok storage and trace audit requests, and relays them to https://cli-chat-proxy.grok.com/v1 with the existing Grok OAuth session.
# Authenticate once with the official Grok Build CLI.
grok login
# Interactive TUI
claude-tap --tap-client grok
# Headless single turn
claude-tap --tap-client grok -- -p "Reply OK"
# Custom Grok-compatible deployment
GROK_CLI_CHAT_PROXY_BASE_URL=https://grok-gateway.example.com/v1 \
claude-tap --tap-client grok -- -p "Reply OK"
DeepSeek Harness examples
DeepSeek Harness (dsh) uses forward proxy mode by default. This captures model traffic whether the endpoint comes from DEEPSEEK_BASE_URL or a stored dsh model setting, including loopback gateways normally covered by NO_PROXY. The launcher verifies that Node supports --use-env-proxy, records only Chat Completions traffic, and passes all arguments after -- to dsh unchanged. If that Node capability is unavailable, upgrade Node or use reverse mode with an environment-configured endpoint.
# One-shot headless task
claude-tap --tap-client dsh -- --profile headless "Summarize this repository"
# Custom dsh profile
claude-tap --tap-client dsh -- --profile my-profile
# Reverse mode for deployments configured only through DEEPSEEK_BASE_URL
claude-tap --tap-client dsh --tap-proxy-mode reverse \
-- --profile headless "Reply OK"
CodeBuddy CLI examples
CodeBuddy uses reverse proxy mode by default. claude-tap auto-detects the upstream from CodeBuddy's own login cache (~/.codebuddy/local_storage/), so iOA / WeChat / Google-Github / Enterprise-Domain login modes all work without any extra flag. When the cache is missing (e.g. before first login), it falls back to https://copilot.tencent.com/v2.
# Auto-detected endpoint (works for all four login modes once logged in)
claude-tap --tap-client codebuddy
# Explicit override (e.g. external SaaS or staging)
claude-tap --tap-client codebuddy --tap-target https://www.codebuddy.ai/v2
# Or via environment variable
CODEBUDDY_BASE_URL=https://www.codebuddy.ai/v2 claude-tap --tap-client codebuddy -- -p "Reply OK"
Viewer, export, and advanced options
# Live viewer runs by default while a client runs
claude-tap
# Disable live viewer for scripts, CI, remote shells, or old behavior
claude-tap --tap-no-live
# Browse saved traces without launching a client
claude-tap dashboard
# Stop the shared dashboard service
claude-tap dashboard stop
# Build a local macOS menu bar app, then double-click it in Finder
claude-tap build-macos-app
open "dist/Claude Tap.app"
# Build an Apple Silicon app that bundles Python and dependencies
claude-tap build-macos-app --self-contained
# Restore Claude/Codex configs if the menu app is force-killed while monitoring
claude-tap monitor-restore
# Regenerate a self-contained HTML viewer from JSONL or compact trace input
claude-tap export .traces/2026-02-28/trace_141557.jsonl -o trace.html
# Export a portable compact trace bundle, then render it later.
# Compact is the default export format.
claude-tap export <session-id> -o trace.ctap.json
claude-tap export trace.ctap.json -o trace.html
# Embed the exported viewer in an iframe with reduced chrome
# trace.html?embed=1&hideHeader=1&hidePath=1&hideHistory=1&hideControls=1&density=compact&theme=light
# Store traces in another directory, or keep fewer sessions
claude-tap --tap-output-dir ./my-traces
claude-tap --tap-max-traces 10
# Start only the proxy for custom setups
claude-tap --tap-no-launch --tap-port 8080
# Disable browser auto-open for live and generated viewers
claude-tap --tap-no-open
In proxy-only mode, start your client in another terminal and point its base URL or proxy settings at the local proxy. Use the client support matrix for exact wiring.
When used as VSCode Claude Code's claudeProcessWrapper, claude-tap honors the Claude binary path passed by the extension.
On macOS, claude-tap build-macos-app creates a local Claude Tap.app bundle. The app runs as a menu bar item with a compact status board, Start Monitor / Stop Monitor controls, and a shortcut to the full dashboard. Start Monitor asks for confirmation, launches local reverse proxies for Claude Code and Codex CLI, then writes temporary base-URL settings into ~/.claude/settings.json and ~/.codex/config.toml so newly opened sessions are captured. Codex custom providers are routed through the selected provider's base_url; Claude Bedrock custom gateways are routed when they do not point at native AWS Bedrock endpoints. Native AWS Bedrock endpoints are left unchanged because reverse-mode URL rewriting would break SigV4 signing. Stop Monitor restores the config files byte-for-byte. If the app is force-killed, run claude-tap monitor-restore to restore configs and clean up monitor processes recorded by the app.
By default the launcher points at the current checkout; pass --installed if claude-tap is installed in the Python environment used to build the app. Pass --self-contained to build an Apple Silicon PyInstaller bundle under Contents/Resources so the app does not depend on a colleague's Python installation. Ad-hoc signed builds may still require the recipient to remove quarantine or approve the app in macOS security settings.
CLI Options
All flags are forwarded to the selected client, except these --tap-* ones:
--tap-client CLIENT Client to launch/listen to: claude (default), agy, codex, codexapp, dsh, gemini, grok, kimi, kimi-code, mimo, opencode, openclaw, pi, hermes, cursor, qoder, or codebuddy
--tap-target URL Upstream API URL (default: auto per client)
--tap-live Start real-time viewer while the client runs (default: on)
--tap-no-live Disable the real-time viewer server (pre-v0.1.75 behavior)
--tap-live-port PORT Port for live viewer server (default: auto)
--tap-no-open Don't auto-open live or generated HTML viewers in a browser
--tap-output-dir DIR Trace output directory (default: ./.traces)
--tap-port PORT Proxy port (default: auto)
--tap-host HOST Bind address (default: 127.0.0.1, or 0.0.0.0 in --tap-no-launch mode)
--tap-no-launch Only start the proxy, don't launch client
--tap-max-traces N Max trace sessions to keep (default: 50, 0 = unlimited)
--tap-store-stream-events Persist raw SSE/WebSocket event arrays during capture so viewer/export output can show them (default: off)
--tap-proxy-mode MODE Proxy mode: reverse or forward (default: reverse for claude/codex/grok/kimi/kimi-code/openclaw/codebuddy, forward for agy/codexapp/dsh/gemini/mimo/opencode/pi/hermes/qoder; cursor is transcript-only and ignores proxy MITM)
--tap-trust-ca On macOS, explicitly trust the local CA in the user login keychain before launch (agy does this automatically)
Viewer Features
Trace viewer capabilities
The viewer is a single self-contained HTML file (zero external dependencies):
- Structural diff — compare consecutive requests to see exactly what changed: new/removed messages, system prompt diffs, character-level inline highlighting
- Path filtering — filter by API endpoint (e.g.,
/v1/messagesonly) - Model grouping — sidebar groups requests by model, with Claude-family priority ordering
- Token usage breakdown — input / output / cache read / cache creation
- Tool inspector — expandable cards with tool name, description, and parameter schema
- Search — full-text search across messages, tools, prompts, and responses
- Dark mode — toggle light/dark themes (respects system preference)
- Iframe embed mode — add query parameters such as
embed=1,hideHeader=1,hidePath=1,hideHistory=1,hideControls=1,density=compact, andtheme=light|dark - Keyboard navigation —
j/kor arrow keys - Copy helpers — one-click copy of request JSON or cURL command
- i18n — English, 简体中文, 日本語, 한국어, Français, العربية, Deutsch, Русский
Architecture
How it works
How it works:
claude-tapstarts a reverse or forward proxy and spawns the selected client- Base URL clients are pointed at the reverse proxy; clients without base URL support use proxy/CA environment variables
- SSE and WebSocket streams are forwarded as chunks/messages arrive with low proxy overhead
- Each request-response pair or WebSocket session is recorded to local trace storage; raw SSE/WebSocket event arrays are omitted by default and must be captured with
--tap-store-stream-eventsif you need them later in viewer/export output - On exit, a self-contained HTML viewer is generated
- Live mode is enabled by default and broadcasts updates to the browser via SSE
Key features: 🔒 Common auth headers auto-redacted · ⚡ Low-overhead streaming · 📦 Self-contained viewer · 🔄 Real-time live mode
Community
Ecosystem
- Phistory archives versioned system prompt snapshots from agent CLIs such as Claude Code, Codex, Kimi, opencode, and Pi. It uses claude-tap's capture-only prompt export to preserve raw HTTP trace evidence and generate comparison-friendly prompt snapshots.
Star History
Contributors
Thanks goes to these contributors:
liaohch3 💻 📖 🚧 ⚠️ |
BKK 💻 |
YoungCan-Wang 💻 |
0xkrypton 💻 |
CYJiang 💻 |
陈展鹏 📖 |
devtalker 💻 |
Yaguang Ding 💻 |
Sephy 💻 |
Contributing
Contributions are welcome. Start with CONTRIBUTING.md.
License
MIT



