chore: import upstream snapshot with attribution
Auto Release / auto-release (push) Failing after 1s
CI / lint (push) Failing after 0s
CI / screenshot-quality (push) Failing after 3s
CI / pr-policy (push) Has been skipped
CI / test (3.11) (push) Failing after 0s
CI / test (3.12) (push) Failing after 0s
CI / test (3.13) (push) Failing after 3s
CI / coverage (push) Failing after 1s
Legibility / legibility (push) Failing after 3s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:31:48 +08:00
commit f3d80b4628
573 changed files with 76162 additions and 0 deletions
+11
View File
@@ -0,0 +1,11 @@
# Maintainer Docs
This directory contains maintainer and agent automation materials that are useful for project operations but are not part of the public user documentation.
- `standards/` contains required maintainer policies and validation rules.
- `plans/` contains implementation plans and handoff notes.
- `error-experience/` and `good-experience/` contain internal learning records.
- `guides/` contains maintainer-facing workflow and integration playbooks.
- `architecture/manifest.yaml` lists paths covered by the legibility check.
Keep public user documentation and README assets in `docs/`.
+11
View File
@@ -0,0 +1,11 @@
expected_paths:
- AGENTS.md
- .agents/docs/README.md
- .agents/docs/standards/README.md
- .agents/docs/standards/hard-rules.md
- .agents/docs/standards/validation-and-gates.md
- .agents/docs/standards/e2e-and-evidence.md
- .agents/docs/standards/coding-and-runtime.md
- .agents/docs/standards/workflow-and-review.md
- scripts/check_legibility.py
- .github/workflows/legibility.yml
@@ -0,0 +1,36 @@
# PR #1 过期基线分支导致 CI 失败
**日期:** 2026-02-25
**严重级别:**
**标签:** git, CI, uv.lock, rebase
## 发生了什么
PR #1(新增 `--tap-host` 功能)在 CI 中测试失败,原因是它基于过期的 `main` 分支。
该分支从 `bc7d344` 分叉,而 `main` 已推进到 `bd7ec3f`。两个版本的 `uv.lock`
不兼容,导致依赖解析失败,测试报错。
## 根因
在打开 PR 前,feature 分支没有 rebase 到最新 `main`
`uv.lock` 已产生分叉,过期版本无法解析正确依赖集。
## 影响
- 原本正确的 PR 在 CI 中失败
- 需要额外一次 rebase 循环修复
- 合并延迟了一个 review 回合
## 经验
**在打开或合并 PR 前,始终先 rebase 到最新 `main`。**
防止复发的 checklist
1. 开 PR 前:`git fetch origin && git rebase origin/main`
2. 验证 `uv.lock` 最新:`uv lock --check`
3. rebase 后本地运行完整测试:`uv run pytest tests/ -x --timeout=60`
## 相关
- PR#1
- Commitsbc7d344..bd7ec3f
@@ -0,0 +1,28 @@
# Codex Sandbox 无法执行 Git Commit
**日期:** 2026-02-26
**严重级别:**
**标签:** codex, sandbox, git, environment
## 问题
Codex `--full-auto` sandbox 会阻止对 `.git/index.lock`
`.git/FETCH_HEAD` 的写访问,导致 `git commit``git fetch` 无法完成。
## 影响
- Codex 可以通过 `git add` 暂存文件,但无法 commit 或 fetch。
- 任何要求最后提交的工作流都必须转到外部环境执行。
## 变通方案
- 用 Codex 完成代码编辑、重构、测试和 lint 检查。
- Codex 完成后,在 sandbox 外执行 `git add -A && git commit`
(通过 OpenClaw exec 或本地 shell)。
- 不要在 Codex 任务提示中要求 `git commit`;它会静默失败或直接报错。
## 经验
Codex sandbox 会限制 `.git/` 目录写入。把会产生文件变更的任务交给 Codex 时,
始终规划一个 Codex 后置 commit 步骤。将流程拆分为:
Codex 编辑 → 外部 git commit → push。
@@ -0,0 +1,26 @@
# Codex Sandbox 阻止 tmux Socket 创建
**日期:** 2026-02-26
**严重级别:**
**标签:** codex, sandbox, tmux, environment
## 问题
在 Codex `--full-auto` 中运行基于 tmux 的 E2E 流程失败,因为 tmux 无法在
`/private/tmp` 下创建或访问其 socket 路径(permission denied)。
## 影响
- Codex 可以更新代码和文档,但不能直接执行 tmux 交互测试。
- 依赖 tmux 的端到端验证必须在 Codex sandbox 外完成。
## 变通方案
- 用 Codex 处理代码编辑、重构以及可静态/可测试逻辑。
- 在 Codex 外执行依赖 tmux 的验证(例如 OpenClaw exec 或本地 shell)。
- 外部验证后,再把结果回填到仓库文档/测试中。
## 经验
需要系统级终端复用器(`tmux``screen`)的任务,无法完整委托给受限的 Codex sandbox。
必须明确划分 sandbox 安全工作与外部执行职责。
@@ -0,0 +1,23 @@
# CLI 中硬编码版本字符串
**日期:** 2026-02-26
**严重级别:**
**标签:** versioning, cli, metadata, release
## 问题
`cli.py` 中的 `__version__` 被硬编码为 `"0.1.7"`,发布时从未更新。
用户即使升级后,用 `-v` 看到的仍是错误版本。
## 根因
版本字符串是源码字面量,而不是从 package metadata 读取。
## 修复
将硬编码值替换为 `importlib.metadata.version("claude-tap")`
使其始终与 `pyproject.toml` 和 PyPI package metadata 一致。
## 经验
不要硬编码版本字符串。始终使用 `importlib.metadata`(或其他单一真相源,例如从 `pyproject.toml` 动态读取)。
@@ -0,0 +1,68 @@
# Python 3.13 SSL 要求证书包含 Authority Key Identifier
**日期:** 2026-02-26
**严重级别:** 高(CI 阻塞 - 连续 4 次失败)
**标签:** ssl, certificates, python313, ci, certs.py
## 问题
`test_forward_proxy_connect` 在 Python 3.13 下失败,报错如下:
```
SSLCertVerificationError: (1, '[SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: Missing Authority Key Identifier (_ssl.c:1032)')
```
CI 在 Python 3.11/3.12 通过,但在 3.13 持续失败。
## 根因
Python 3.13 收紧了 SSL 证书校验。它现在**要求**由 CA 签发的证书包含
Authority Key IdentifierAKI)扩展。
我们的 `certs.py` 生成结果:
- CA 证书:缺少 `SubjectKeyIdentifier`SKI
- Host 证书:缺少 `AuthorityKeyIdentifier`AKI)和 SKI
这些扩展在 X.509 中技术上可选,但 Python 3.13 的 SSL 实现会将缺失 AKI
视为校验失败。
## 为什么本地未捕获
本地开发环境是 Python 3.11,不强制 AKI 存在。
该问题只在 CI 的 Python 3.13 matrix 中暴露。
## 修复
`certs.py` 中为两类证书都加入正确的 X.509 扩展:
**CA 证书:**
```python
.add_extension(
x509.SubjectKeyIdentifier.from_public_key(key.public_key()),
critical=False,
)
```
**Host 证书:**
```python
.add_extension(
x509.AuthorityKeyIdentifier.from_issuer_public_key(
self._ca_key.public_key()
),
critical=False,
)
.add_extension(
x509.SubjectKeyIdentifier.from_public_key(key.public_key()),
critical=False,
)
```
## 经验
1. **在假定测试通过之前,务必检查 CI 的全部 Python 版本。**
本地 3.11 绿灯不代表 3.13 也绿灯。
2. **自签证书生成必须包含 SKI/AKI 扩展。**
即使旧版 Python 容忍缺失,新版也可能不容忍。
3. **当 CI 只在特定 Python 版本失败时,先看该版本 changelog 是否有更严格的安全要求**
SSL/TLS 校验是常见收紧区域。
@@ -0,0 +1,33 @@
# `rg`ripgrep)并非所有环境都可用
**日期:** 2026-02-26
**严重级别:**
**标签:** portability, shell, tooling
## 问题
Shell 脚本 `run_real_e2e_tmux.sh` 使用 `rg`ripgrep)做 JSONL 断言。
在未安装 ripgrep 或不在 `$PATH` 的环境里,脚本会静默失败或给出误导结果。
## 根因
`rg` 是 Rust 工具,不属于 POSIX,也不是 macOS 默认安装。
CI runner、Codex sandbox 和新初始化机器都可能缺少它。
## 修复
将全部 `rg` 调用替换为 `grep -F`(固定字符串匹配),后者是 POSIX 标准且普遍可用。
```bash
# Before (fragile)
rg '"tool_use"' "$JSONL_FILE"
# After (portable)
grep -F '"tool_use"' "$JSONL_FILE"
```
## 经验
**在 shell 脚本中优先使用 POSIX 标准工具**`grep``sed``awk``find``cut`
`rg``fd``jq` 等工具应留给交互式使用,或明确声明为依赖。
脚本必须能在裸环境运行。
@@ -0,0 +1,30 @@
# 子进程退出后因 SIGTTOU 被挂起
**日期:** 2026-02-26
**严重级别:**
**标签:** signal, tty, process-group, tcsetpgrp, exit-path
## 问题
Claude Code 退出后,`claude-tap` 尝试打印摘要并生成 HTML 输出,
却每次都触发 `suspended (tty output)`
## 影响
高。这是用户感知最强的问题:进程在收尾路径完成前就被挂起,
用户始终拿不到 HTML 输出。
## 根因
`claude-tap` 通过 `tcsetpgrp` 把终端前台控制权交给 Claude Code 子进程。
当子进程退出时,`claude-tap` 仍处于后台进程组。任何终端写操作都会触发 `SIGTTOU`
导致进程被挂起,因此 `finally` 块(HTML 生成与摘要)无法运行。
## 修复
在调用 `tcsetpgrp` 夺回前台进程组前先忽略 `SIGTTOU`
之后恢复原始 signal handler。
## 经验
只要使用 `tcsetpgrp` 进行进程组切换,在切回父进程前台组时就必须处理 `SIGTTOU`
@@ -0,0 +1,35 @@
# GitHub Raw URL 导致 PR 截图缓存陈旧
**日期:** 2026-02-27
**标签:** github, pr, screenshot, cache, review
## 问题
向 PR 分支 push 更新后的截图后,PR 描述仍显示旧图,看起来像变更未生效。
## 根因
PR 描述引用了稳定不变文件名的 `raw.githubusercontent.com` 路径。
GitHub/CDN 会在一段时间内继续缓存这些 URL 的旧内容。
## 影响
- review 混乱:reviewer 误以为 PR 证据未更新。
- 增加沟通成本与反复手动刷新。
## 已实施修复
1. 生成带版本后缀的新图片文件名(`*-v2.png`)。
2. 更新 PR 描述中的图片链接,指向新文件名。
3. 确认 PR 引用已切换到版本化 URL。
## 预防规则
更新 PR 内嵌截图时,优先通过改文件名使用不可变图片 URL
(如 `before-v2.png``after-v3.png`),不要复用旧文件名。
## 验证 Checklist
- 新文件出现在 `Files changed`
- PR markdown 链接指向带版本后缀的文件名。
- reviewer 无需强制刷新也能看到更新图片。
@@ -0,0 +1,35 @@
# Codex Reverse Mode 可能遗漏 Responses 流量
**日期:** 2026-02-28
**严重级别:**
**标签:** codex, reverse-proxy, websocket, trace-capture
## 问题
`--tap-client codex` 的 reverse mode 中,部分运行只捕获到 `/v1/models`
viewer 中 token usage 为 0。后续对话流量未能稳定捕获为 `/v1/responses`
## 根因
Codex 在交互/会话流中可能尝试基于 websocket 的 Responses 路径。
当用户配置或特性开关启用 websocket 行为时,reverse-mode base URL 路由会出现
trace 捕获不一致。
## 修复
- 在 Codex 的 reverse mode 下自动注入:
- `--disable responses_websockets`
- `--disable responses_websockets_v2`
- 保留用户意图:若用户已通过 `--enable``--disable``-c/--config features.<name>=...`
显式覆盖特性,不再强制覆盖。
## 验证
- 增加 E2E 断言:reverse-mode 启动包含 websocket-disable flags。
- 增加 E2E 断言:尊重用户显式 feature override。
- 运行完整 gate 检查(`ruff`、format check、`pytest tests/ -x --timeout=60`)。
## 经验
为了保证 proxy 捕获可靠性,不要假设 Codex 传输永远是 HTTP POST。
在 reverse proxy 场景下,应显式将传输特性固定到可捕获路径,并在测试中让覆盖行为具备确定性。
@@ -0,0 +1,36 @@
# Codex WS 超时且已验证 HTTPS 回退(PR #22
**日期:** 2026-03-03
**标签:** codex, websocket, reverse-proxy, validation, fallback
## 背景
在用真实 Codex 运行验证 PR #22`feat/ws-proxy`)时,我们需要对 WebSocket 传输行为和当前环境中的回退行为提供硬证据。
## 发生了什么
- 通过 `claude_tap --tap-client codex` 的常规 Codex 运行经由 HTTP/SSE`POST /v1/responses`)成功。
- 强制 WS 运行(`--enable responses_websockets``--enable responses_websockets_v2`)反复连接上游失败:
- `502 Bad Gateway`
- 连接 `wss://chatgpt.com/backend-api/codex/responses` 超时
- 多次重试后,Codex 自动回退到 HTTPS 传输并成功完成该轮。
## 根因(观察)
观察到的问题是当前环境中的上游 WS 连接超时,而非 proxy 进程本地崩溃。proxy 正确记录了 WS 失败,client 的 fallback 路径恢复了运行。
## 证据
- `/tmp/pr22-ws-unblock-evidence-20260303/codex-ws-v1-run.log`
- `/tmp/pr22-ws-unblock-evidence-20260303/codex-ws-v2-run.log`
- `/tmp/pr22-ws-unblock-evidence-20260303/codex-ws-v1-run/trace_20260303_180901.jsonl`
- `/tmp/pr22-ws-unblock-evidence-20260303/codex-ws-v2-run/trace_20260303_180901.jsonl`
## 经验
对传输敏感验证,需明确区分三类陈述:
1. 已实现行为(代码 + 测试),
2. 当前环境中的观察行为,
3. 因网络/运行时约束尚未验证的行为。
这样能避免过度声明,同时仍可在已验证范围内推进合并决策。
@@ -0,0 +1,57 @@
# PR 截图质量失败案例(2026-03-03
## 发生了什么
PR #22WebSocket proxy 修复)需要截图证据。在产出可接受截图前,先后出现了三次质量失败:
### 失败 1:移动端 Viewport 布局
- **症状**Trace viewer 渲染为移动端布局,拥挤、单列、难以阅读
- **根因**OpenClaw 内置浏览器默认窄 viewport(约 750px),触发响应式移动端断点
- **影响**:截图展示的是移动端 UI,和用户实际看到的不一致
### 失败 2Unicode 箭头乱码
- **症状**:日志中的箭头 `→``←` 在截图里显示为乱码 `鉞@`
- **根因**:日志文件包含 Unicode 箭头。headless 环境中的浏览器或字体渲染破坏了多字节字符;原始 `.log` 被直接提供且未处理 charset。
- **影响**:关键证据(WS 方向标记)不可读
### 失败 3:截图内容错误
- **症状**Trace viewer 显示的是 `/v1/models` 请求详情,而非 WebSocket `/v1/responses` 请求
- **根因**:截图前没有先点击到正确 trace entry,误以为默认视图就是 WS 请求
- **影响**:截图无法证明 PR 声称的内容
### 元失败:缺少提交前审查
- 三张错误截图都在未先审查的情况下被 commit 并 push 到 PR
- 用户不得不手动检查并逐个指出问题
- 多轮往返才修复本应在 commit 前捕获的问题
## 如何修复
1. **Viewport**:截图前执行 `browser act resize width=1440 height=900`
2. **Unicode**:创建自定义 HTML 卡片(`ws-log-clean.html`),使用 HTML entities`&gt;` `&lt;` `->`)替代原始 Unicode 箭头
3. **内容**:先导航到正确 trace entryTurn 2 WEBSOCKET),确认内容后再截图
4. **审查**commit 前逐张肉眼检查截图
## 形成的标准
### 截图提交前 Checklist
1. **Viewport**:截图前设为桌面宽度(≥1280px)
2. **内容**:确认截图展示的内容与声明完全一致
3. **编码**:检查是否有乱码/损坏字符,尤其是 Unicode 符号、CJK 文本、emoji
4. **布局**:确认渲染为桌面布局(不是移动端响应断点)
5. **可读性**:关键证据文本在 1x 缩放下可清晰阅读
6. **审查**`git add` 前查看实际 PNG 文件,禁止盲目提交
### 自动化机会
- `scripts/check_screenshots.sh`:自动检查图像尺寸(拒绝宽度 <1000px,疑似移动端)、文件大小(拒绝 <10KB,疑似错误页)和基础合理性
- PR 正文模板可以加入截图 checklist 章节
- 可考虑通过固定 viewport 设置程序化生成证据截图
## 预防
- 已在 `.agents/docs/standards/e2e-and-evidence.md` 增加截图质量 gate
- 已新增 `scripts/check_screenshots.sh` 用于 pre-commit 自动验证
- AGENTS.md 应对任何包含视觉证据的 PR 引用该截图标准
## 关键结论
截图是证据。证据必须在提交前验证。“我截了图”不等于“我验证了这张图能证明我的结论”。
@@ -0,0 +1,43 @@
# PR #22 Screenshot Quality Regression (WebSocket Proxy Fix)
**Date:** 2026-03-03
**Tags:** pr, screenshot, viewport, encoding, websocket, review
## What Happened
Timeline:
1. PR #22 (WebSocket proxy fix) required screenshot evidence for the trace viewer output.
2. Screenshots were captured in an openclaw browser session that defaulted to a narrow viewport (~750px width).
3. The rendered trace log contained raw Unicode arrows (`→`, `←`) and those characters displayed as garbled glyphs in the screenshot.
4. The captured screenshot focused on a `GET` request row instead of the `101 WEBSOCKET` row that was the core proof for the fix.
5. The screenshots were committed and pushed without a dedicated screenshot quality review step.
Specific failures:
- Desktop UI evidence looked like a mobile layout.
- Important log symbols were corrupted.
- The screenshot did not show the feature being validated.
- No pre-commit evidence verification caught the mismatch.
## Root Causes
1. **Viewport control gap:** no enforced minimum viewport width for desktop evidence screenshots.
2. **Encoding safety gap:** generated HTML used raw Unicode arrows in log rendering instead of ASCII-safe symbols or entities.
3. **Content verification gap:** no explicit check that screenshot content matched the exact PR claim (`101 WEBSOCKET` for this fix).
4. **Process gap:** no mandatory screenshot checklist before commit/push.
## Fix Applied
1. Defined screenshot standards with hard desktop viewport minimum (`>=1280px`).
2. Added encoding rule to avoid raw Unicode arrows in generated HTML evidence content.
3. Added content verification requirements to confirm screenshots display the specific feature/fix target.
4. Added automated validation script (`scripts/check_screenshots.py`) for baseline image quality checks.
5. Wired screenshot validation into CI for PR evidence images.
## Lessons Learned
1. Screenshot evidence needs the same validation rigor as tests and linting.
2. Visual proof must be scoped to the exact behavior under review, not adjacent activity.
3. Encoding decisions in generated artifacts can silently degrade review quality.
4. Lightweight automated checks plus a short human checklist prevent most screenshot regressions.
@@ -0,0 +1,92 @@
# WS Proxy Debugging Failure — Agent Blind Spot
**Date:** 2026-03-03
**Severity:** High (hours wasted, multiple agent iterations, cron loops)
**PR:** #22 (WebSocket proxy support)
## What Happened
PR #22 added WebSocket proxy support. After implementation, real Codex WS
connections kept timing out. Multiple Codex agents were spawned across many
iterations — a cron monitoring job ran continuously, spawning more agents,
checking status, retrying — but **none** identified the root cause.
The human user stepped in, asked to check the WS connect code path, and
within minutes pinpointed the exact problem.
## Root Cause (of the Bug)
`claude_tap/proxy.py` line 379 had `proxy=None` hardcoded in
`session.ws_connect()`. This explicitly bypassed the Clash Verge proxy
(`http://127.0.0.1:7897`) that all HTTP requests used successfully.
Direct WSS connections to `wss://chatgpt.com` timed out because the machine
required a proxy for external access.
## Root Cause (of the Debugging Failure)
### 1. Agents never read the actual code path
The `ws_connect()` call had `proxy=None` in plain sight. Any agent reading
that single line should have asked: "Why is proxy disabled for WS when HTTP
requests work fine through the proxy?"
### 2. Over-reliance on black-box testing
Agents kept re-running Codex with different flags (`--enable responses_websockets`,
`--enable responses_websockets_v2`) and analyzing timeout logs. None traced
the code path from the WS upgrade handler → upstream connect → `ws_connect()`
call parameters.
### 3. Cron monitoring loops burned cycles without progress
A monitoring cron job kept cycling: check status → agent still stuck →
spawn more agents → repeat. Many iterations, zero conceptual progress.
The loop was busy, not productive.
### 4. No systematic debugging methodology
A simple comparison would have solved it in minutes:
- **Does HTTP work?** → Yes (through proxy)
- **Does WS work?** → No (timeout)
- **What's different in the code path?** → HTTP uses session default proxy,
WS has explicit `proxy=None`
- **That's the bug.**
## The Fix
One line: remove `proxy=None` from `session.ws_connect()`. Let
`trust_env=True` on the `ClientSession` handle proxy resolution naturally.
Commit: `fcb2982`
## Lessons Learned
1. **Read the code before running tests.** When debugging proxy issues,
trace the actual network call and check what proxy settings are passed.
2. **Compare working vs non-working paths.** HTTP worked, WS didn't. The
diff between those two code paths is exactly where the bug lives.
3. **Stop the loop early.** If 2-3 iterations of black-box testing haven't
found the issue, switch strategy: read code, add logging, trace the call.
4. **Simple mental models beat brute force.** "Proxy works for A but not B
→ B must handle proxy differently" is a 30-second insight. No amount of
re-running tests with different flags will discover a hardcoded `proxy=None`.
5. **Cron loops are not debugging.** Automated retry loops are for monitoring
known-good processes, not for solving unknown bugs. When stuck, stop the
loop and think.
## Standards (Added)
See `.agents/docs/standards/debugging-standards.md` for the full checklist.
Key additions:
- Before spawning agents for debugging: spend 5 minutes reading the relevant
code path yourself
- When proxy/network issues: always check what `proxy=` parameter is passed
in the actual connect call
- After 2 failed iterations of the same approach: STOP and change approach
- Document "working vs broken" comparison explicitly before diving into fixes
@@ -0,0 +1,61 @@
# Codex strip_path_prefix URL mismatch
Date: 2026-03-10
## What broke
Codex CLI through claude-tap proxy returned 404 (HTTP) and 502 (WebSocket) for all requests.
## Root cause
`strip_path_prefix="/v1"` was set for the codex client, but `default_target` was `https://api.openai.com` (without `/v1`). This caused:
- Codex sends `/v1/responses`
- Proxy strips `/v1` → path becomes `/responses`
- Upstream URL: `https://api.openai.com/responses` ← wrong, should be `/v1/responses`
## What we tried
### Attempt 1: Remove strip_path_prefix entirely
Set `strip_path_prefix=""` for all codex clients. This fixed `api.openai.com` (API Key auth) but broke `chatgpt.com/backend-api/codex` (OAuth auth), because that backend expects `/responses` without `/v1`.
**Why it failed:** Only considered one authentication mode. Codex has two different backends with different URL structures:
| Backend | Expected path | Needs strip? |
|---------|-------------|-------------|
| `api.openai.com` | `/v1/responses` | No |
| `chatgpt.com/backend-api/codex` | `/responses` | Yes |
### Attempt 2: Conditional strip based on target URL
```python
"strip_path_prefix": "/v1" if args.client == "codex" and "api.openai.com" not in args.target else ""
```
This correctly handles both backends.
## What actually fixed it
The conditional strip (attempt 2), combined with tmux-based real E2E verification against the live chatgpt.com backend.
## Why the bug wasn't caught originally
1. **E2E test validated internal consistency, not external correctness.** The fake upstream handler asserted `request.path == "/messages"` (the stripped path), which confirmed stripping worked — but never checked if the constructed upstream URL matched the real API.
2. **Failure was misattributed to environment.** WS_VERIFY_REPORT.md documented WS 502 failures but concluded "environment/network dependent WS reachability" instead of investigating the URL construction.
3. **No real-endpoint URL assertion.** No test verified that `target + stripped_path` produces a valid real API URL.
## Lessons
1. **For proxy code, test the final upstream URL, not just the forwarded path.** Add assertions like:
```python
assert upstream_url == "https://api.openai.com/v1/responses"
```
2. **When a client supports multiple backends, enumerate all (client × auth × target) combinations** before modifying URL handling logic. Draw a matrix.
3. **Don't attribute failures to "environment" without first verifying the code path.** Print/log the actual upstream URL to confirm correctness before blaming network.
4. **Always run real E2E after proxy changes.** Unit tests with fake upstreams give false confidence for networking code. Use `tmux` to run a real request through the proxy.
@@ -0,0 +1,48 @@
# Mock E2E 测试模式:Fake Upstream + Fake Claude
**日期:** 2026-02-25
**标签:** testing, E2E, mock, best-practice
## 模式描述
现有 E2E 测试套件(`tests/test_e2e.py`)采用了全 mock 方案,
可在无外部依赖的情况下测试整个 claude-tap pipeline
1. **Fake upstream server** - 在后台线程运行 aiohttp server
模拟 Anthropic API,同时返回 non-streaming 与 streamingSSE)响应。
2. **Fake Claude script** - 在 PATH 中放置临时 Python 脚本作为 `claude` CLI。
它向 `ANTHROPIC_BASE_URL`(由 claude-tap 设置为 proxy 地址)发送 HTTP 请求并打印结果。
3. **真实 claude-tap** - 以子进程方式运行真实 `claude_tap` module
并通过 `--tap-target` 指向 fake upstream。
## 为什么效果好
- **无外部依赖**:测试可离线运行,无需 API keys
- **可确定性**:相同输入始终产生相同输出
- **速度快**:无网络时延,无 rate limit
- **覆盖完整**:测试全链路,包括 proxy 启动、请求转发、SSE 重组、JSONL 记录、HTML viewer 生成、API key 脱敏
- **边界情况稳健**:覆盖上游错误(500)、畸形 SSE、大 payload100KB+
## 关键实现细节
- `run_fake_upstream_in_thread()` 使用 `threading.Event` 做同步
- Fake Claude 脚本由 `_create_fake_claude()` 创建并设为可执行
- 临时 bin 目录会 prepend 到 `PATH`,让 claude-tap 找到 fake `claude`
- 端口硬编码(19199、19200 等)保持测试隔离
- Trace 文件写入 `tempfile.mkdtemp()`,断言后清理
## 何时使用此模式
适用于:
- 测试 proxy 行为(转发、记录、SSE 处理)
- 测试 HTML viewer 生成
- 测试 header 脱敏与安全能力
- 在无 Claude API 访问的 CI 中运行
## 互补模式
若要测试真实 Claude 集成(实际 API 响应、tool use、多轮对话),
请参考 `tests/e2e/` 下的 real E2E 测试。它们需要可用的 `claude` CLI 安装,
且在 CI 中默认跳过。
@@ -0,0 +1,43 @@
# 面向 Proxy 的 E2E 与 CI 强化
**日期:** 2026-02-25
**标签:** e2e, proxy, oauth, ci, reliability
## 背景
真实 E2E 覆盖曾被环境相关行为阻塞:
- forward-proxy 运行中的 OAuth 流程出现 `403 Request not allowed`
- CI 在 Python 3.13 下因 forward proxy 测试中的 TLS 证书校验更严格而失败。
- 测试预期误以为首个 API 调用总是 `/v1/messages`,但 OAuth 预检并非如此。
## 有效做法
1. **上游转发尊重系统 proxy env**
- 在 claude-tap 上游转发使用的 aiohttp session 中设置 `trust_env=True`
- 使流量能遵循本地 proxy/VPN 工具(如 Clash),而不是绕过它。
2. **提升 forward/OAuth 断言鲁棒性**
- 不假设首条 trace 请求是 `/v1/messages`
- 在记录中搜索至少一条 `/v1/messages` 调用,并在该处验证响应内容。
3. **为 Python 3.13 强化测试证书**
- 在自签测试证书中同时添加 `SubjectKeyIdentifier``AuthorityKeyIdentifier` 扩展。
- 解决 CI 中 `CERTIFICATE_VERIFY_FAILED: Missing Authority Key Identifier`
4. **修复 trace 统计的空值安全**
- 在收集 model usage 指标时防护 `request.body is None`
## 为什么重要
- 真实 E2E 可在 proxy 重环境的开发机中通过,避免隐藏的网络路径不一致。
- CI 在不同 Python 版本间更稳定。
- 测试验证的是行为本身,而不是偶然的请求顺序。
## 运行说明
- 真实 E2E 仍通过 `--run-real-e2e` opt-in。
- Browser integration 测试需要安装 Playwright 与浏览器二进制。
- 调试 proxy 问题时,两个链路都要验证:
- Claude CLI -> claude-tap
- claude-tap -> upstream(必要时必须遵守 proxy env
@@ -0,0 +1,40 @@
# tmux 真实 E2E 成功模式
**日期:** 2026-02-26
**标签:** e2e, tmux, testing, verification
## 问题
tmux `send-keys` 在真实交互式 E2E 运行中,无法稳定向 Claude Code TUI 提交 prompt。
## 根因
- 部分流程假设 `rg` 可用于断言,但在一些环境中并不存在。
- 对 Claude Code TUI 在 tmux 下的提交行为建模错误;正确提交键是 `Enter`
## 解决方案
- 将脆弱的 `rg` 断言替换为可移植的 `grep -F`
- 将默认提交行为统一为 `Enter`
- 加入“未命中重试提交”逻辑,降低瞬时输入时序失败。
## 验证
通过 JSONL 断言验证:
1. 两个 prompt 都出现在 trace 数据中。
2. `/v1/messages` 调用至少为 2 次。
3. 至少一个响应内容块为 `tool_use`
4. 生成了 HTML viewer 产物。
## 结果
- pytest real E2E 用例 `7/7` 通过。
- tmux 交互式 E2E 通过,且确认捕获到 `tool_use`
- 生成了 asciinema 录制。
- 已截取生成的 HTML viewer browser 截图。
## 经验
在 tmux 下做真实 Claude TUI 自动化时,可移植性与输入语义比“巧工具”更重要:
优先 `grep -F`,使用 `Enter`,并通过 trace 产物验证。
@@ -0,0 +1,25 @@
# UI 修复交付模式:Sticky Header + 证据优先验证
**日期:** 2026-02-27
**标签:** ui, viewer, testing, workflow, pr
## 背景
一个小型 UI 行为修复(viewer detail panel 的操作控件 sticky)需要以可靠证据交付,便于 review。
## 有效做法
1. 先应用最小化、仅 CSS 的修复,不改动 JS 行为。
2. 提前运行快速质量 gate`ruff` + `pytest`)以快速发现回归。
3. 产出可视化证据(滚动前/后状态)供 PR review。
4.`AGENTS.md` 添加明确流程要求,确保未来 UI 变更始终附带 E2E 验证与截图。
## 为什么这个模式好
- 低风险:改动面很小。
- review 清晰度高:截图可直接证明行为。
- 流程可复用:同样流程可应用于后续 UI 变更。
## 经验
对 UI 行为修复,证据是交付物的一部分。好的 PR 不只是“代码 + 测试”,还包括“可视化证明 + 明确验证步骤”。
@@ -0,0 +1,130 @@
# 工程实践指南
本文档对 claude-tap 项目的工程标准进行规范化。
## Python 代码风格
- **Linter/formatter** [ruff](https://docs.astral.sh/ruff/)
- **行长限制:** 120 字符
- **目标版本:** Python 3.11+
- **Lint 规则:** `E`errors)、`F`pyflakes)、`W`warnings)、`I`import sorting
- **忽略规则:** `E501`(行长,由 formatter 而非 lint 强制)
本地运行:
```bash
uv run ruff check . # Lint
uv run ruff format --check . # Check formatting
uv run ruff format . # Auto-fix formatting
```
## 测试策略
### 测试分层
| Layer | Location | 测试内容 | 在 CI 中运行 | 外部依赖 |
|-------|----------|----------|--------------|----------|
| **Unit** | `tests/test_diff_matching.py` | 纯逻辑(diff matching、parsing | 是 | 无 |
| **Mock E2E** | `tests/test_e2e.py` | fake upstream + fake Claude 的完整 pipeline | 是 | 无 |
| **Browser integration** | `tests/test_nav_browser.py` | HTML viewer JavaScript 逻辑 | 是(使用 Playwright | Playwright |
| **Real E2E** | `tests/e2e/` | 真实 Claude CLI 集成 | 否(opt-in | Claude CLI、API key |
### 运行测试
```bash
# Full CI suite (unit + mock E2E)
uv run pytest tests/ -x --timeout=60
# Specific test file
uv run pytest tests/test_e2e.py -x --timeout=120
# Real E2E tests (requires claude CLI)
uv run pytest tests/e2e/ --run-real-e2e --timeout=300
# All tests including real E2E
uv run pytest tests/ --run-real-e2e --timeout=300
```
### 编写新测试
- 使用 `pytest` fixtures 做 setup/teardown(见 `conftest.py`
-`tempfile.mkdtemp()` 创建临时目录,并始终清理
- 对 async 测试,使用 `pytest-asyncio`(配置为 `asyncio_mode = "auto"`
- 慢测试标记为 `@pytest.mark.slow`
- integration 测试标记为 `@pytest.mark.integration`
## Commit 约定
- commit message 使用英文
- 使用祈使语气:“add feature” 而非 “added feature”
- 使用类型前缀:`feat:``fix:``refactor:``test:``docs:``chore:`
- subject 行保持在 72 字符以内
- 对非简单变更,在 body 解释“为什么”
示例:
```
feat: add --tap-host flag for custom bind address
fix: handle malformed SSE events without crashing
test: add real E2E tests with Claude CLI integration
docs: update engineering practices guide
```
## Pre-Work Checklist
进行任何代码变更之前:
1. **检查仓库状态:**
```bash
git diff --stat
git log --oneline -10
```
2. **确保工作树干净**,或先 stash 变更
3. **拉取最新 main** `git fetch origin && git rebase origin/main`
4. **确认测试通过:** `uv run pytest tests/ -x --timeout=60`
## Feature 的 Worktree 工作流
使用 git worktree 进行隔离的 feature 开发:
```bash
# Create worktree for new feature
git worktree add -b feat/my-feature /tmp/claude-tap-my-feature main
# Work in the worktree
cd /tmp/claude-tap-my-feature
# ... make changes, run tests ...
# Merge back (fast-forward only)
cd /path/to/claude-tap
git merge --ff-only feat/my-feature
# Clean up
git worktree remove /tmp/claude-tap-my-feature
git branch -d feat/my-feature
```
收益:
- 主 worktree 保持干净
- 无需 stash 即可在多个 feature 间切换
- 自然隔离可防止相互污染
## Code Review 流程
commit 前:
1. **Lint** `uv run ruff check .`
2. **Format** `uv run ruff format --check .`
3. **Test** `uv run pytest tests/ -x --timeout=60`
4. **Review diff** `git diff`,逐行阅读每个变更
5. **验证范围:** 仅修改了与任务相关的文件吗?
合并 PR 前:
1. 所有 CI 检查通过
2. 无未解决 review 评论
3. 分支已 rebase 到最新 `main`
4. `uv.lock` 保持一致
## 语言
代码、注释、commit message 和 skill 文件必须使用英文。
对外文档必须中英文双份:`README.md` 对应 `README_zh.md``docs/guides/*.md` 对应同目录的 `*.zh.md`。
@@ -0,0 +1,190 @@
# Playbook:为 claude-tap 添加新的 LLM Client
提炼自 Codex 集成(PR #12,2026-02-28)。将其作为可复用框架,用于为任意新的 LLM client(如 Gemini CLI、Grok CLI 等)添加支持。
---
## 第 1 阶段:侦察 - 理解 Client 的 Wire Protocol
在写代码前,先回答以下问题:
1. **Client 调用的是哪个 API endpoint**(例如 `api.openai.com/v1/responses`
`api.anthropic.com/v1/messages`
2. **它是否有备用 endpoint**(例如 Codex 对 ChatGPT Plus 用户使用 `chatgpt.com/backend-api/codex`,而不是 `api.openai.com`
3. **它使用什么传输方式?** HTTP POSTWebSocketgRPC
- **Codex 经验**Codex v0.106.0 在无提示情况下把 `/v1/responses` 从 HTTP 切到 WebSocket。HTTP proxy 看不到任何内容。务必验证实际 wire transport,而不是只看文档。
4. **哪个 env var 控制 base URL**`OPENAI_BASE_URL``ANTHROPIC_BASE_URL` 等)
5. **子进程是否真的继承了该 env var?**Codex 有 Rust 子进程,可能会或可能不会遵循 Node.js 父进程环境)
6. **它使用何种编码/压缩?**Codex 会发送 zstd 压缩体)
### 如何调查
```bash
# Watch actual network traffic
lsof -i -P | grep <process_name>
# Check what the process sees
ps -p <pid> -E | tr ' ' '\n' | grep BASE_URL
# Intercept with mitmproxy for full visibility
mitmproxy --mode reverse:https://api.example.com --listen-port 8080
```
**关键原则**:不要盲信文档。观察真实行为。
---
## 第 2 阶段:Proxy Wiring - 让每个请求都可见
### Checklist
- [ ] 设置正确的 env var,将流量重定向到 claude-tap 本地 proxy
- [ ] 处理路径映射(client 发送 `/v1/responses`upstream 期望 `/responses`
- [ ] 处理请求体编码(zstd、gzip 等)
- [ ] 处理响应流(SSE events、chunked transfer
- [ ] **若使用 reverse proxy,将传输固定为 HTTP** - 禁用会绕过 HTTP proxy 的 WebSocket/gRPC 特性
- [ ] 用真实流量验证(不仅是 unit tests)
### 验证检查点
```bash
# Run the client through claude-tap
uv run python -m claude_tap --tap-client <name> -- <simple prompt>
# Check trace file has actual API calls (not just /models)
wc -l .traces/trace_*.jsonl # Should be > 1
python3 -c "
import json
with open('.traces/trace_<latest>.jsonl') as f:
for line in f:
d = json.loads(line)
print(d['request']['path'], d['response']['status'])
"
```
**如果 trace 只有 1 行(models/health check),说明真实 API 调用绕过了你的 proxy。** 立即停止并排查传输路径。
---
## 第 3 阶段:Viewer 兼容性 - 每种 API 格式都不同
每个 LLM provider 的响应格式都不同。需要映射这些字段:
| Concept | Claude (Chat Completions) | OpenAI (Responses API) | Your Client |
|---------|--------------------------|----------------------|-------------|
| System prompt | `body.system` | `body.instructions` | ? |
| Messages | `body.messages[]` | `body.input[]` | ? |
| Message content | `{type: "text", text}` | `{type: "input_text", text}` | ? |
| Token usage | `response.body.usage` | SSE `response.completed` event | ? |
| Response output | `response.body.content` | SSE `response.output_text.delta` | ? |
| Tools | `body.tools[]` | `body.tools[]` | ? |
### Viewer 修复模式
1. 找出 viewer 读取 Claude 特定字段的所有位置
2. 新增 normalize 函数,把新格式映射到通用结构
3. 保持向后兼容,旧 trace 仍要能正确渲染
### 验证检查点
用真实 trace 打开 HTML viewer 并验证:
- [ ] System prompt 已显示
- [ ] Messages 以正确 role 渲染
- [ ] Token 计数非零
- [ ] Turn 间 diff 可用
- [ ] Response output 已显示
---
## 第 4 阶段:录制 - 证据是交付物的一部分
每次新增 client 集成都需要:
1. **终端录制**:展示 client 通过 claude-tap 运行的真实 E2E 会话
- 工具:`asciinema rec``agg`GIF)→ `ffmpeg`MP4
- 必须展示:启动 banner、至少 2-3 轮对话、如适用的 tool calls
2. **Viewer 录制**:通过 Playwright 自动化演示 HTML viewer
- 工具:Playwright `record_video_dir``.webm``ffmpeg`MP4
- 必须展示:system prompt、messages、tokens、diff
3. **截图**:用于 PR review 的静态证据
- 至少包括:overview、messages、diff、token stats
**使用真实 trace 数据,不要用 mock。** reviewer 一眼就能看出来。
### PR 截图陷阱
PR 描述中使用绝对 URL`raw.githubusercontent.com/...`),不要用相对路径。
GitHub PR 正文不会解析相对图片路径。
---
## 第 5 阶段:防御式设计 - 下一次会坏在哪里?
集成跑通后,提前预判未来的破坏点:
- **Client 更新传输方式**:显式固定传输,不要假设永远是 HTTP。
记录 workaround,并为原生支持创建 TODO。
- **Client 修改 API 格式**:将 normalize 函数隔离,便于后续更新。
- **Client 新增 auth 复杂性**:在 plan 文档记录所需 scopes/permissions。
### 始终留下这些资产
- [ ] 遇到非显而易见问题时,写 error experience 文档
- [ ] 已知限制写 TODO/plan 文档(例如 WebSocket 原生支持)
- [ ] 补能覆盖你发现的特定失败模式的测试
---
## 反模式(来自 Codex 集成)
| Anti-pattern | 发生了什么 | 经验 |
|-------------|-----------|------|
| 相信“看起来明显”的 API endpoint | 以为是 `api.openai.com`,实际是 `chatgpt.com/backend-api/codex` | 必须追踪真实网络调用 |
| 假设传输一定是 HTTP | Codex 默认使用 WebSocketproxy 什么都看不到 | 验证 wire transport,必要时固定 |
| 在数据修复前录制演示 | 首版录制中 token 为 0trace 中有 403 错误) | 先修数据链路,最后再录制 |
| 截图使用旧 trace 数据 | 截图仍显示修复前 trace 的错误 | 修复后必须重新录制 |
| PR 正文使用相对图片路径 | GitHub 上所有图片都坏掉 | 使用 `raw.githubusercontent.com` 绝对 URL |
---
## 模板:新 Client Checklist
```markdown
## Adding support for: <CLIENT_NAME>
### Recon
- [ ] Identified API endpoint(s)
- [ ] Identified transport (HTTP/WS/gRPC)
- [ ] Identified env var for base URL
- [ ] Verified child process inherits env var
- [ ] Identified request encoding
### Proxy
- [ ] Env var injection working
- [ ] Path mapping correct
- [ ] Request decompression working
- [ ] Response streaming captured
- [ ] Transport pinned to HTTP (if reverse proxy)
- [ ] Trace file has real API calls (not just models)
### Viewer
- [ ] System prompt displayed
- [ ] Messages rendered
- [ ] Token counts non-zero
- [ ] Diff working
- [ ] Response output shown
- [ ] Old Claude traces still work
### Evidence
- [ ] Terminal recording (≥3 turns)
- [ ] Viewer recording
- [ ] Screenshots (≥5)
- [ ] PR description with absolute image URLs
### Docs
- [ ] Error experience (if applicable)
- [ ] TODO for known limitations
- [ ] Checklist template used
```
@@ -0,0 +1,42 @@
# Viewer UX 设计依据
本指南记录了稳定的设计决策,这些决策此前分散在一次性的实现规格中。
## 导航顺序
键盘与触控导航必须遵循侧边栏展示的同一视觉顺序。
- 侧边栏的分组与排序定义了用户预期的导航顺序。
- `j/k` 与方向键导航应按 DOM 顺序在可见项间移动。
- 移动端上一条/下一条控件应与桌面键盘导航保持相同顺序。
## Mobile-First 约束
viewer 在窄屏下必须保持可用,且不能出现水平溢出。
- 移动端布局应优先一次只显示一个主 pane。
- detail view 的操作区需要触控友好的控件和清晰边界。
- diff 与内容密集视图在移动端应切换为堆叠布局。
## Diff 匹配语义
diff 质量取决于是否比较了相关 turn。
- 优先使用带历史感知的匹配(共享消息前缀或等价线程信号)。
- 如果 fallback 匹配是近似的,应在 UI 中明确给出警告。
- 允许手动选择目标,使用户可以覆盖自动匹配结果。
## 国际化
新增面向用户的 UI 文案必须保持一致的本地化策略。
- 所有字符串都要经过翻译层。
- 增加新 key 时保持 language pack 完整。
## 测试与范围纪律
viewer UX 相关工作应保持聚焦且可验证。
- 可行时优先在 `claude_tap/viewer.html` 中做受限改动。
- 在改进移动端 UX 的同时保持桌面行为稳定。
- 通过测试与手动 trace-viewer 验证来确认行为。
@@ -0,0 +1,243 @@
---
status: completed
---
# Codex Support Handoff (Detailed)
Date: 2026-02-27
Branch: `feat/codex-client-support`
Repository: `liaohch3/claude-tap`
## 1. Goal, Scope, and Current Status
### Original Goal
Add support for Codex in addition to Claude, while preserving existing Claude behavior.
### Acceptance Constraint From User
Real Codex validation must be successful end-to-end. Any `403` is considered a hard failure.
### Current Status
- Core implementation for `--tap-client codex` is completed.
- Mock/unit/integration tests are passing locally.
- Real Codex run is still blocked by account/API permissions (`Missing scopes: api.model.read`) and upstream behavior in this environment.
- Work is committed on branch `feat/codex-client-support`.
## 2. What Was Done (and Why)
### A. CLI and Runtime Behavior
#### File: `claude_tap/cli.py`
Changes made:
- Added client selection support:
- New flag `--tap-client` with values `claude|codex`, default `claude`.
- Extended launch path so we can run either `claude` or `codex`.
- Reverse proxy env injection is now client-specific:
- Claude: `ANTHROPIC_BASE_URL=http://127.0.0.1:<port>`
- Codex: `OPENAI_BASE_URL=http://127.0.0.1:<port>/v1`
- `--tap-target` default is now derived from client when omitted:
- Claude -> `https://api.anthropic.com`
- Codex -> `https://api.openai.com`
- Updated user-facing startup/shutdown messages to reflect selected client.
- Preserved existing Claude forward-mode behavior, including `--settings` injection path only for Claude.
Reason:
- Existing implementation was Claude-only and hardcoded around Anthropic variables.
- Codex requires OpenAI-style base URL behavior in reverse mode.
- Keeping default `claude` preserves backward compatibility.
### B. Trace Model Enhancement for Viewer
#### File: `claude_tap/proxy.py`
Changes made:
- Added `upstream_base_url` into each trace record via `_build_record(...)`.
- Threaded `upstream_base_url` through streaming and non-streaming handlers.
- Simplified upstream encoding behavior by forcing `Accept-Encoding: identity` to avoid zstd-related client incompatibilities in this environment.
Reason:
- Viewer copy-curl was hardcoded to Anthropic domain; needed source-specific upstream reconstruction.
- Codex and some responses exhibited zstd decode failures in environment; identity encoding reduces this proxy-side compatibility risk.
### C. Forward Proxy Consistency
#### File: `claude_tap/forward_proxy.py`
Changes made:
- Also force `Accept-Encoding: identity` when forwarding upstream requests.
Reason:
- Keep behavior consistent with reverse proxy path and reduce compression-related failures.
### D. Viewer Behavior
#### File: `claude_tap/viewer.html`
Changes made:
- `copyCurl(...)` now uses `entry.upstream_base_url` when available.
- Falls back to `https://api.anthropic.com` for legacy traces.
Reason:
- Makes generated curl command accurate for Codex/OpenAI traces.
- Maintains backward compatibility for existing old trace files.
### E. Test Coverage Updates
#### File: `tests/test_e2e.py`
Changes made:
- Extended `_run_claude_tap(...)` helper with `tap_client` argument.
- `test_parse_args` now validates Codex defaults:
- `--tap-client codex`
- default target -> `https://api.openai.com`
- Added `test_codex_client_reverse_proxy` with fake `codex` executable:
- fake codex uses `OPENAI_BASE_URL`
- fake upstream expects `/v1/messages`
- asserts trace contains expected path/model
- asserts `upstream_base_url` is recorded
- asserts startup output includes `OPENAI_BASE_URL=...`
Reason:
- Validate new client behavior without depending on real external credentials.
- Ensure no regressions in argument parsing and runtime wiring.
### F. Planning Docs
#### File: `.agents/docs/plans/2026-02-27-codex-support-plan.md`
Changes made:
- Added a scoped implementation plan and explicit acceptance/risk notes.
Reason:
- Follow repository guidance for plan documentation and clear handoff context.
## 3. What Was Not Done (or Not Fully Done)
### A. Real Codex E2E Success
Not achieved yet due environment/account limitations.
Observed blocker:
- Codex model refresh request failed:
- `GET /v1/models?client_version=...`
- `403 Forbidden`
- message includes `Missing scopes: api.model.read`
Implication:
- Per user requirement, this means real validation is not complete.
### B. Dedicated `tests/e2e/` Real Codex Suite
Not added yet.
Reason:
- Given hard requirement that real run must succeed, adding real tests now would produce consistent failures in this environment until permissions are fixed.
### C. README / README_zh User-Facing Docs
Not updated in this round.
Reason:
- Priority was code path and correctness first.
- Follow-up should document new `--tap-client` behavior and examples.
## 4. Exact Test Execution and Results
The following were run locally and passed:
- `uv run ruff check .`
- `uv run ruff format --check .`
- `uv run pytest tests/ -x --timeout=60`
- Result: `48 passed, 18 skipped`
Additional targeted tests run:
- `uv run pytest tests/test_e2e.py -k "test_parse_args or test_codex_client_reverse_proxy" -x --timeout=120`
- Passed
- `uv run pytest tests/test_e2e.py -k "test_e2e or test_forward_proxy_connect or test_codex_client_reverse_proxy" -x --timeout=120`
- Passed
Real Codex smoke run attempted and failed (as expected in this env):
- Command pattern:
- `uv run python -m claude_tap --tap-client codex --tap-target https://api.openai.com --tap-no-update-check --tap-no-open -- exec "Reply with exactly: CODEX_REAL_OK" --skip-git-repo-check --json`
- Failure indicators:
- `403 Forbidden` with missing `api.model.read`
- Codex exits non-zero
## 5. Working Tree Hygiene / Commit Scope
Important context:
- There was a pre-existing `uv.lock` modification before this work.
- `log/` contains runtime artifacts and is untracked.
- These were intentionally excluded from commit scope.
Files intended for this task commit:
- `claude_tap/cli.py`
- `claude_tap/proxy.py`
- `claude_tap/forward_proxy.py`
- `claude_tap/viewer.html`
- `tests/test_e2e.py`
- `.agents/docs/plans/2026-02-27-codex-support-plan.md`
- `.agents/docs/plans/2026-02-27-codex-support-handoff.md` (this file)
## 6. Recommended Next Actions for the Next Codex Process
### 1) Resolve Real Credential/Scope Blocker First
- Ensure API key/project/org has `api.model.read` (and required response scopes).
- Re-run real Codex smoke command to confirm non-403.
### 2) Add Real Codex E2E Coverage
Suggested additions:
- New test module under `tests/e2e/` for Codex real runs.
- Cover at least:
- single turn success
- multi-turn continuity
- trace generation and path assertions
- Keep `403/401` as hard fail per user rule.
### 3) Update User Docs
- Update `README.md` and optionally `README_zh.md` with:
- `--tap-client codex`
- reverse mode example for Codex
- known prerequisite: proper OpenAI scopes
### 4) Optional: Investigate zstd error path deeper
- Even after identity preference from proxy to upstream, Codex may still log zstd decode issues in failure scenarios.
- Verify whether this is tied to non-proxied traffic, fallback channels, or specific Codex internal endpoints.
## 7. Quick Technical Summary for Next Agent Prompt
If you need an abbreviated bootstrap prompt for another Codex process:
- Branch: `feat/codex-client-support`
- Core feature done: `--tap-client codex` + OpenAI reverse mode env wiring.
- Tests pass locally (`48 passed, 18 skipped`) for `tests/`.
- Real Codex still blocked by `403 missing scopes: api.model.read`.
- Continue by fixing credentials/scopes and adding real Codex E2E tests + README updates.
@@ -0,0 +1,58 @@
---
status: completed
---
# Codex Support Plan
Date: 2026-02-27
## Goal
Add first-class Codex support to `claude-tap` while keeping Claude behavior fully backward compatible.
## Scope
- Add client selection: `--tap-client {claude,codex}`.
- Keep default client as `claude`.
- For `codex` in reverse proxy mode, inject `OPENAI_BASE_URL=http://127.0.0.1:<port>/v1`.
- Keep existing Claude behavior: `ANTHROPIC_BASE_URL=http://127.0.0.1:<port>`.
- Set default upstream target by client when `--tap-target` is omitted:
- `claude` -> `https://api.anthropic.com`
- `codex` -> `https://api.openai.com`
- Preserve existing forward proxy behavior.
- Add trace metadata `upstream_base_url` to improve viewer cURL reconstruction.
## Non-Goals
- Rename package/project branding (`claude-tap`) in this change.
- Add broad provider abstractions beyond Claude/Codex.
- Guarantee Codex ChatGPT login flow under forward proxy mode.
## Test Strategy
- Unit / mock E2E regression:
- `uv run pytest tests/test_e2e.py -x --timeout=120`
- New Codex mock E2E:
- fake `codex` binary + fake upstream API
- assert reverse-mode request path and `upstream_base_url` trace field
- Repo gate checks:
- `uv run ruff check .`
- `uv run ruff format --check .`
- `uv run pytest tests/ -x --timeout=60`
## Success Criteria
- Claude default workflow unchanged.
- `--tap-client codex` launches `codex` and writes valid trace output.
- Viewer copy-cURL uses `upstream_base_url` if present.
- No regressions in existing test suite.
## Real E2E Acceptance Rule
For Codex real E2E validation, HTTP `403/401` is treated as a hard failure.
A run is considered successful only when the end-to-end request completes with successful upstream responses.
## Risks
- Codex account scopes may block model-list or response APIs (`403`) even if proxy wiring is correct.
- Codex ChatGPT-web route traffic may not be fully covered by current forward mode assumptions.
@@ -0,0 +1,51 @@
---
status: completed
---
# Viewer Sticky Action Bar + 验证工作流计划
## 问题
`claude_tap/viewer.html` 中 detail pane 的操作按钮行在用户向下滚动时会消失。
这会降低重复操作(`Request JSON``cURL``Diff with Prev`)的效率。
## 范围
- 在滚动 detail 内容时保持 action bar 可见。
- 更新仓库工作流指引,确保未来 PR 包含:
- 真实 E2E 验证期望
- 对影响 UI 变更的截图要求
- 产出 review 证据(测试输出 + 截图)。
## 执行顺序
1. 定义目标行为与范围。
2. 实施最小化 UI 变更。
3. 运行受影响行为的定向测试。
4. 运行 E2E/browser 验证并收集截图。
5. 运行完整项目质量 gate。
6. 准备包含证据的 PR。
7. 记录经验教训。
## 变更摘要
- `claude_tap/viewer.html`
-`.action-bar` 在 detail 滚动容器中保持 sticky。
- `AGENTS.md`
- 新增 `E2E Validation Requirements` 章节。
- 新增 `PR Requirements for UI Changes` 章节。
## 验证
- Unit/integration 测试:`uv run pytest tests/ -x --timeout=60`
- Lint/format 检查:
- `uv run ruff check .`
- `uv run ruff format --check .`
- UI 证据:
- 顶部状态截图
- 滚动状态截图,确认 sticky action bar 仍可见
## 范围外
- Viewer 布局的功能重设计。
- 非相关依赖更新。
@@ -0,0 +1,59 @@
---
status: active
---
# TODO:支持 /v1/responses 的 Codex WebSocket 传输
**日期:** 2026-02-28
**优先级:**
**状态:** 计划中
## 背景
Codex CLI v0.106.0+ 默认对 `/v1/responses` API 调用使用 WebSocket 传输
`responses_websockets``responses_websockets_v2` 特性)。当前 claude-tap
通过自动注入 `--disable responses_websockets` 的方式绕过该行为并强制使用 HTTP,
从而使现有 HTTP reverse proxy 能捕获全部请求。
这个方案可用,但不理想。WebSocket 传输可能更快,且未来 Codex 版本很可能会
成为默认/唯一路径。
## 目标
在 claude-tap 的 reverse proxy mode 中原生支持 WebSocket 拦截,使 Codex
可使用默认 WebSocket 传输,同时 claude-tap 仍能捕获全部 API 调用。
## 方案选项
### 方案 AWebSocket MITM proxy
- 拦截到 `/v1/responses` 的 WebSocket upgrade 请求
- 代理 WebSocket 连接并记录全部 frame
- 将 frame 重组为相同 trace 格式(请求体 + SSE 等价事件)
- 优点:对 Codex 透明,无需注入 CLI flag
- 缺点:实现更复杂,需要处理 WS frame 重组
### 方案 B:带 CONNECT 隧道的 forward proxy
- 使用 forward proxy modeHTTP_PROXY/HTTPS_PROXY)并做 TLS 拦截
- 在 TLS 层同时拦截 HTTP 与 WebSocket 流量
- 优点:适用于所有传输类型
- 缺点:需要注入 TLS 证书,系统组成更复杂
### 方案 C:混合方案(检测并自适应)
- 检测 Codex 是否使用 WebSocket(检查 Upgrade headers
- 若是 WebSocket:代理 WS 连接并记录 frame
- 若是 HTTP:使用现有 streaming proxy 路径
- 优点:向后兼容,可覆盖任意 Codex 版本
- 缺点:需要维护两条代码路径
## 实现说明
- Responses API 的 WebSocket frame 很可能遵循类似 SSE 的事件结构
- 需要进一步确认 Codex 使用的具体 WebSocket 消息格式
- `aiohttp`(已是依赖)支持 WebSocket proxying
- `--disable` flag 绕过方案应保留为 fallback 选项
## 参考
- 修复 commit`a0e00e2`(在 reverse mode 下禁用 websocket 传输)
- 错误经验:`.agents/docs/error-experience/entries/2026-02-28-codex-reverse-websocket-capture-gap.md`
- Codex CLI flags`--enable/--disable responses_websockets[_v2]`
+20
View File
@@ -0,0 +1,20 @@
---
owner: claude-tap-maintainers
last_reviewed: 2026-05-06
source_of_truth: AGENTS.md
---
# 标准元数据
`.agents/docs/standards/*.md` 下所有文件都必须包含 frontmatter,字段包括:
- `owner`:负责更新的团队或维护者。
- `last_reviewed`:最近一次策略审查的 ISO 日期 `YYYY-MM-DD`
- `source_of_truth`:规范策略来源引用。
# 维护工作流
1. 更新受影响的标准文件,并刷新 `last_reviewed`
2. 保持 `AGENTS.md` 为简洁索引,并链接到更新后的文件。
3. 本地运行 `python scripts/check_legibility.py`
4. 若策略行为发生变化,在 PR 描述中记录理由。
@@ -0,0 +1,35 @@
---
owner: claude-tap-maintainers
last_reviewed: 2026-05-06
source_of_truth: AGENTS.md
---
# 编码标准
## 应做
- 删除无用代码。
- 修复测试失败的根因。
- 使用现有模式,并将范围限制在相关文件。
- 信任类型不变量,避免对已类型化值进行冗余运行时检查。
- 保持函数聚焦单一职责。
- 在脚本中优先使用 POSIX shell 工具。
- 在脚本中使用 `grep -F` 做固定字符串匹配。
- 从 metadata 读取 package version,而不是硬编码字符串。
## 禁止
- 保留注释掉的代码。
- 添加猜测性的抽象。
- 无理由抑制 linter 警告。
- 提交生成文件。
- 将 refactor 与 feature 工作混在一起。
- 为未使用代码添加兼容性 shim。
- 在未做检查时依赖不可移植工具(`rg``jq``fd` 可能不存在)。
# 运行时安全规则
- 如果使用 `tcsetpgrp` 的前台控制权切换,在将父进程组切回前台时要处理 `SIGTTOU`
- 将 CI 最高 Python 版本(当前为 3.13)视为运行时敏感行为的兼容性上限。
- TLS 测试/运行时的证书生成必须包含 SKI/AKI 扩展,以兼容 Python 3.13。
- 涉及证书/proxy/安全敏感变更时,在可用条件下本地以 Python 3.13 验证。
@@ -0,0 +1,104 @@
---
owner: claude-tap-maintainers
last_reviewed: 2026-05-06
source_of_truth: AGENTS.md
---
# Debugging Standards
## Pre-Debug Checklist
Before spawning agents or running automated retry loops:
1. **Read the code path** (5 minutes max). Trace from the entry point to the
failing operation. Look for hardcoded values, skipped parameters, or
divergent paths.
2. **Compare working vs broken.** If feature A works but feature B doesn't,
write down the differences in their code paths. The bug is in the diff.
3. **Check the obvious.** Proxy settings, environment variables, port numbers,
feature flags. Most bugs are configuration, not logic.
## During Debugging
4. **2-strike rule.** If the same approach (re-run tests, try different flags)
fails twice, STOP. Switch to a different strategy:
- Add targeted logging/print statements
- Read the source code of the failing library
- Reduce to minimal reproduction
- Ask: "What assumption am I making that could be wrong?"
5. **No infinite loops.** Cron monitoring is for watching known-good processes.
It is not a debugging tool. If a cron loop hasn't produced progress in
2 cycles, disable it and debug manually.
6. **Log your hypotheses.** Before each attempt, write down:
- What you think the problem is
- What evidence would confirm/deny it
- What you'll try
This prevents circular reasoning and repeated attempts.
## Network/Proxy Debugging Specifically
7. **Always check the actual connect call parameters.** When debugging proxy
issues, find the line where the connection is made and verify:
- Is `proxy=` set? To what?
- Is `trust_env=` True?
- Are environment variables (`HTTP_PROXY`, `HTTPS_PROXY`) being read?
8. **Use the simplest possible test.** Before complex E2E:
```bash
# Can we reach the host through proxy?
curl -x http://127.0.0.1:7897 https://target-host/
# Can we reach it directly?
curl https://target-host/
```
9. **Check for proxy bypass.** Libraries often have `NO_PROXY`, `proxy=None`,
or per-request proxy overrides that silently skip the system proxy.
## Proxy URL Construction Verification
10. **Verify the final upstream URL, not just the forwarded path.** When
modifying path stripping, target URL, or route logic, add an assertion or
log line that prints the fully constructed upstream URL. Confirm it matches
the real API endpoint. Fake upstreams in unit tests only verify internal
consistency — they cannot catch URL mismatches with real APIs.
11. **Enumerate all (client × auth × target) combinations.** Before changing
URL handling, draw a matrix of every supported configuration:
```
api.openai.com chatgpt.com/backend-api/codex
strip /v1 ✗ 404 ✓
no strip ✓ ✗ wrong path
conditional ✓ ✓ ← correct
```
Every cell must be verified — either by automated test or real E2E run.
12. **Run real E2E after any proxy/routing change.** Unit tests with fake
upstreams are necessary but not sufficient. After proxy changes, run at
least one real request through the proxy using tmux:
```bash
# Example: verify Codex through proxy
tmux new-session -d -s verify \
"uv run python -m claude_tap --tap-client codex --tap-target TARGET --tap-no-launch --tap-port 0"
# Then launch client in another window and send a test message
```
13. **Don't attribute failures to "environment" without evidence.** When a
request fails through the proxy, first print/log the constructed upstream
URL. Only blame network/environment after confirming the URL is correct.
See: `.agents/docs/error-experience/entries/2026-03-10-codex-strip-prefix-url-mismatch.md`
## Post-Debug
14. **Write the experience doc.** Every non-trivial debugging session produces
an entry in `.agents/docs/error-experience/entries/`. Include:
- What broke
- What you tried (and why it didn't work)
- What actually fixed it
- The lesson for future debugging
@@ -0,0 +1,72 @@
---
owner: claude-tap-maintainers
last_reviewed: 2026-05-29
source_of_truth: AGENTS.md
---
# E2E 验证要求
如果变更影响 proxying、trace 捕获、CLI session 流程、auth 处理或其他端到端行为,在开 PR 前必须运行真实 E2E 验证。
优先命令:
```bash
uv run pytest tests/e2e/ --run-real-e2e --timeout=300
uv run pytest tests/e2e/test_real_proxy.py::TestRealProxy::test_single_turn --run-real-e2e --timeout=180
```
手动替代方案:
```bash
scripts/run_real_e2e.sh
scripts/run_real_e2e_tmux.sh
```
如果无法运行真实 E2E(例如缺少 auth/token),在 PR 正文中记录原因与剩余风险。
# 代理/路由变更的额外要求
如果变更影响 URL 构造、路径 strip、target 选择或代理转发逻辑:
1. 参照 `docs/support-matrix.md` 中的支持矩阵,确认所有组合的 URL 构造正确。
2. 运行 `test_codex_upstream_url_construction` 验证 URL 断言。
3. 至少对一种真实后端做 tmux E2E 验证(如果 auth 可用)。
4. 不要将 proxy 请求失败归因为"环境问题",除非已确认构造的 upstream URL 正确。
# E2E 对话规则
每次 E2E 运行必须至少包含一次完整的多轮对话。
对于对话验证和截图证据,请使用 tmux 交互流程(`scripts/run_real_e2e_tmux.sh`)。
不要使用 `claude -p` 的单次运行作为对话完整性的证明。
# UI 证据要求
对于会改变 UI 布局、样式、交互流程或渲染内容的 PR:
- 每个变更的页面/状态至少提供一张截图。
- 当视觉差异重要时,提供变更前后截图。
- 当移动端行为受影响时,提供移动端截图。
- 使用来自 `.traces/trace_*.jsonl` 或真实运行输出的真实 trace 产物。
- 对于与 E2E 相关的 UI 变更,截图必须来自至少完成一次完整多轮对话的运行。
- 对于 proxy、runtime、client 或 trace 捕获变更,截图必须展示真实 trace viewer/dashboard 中的具体 API entry 或 session 状态,不能使用手写 HTML、终端输出截图、pytest 输出截图或只展示验证摘要的图片替代。
- PR 正文必须说明截图来源,例如真实 `claude-tap` 命令、导出的 `trace_*.jsonl`、viewer HTML 或 dashboard session。
# 截图质量门禁
作为 PR 证据提交的每张截图,在 `git add` 之前都必须通过以下检查:
## 强制检查
1. **Viewport 宽度 ≥ 1280px** — Headless browser 常默认窄 viewport。截图前务必调整为桌面尺寸(1280x800 或 1440x900)。
2. **内容与声明一致** — 如果 PR 写的是“已捕获 WS trace”,截图中必须清楚显示 WS trace,而不是其他请求或加载页。
3. **无编码损坏** — Unicode 箭头(→←)、CJK 字符和 emoji 必须正确渲染。如有疑问,在生成证据页面时使用 ASCII 等价字符或 HTML entity。
4. **无错误页面** — 404、ERR_EMPTY_RESPONSE、空白页或 “page not found” 不能作为证据。
5. **最小分辨率** — 图像宽度必须 ≥ 1000px。更窄通常是移动端/平板截图。
6. **文件大小合理** — 小于 10KB 的截图通常是空白页或错误页。典型 trace viewer 截图为 100KB500KB。
## 最佳实践
- 对日志/文本证据,优先渲染为有样式的 HTML(深色卡片、monospace、语法高亮),不要直接提供原始 `.log` 文件,以避免字体/编码问题。
- 截取 trace viewer 时,先导航到指定 entry,再截图。
- 使用 `scripts/check_screenshots.sh` 自动执行 pre-commit 验证。
## 反模式:盲目提交
不要在未先打开并检查截图的情况下直接 `git add` + `git commit` + `git push`。这会浪费 reviewer 时间并削弱证据信任。
+18
View File
@@ -0,0 +1,18 @@
---
owner: claude-tap-maintainers
last_reviewed: 2026-05-06
source_of_truth: AGENTS.md
---
# 硬性规则
以下规则是强制性的。若你无法遵守,请停止并说明原因。
1. 每次 commit 前执行 gate 检查:`ruff check``ruff format --check``pytest`。不得延期修复。
2. UI 变更要求在 PR 正文中提供使用 `raw.githubusercontent.com` 绝对 URL 的截图。
3. 每个 commit 只处理一个关注点。不要把 refactor 与 feature 或 bug fix 混在一起。
4. 代码、注释、commit message 和 skill 文件仅使用英文。对外文档必须中英文双份,中文内容放在 `README_zh.md` 或对应的 `*.zh.md` 文件中。
5. 截图、演示和测试证据必须使用 `.traces/` 中的真实 trace 数据,禁止 mock 或合成数据。
6. 编码前必须完成 pre-work checklist;开 PR 或合并前必须完成 pre-PR checklist。
7. 变更后必须执行 `git add``git commit``git push origin <branch>`
8. 必须使用 `gh pr create` 创建 GitHub PR;PR 未创建前,工作不算完成。
@@ -0,0 +1,44 @@
---
owner: claude-tap-maintainers
last_reviewed: 2026-05-29
source_of_truth: AGENTS.md
---
# Screenshot Standards
These rules apply to PR evidence screenshots used to validate UI, viewer output, trace logs, and proxy behavior.
## Viewport Rules
1. Desktop screenshots must use a viewport width of at least `1280px`.
2. If a change is desktop-only, do not submit mobile-layout screenshots as primary evidence.
3. If a change intentionally affects mobile behavior, include separate mobile screenshots and label them clearly.
## Encoding Rules
1. Use ASCII-safe characters or HTML entities in generated HTML/log views for symbols that may render inconsistently.
2. Do not use raw Unicode arrows (`→`, `←`) in generated HTML evidence content.
3. Prefer explicit text alternatives (`->`, `<-`) or entities (`&rarr;`, `&larr;`) to avoid garbled output.
## Content Verification Rules
1. Every screenshot must show the exact feature or fix claimed in the PR description.
2. For protocol fixes, capture the specific row/event proving the behavior (example: `101 WEBSOCKET`, not unrelated `GET` rows).
3. Runtime, proxy, viewer, dashboard, and client screenshots must come from a real trace viewer/dashboard or a real browser run generated by `claude-tap`, not from handcrafted HTML, terminal output, or test-output art.
4. PR evidence links for runtime behavior must point to committed images under `.agents/evidence/pr/` and use filenames that identify the real surface, such as `trace-viewer`, `dashboard`, `session`, `e2e`, or `browser`.
5. Before commit, verify the screenshot path and filename match the content shown in the PR markdown links.
## Screenshot Pre-commit Checklist
Run this checklist before `git commit` when screenshots are part of the PR:
1. Viewport width is `>=1280px` for desktop evidence.
2. Screenshot contains the exact target state/line proving the fix.
3. No garbled characters or encoding corruption in visible text.
4. Images are legible, not mostly blank, and reasonably sized.
5. Runtime evidence is backed by a real trace source, and the PR body states the source command or exported `trace_*.jsonl` / viewer HTML.
6. Local automated check passes:
```bash
python3 scripts/check_screenshots.py .agents/evidence/pr/
```
@@ -0,0 +1,77 @@
---
owner: claude-tap-maintainers
last_reviewed: 2026-05-06
source_of_truth: AGENTS.md
---
# Pre-commit CI 检查
每次 commit 前运行:
```bash
uv run ruff check .
uv run ruff format --check .
uv run pytest tests/ -x --timeout=60
```
所有检查都必须通过。若格式检查失败,运行 `uv run ruff format .` 后重新检查。
# Coverage Targets
Coverage gates are configured in `pyproject.toml` under `[tool.claude_tap.coverage]`.
- Backend Python project coverage: at least `65%`.
- Backend Python incremental coverage: at least `80%` of changed executable package lines.
- Frontend viewer JavaScript function coverage: at least `50%` of V8-reported inline JS functions executed by the viewer contract suite.
- Frontend viewer JavaScript incremental coverage: at least `80%` of changed `viewer.html` JavaScript functions exercised by V8 coverage.
- Frontend viewer CSS selector coverage: at least `65%` of queryable CSS selectors must match real DOM states exercised by the viewer contract suite.
- Frontend viewer CSS incremental coverage: at least `80%` of changed queryable `viewer.html` CSS selectors must match exercised DOM states.
Run the deterministic coverage gate with:
```bash
python -m coverage run -m pytest tests/ -q
python -m coverage json -o .coverage.json
python scripts/check_coverage.py --python-coverage .coverage.json
```
# Meaningful Test Requirements
Coverage percentage is a floor, not the goal. Tests added only to execute lines
without proving behavior are not acceptable.
Every new or changed test must assert at least one meaningful contract:
- returned values, normalized data, status transitions, or error behavior for
Python code;
- rendered DOM sections, visible semantic text, user interactions, browser
runtime errors, V8-executed viewer functions, or CSS-backed layout and theme
states for `viewer.html`;
- persisted files, trace records, or generated evidence when the change affects
filesystem output.
Viewer tests must prefer generated HTML opened in Chromium for user-visible
behavior. `Full JSON` may be asserted as a fallback section, but a viewer test
must not treat `Full JSON` alone as successful semantic rendering.
Viewer style changes must prove more than screenshot existence. Tests should
assert durable layout and visual contracts such as desktop/mobile widths,
overflow bounds, expanded content dimensions, and light/dark theme differences.
# Pre-work Checklist
在进行任何代码变更之前:
```bash
git diff --stat
git log --oneline -10
git fetch origin
```
在打开或合并 PR 之前:
```bash
git rebase origin/main
uv lock --check
uv run pytest tests/ -x --timeout=60
```
@@ -0,0 +1,75 @@
---
owner: claude-tap-maintainers
last_reviewed: 2026-05-29
source_of_truth: AGENTS.md
---
# Worktree 工作流
使用 git worktree 进行隔离的 feature 开发:
```bash
git worktree add -b feat/<name> /tmp/claude-tap-<name> main
cd /tmp/claude-tap-<name>
uv run pytest tests/ -x --timeout=60
cd /path/to/claude-tap
git merge --ff-only feat/<name>
git worktree remove /tmp/claude-tap-<name>
git branch -d feat/<name>
```
# Code Review Checklist
每次 commit 前:
1. `uv run ruff check .`
2. `uv run ruff format --check .`
3. `uv run pytest tests/ -x --timeout=60`
4. `git diff` 并检查每一行变更。
5. 确认只改动了相关文件。
# PR 正文策略门禁
`scripts/check_pr_policy.py` 是 PR 正文和 evidence 规则的统一机器检查入口。开 PR 前和 review preflight 都必须让它通过。
该门禁检查:
1. PR 正文包含 Summary/Problem/Goal 等摘要段落。
2. PR 正文包含 Validation/Test plan/Results 等验证段落。
3. 改动 runtime、viewer、client、proxy 或 UI 行为时,PR 正文包含 `raw.githubusercontent.com` 截图证据链接。
4. PR 正文中的图片链接使用 `raw.githubusercontent.com` 绝对 URL。
5. Runtime 行为截图必须链接到 `.agents/evidence/pr/` 下提交的 trace/viewer/dashboard/session/browser 证据图,并在 PR 正文说明真实 trace/viewer 来源。
6. PR 不包含 raw trace、生成的 trace viewer、日志、secret-like 文件或明显 token。
本地检查:
```bash
scripts/check_pr.sh <pr-number> --no-tests
```
CI 检查:
```bash
python scripts/check_pr_policy.py --event-path "$GITHUB_EVENT_PATH" --changed-files-file /tmp/pr-changed-files.txt
```
CI 同时在独立的 `pr-policy` job 和现有 required `lint` job 中运行该检查,避免新增 required status check 之前出现可绕过窗口。
# 复利式工程实践
记录经验教训:
- 错误经验:`.agents/docs/error-experience/entries/YYYY-MM-DD-<slug>.md`
- 正向经验:`.agents/docs/good-experience/entries/YYYY-MM-DD-<slug>.md`
- 汇总:`.agents/docs/error-experience/summary/entries/``.agents/docs/good-experience/summary/entries/`
- 计划:`.agents/docs/plans/`
- 指南:`docs/guides/`
在出现重大 bug、CI 失败或发现有价值模式后,创建一条条目并记录根因与经验。
# Brain + Hands 协议
- Claude Code (Opus):规划大脑,负责架构/API/模式/review 决策。
- Codex:执行双手,负责样板代码/命令/机械性编辑。
不要把架构决策委托给执行工具。
Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 207 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

@@ -0,0 +1,33 @@
# PR #276 double-serialized request body evidence
`double-serialized-viewer.png` was captured from a real `python -m claude_tap`
reverse-proxy run against a local fake Claude Code binary and a local fake
upstream server.
The fake Claude binary sent a double-serialized request body:
```python
inner = {"model": "claude-test", "messages": [{"role": "user", "content": "hi"}]}
payload = json.dumps(json.dumps(inner)).encode()
```
The run confirmed both required behaviors:
- The fake upstream still received the original one-pass parsed JSON string, so
claude-tap did not mutate the request bytes sent upstream.
- The stored trace request body was a dict, allowing the viewer to render the
user message normally instead of showing an opaque escaped JSON string.
Validation commands:
```bash
uv run ruff check claude_tap/forward_proxy.py claude_tap/proxy.py tests/test_e2e.py tests/test_request_body_parsing.py
uv run ruff format --check claude_tap/forward_proxy.py claude_tap/proxy.py tests/test_e2e.py tests/test_request_body_parsing.py
uv run pytest tests/test_request_body_parsing.py tests/test_e2e.py::test_double_serialized_request_body -q
python scripts/check_screenshots.py .agents/evidence/pr/276-double-serialized-body
```
The temporary SQLite database and exported HTML used to capture the screenshot
were local-only and are not committed. No API keys, auth tokens, private
prompts, raw trace files, or generated HTML viewers are present in this
evidence directory.
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

+81
View File
@@ -0,0 +1,81 @@
# PR 286 Kimi Code CLI Evidence
Real validation for `--tap-client kimi-code` (not fake upstream).
## Commands
```bash
cd /Users/civa/code/kimi-ws2/kimi-code-demo
claude-tap --tap-client kimi-code --tap-no-open --tap-no-update-check -- -p "Reply with exactly: KIMI_CODE_E2E_OK"
```
Direct control (no claude-tap):
```bash
kimi -p "Reply with exactly: KIMI_DIRECT_OK"
```
## Results
- `claude-tap --tap-client kimi-code`: exit 0, assistant text `KIMI_CODE_E2E_OK`, 1 API call captured
- Sandbox env: `KIMI_CODE_HOME` temp dir with patched `config.toml` `base_url``http://127.0.0.1:<port>`
- OAuth: `credentials/` and `oauth/` symlinked from real `~/.kimi-code`
- Migration prompt suppressed via `.skip-migration-from-kimi-cli` when `~/.kimi/.migrated-to-kimi-code` targets `~/.kimi-code`
## Trace source
- SQLite: `~/.local/share/claude-tap/traces.sqlite3`
- Example session: `3a09233f-f616-4f90-847a-d35178b7fa36` (`client=kimi-code`, `record_count=1`)
- Request path: `/chat/completions`, status `200`, upstream `https://api.kimi.com/coding/v1`
## Automated tests
```bash
uv run pytest tests/test_kimi_code_launch.py tests/test_e2e.py::test_kimi_code_client_reverse_proxy -x --timeout=60
```
## Maintainer validation
Conflict resolution and maintainer-side validation were run from a Linux worktree after rebasing the PR branch onto the latest `main`.
```bash
uv run --with ruff ruff check .
uv run --with ruff ruff format --check .
uv run --with pytest --with pytest-asyncio --with pytest-timeout pytest tests/ -x --timeout=60
uv run --with pytest --with pytest-asyncio pytest tests/test_client_config_framework.py tests/test_kimi_code_launch.py -q
env -u MOONSHOT_BASE_URL -u OPENAI_BASE_URL -u OPENROUTER_BASE_URL \
uv run --with pytest --with pytest-asyncio pytest tests/test_kimi_launch.py tests/test_e2e.py -q -k 'kimi'
uv run --with pytest --with pytest-asyncio pytest tests/test_opencode_launch.py -q
```
Results:
- Full test suite: `696 passed, 25 skipped`
- Focused Kimi Code/client framework suite: `65 passed`
- Focused legacy Kimi suite: `9 passed`
- OpenCode launch suite: `8 passed`
Local real trace viewer evidence:
![Kimi real trace viewer](kimi-viewer-real-trace.png)
This screenshot was generated from the local claude-tap SQLite session `0bb44d26-a814-4e59-a65d-9bf16b41c8aa`, a real Kimi CLI reverse-proxy capture with two `/chat/completions` records. It validates that the existing Kimi capture/export path still renders real Kimi request details after the Kimi Code client changes. The real Kimi Code upstream capture remains the contributor-side session documented above.
Latest published Kimi Code CLI smoke check:
```bash
npm view @moonshot-ai/kimi-code version dist-tags.latest
npm exec --yes --package @moonshot-ai/kimi-code@latest -- kimi --version
PATH=/tmp/claude-tap-pr287-kimi-code-latest/node_modules/.bin:$PATH \
KIMI_CODE_HOME=/tmp/claude-tap-pr287-kimi-smoke/home \
uv run python -m claude_tap --tap-client kimi-code \
--tap-output-dir /tmp/claude-tap-pr287-kimi-smoke/traces \
--tap-no-live --tap-no-open -- --help
```
Results:
- Latest npm package: `@moonshot-ai/kimi-code@0.11.0`
- Latest `kimi --version`: `0.11.0`
- `claude-tap --tap-client kimi-code -- --help`: exit 0, with `KIMI_CODE_HOME` and `KIMI_CODE_BASE_URL` injected through the reverse-proxy sandbox
- A real prompt was attempted with the latest CLI, but the local Linux test environment has no Kimi Code OAuth/API key configured, so the CLI stopped before sending an upstream request. This validates launch/config wiring, while the real upstream request capture is covered by the contributor evidence above.
Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

+132
View File
@@ -0,0 +1,132 @@
# PR 287 Kimi Code Deep E2E Evidence
Real validation for `--tap-client kimi-code` using the latest `@moonshot-ai/kimi-code` package and a logged-in local Kimi Code profile.
## Scenario
The test used one real Kimi Code conversation resumed across three non-interactive turns. Each turn required file or shell tool use inside an isolated workspace:
- workspace: `/tmp/claude-tap-kimi-code-deep-287-workspace`
- trace DB: `/tmp/claude-tap-kimi-code-deep-287.sqlite3`
- Kimi resume id: `276aae75-89fb-4093-a6d7-5861c1acc4ef`
The workspace started with `seed.txt`. Kimi Code then created and modified:
- `turn1_notes.md`
- `turn2_report.json`
- `turn3_verification.txt`
## Commands
Turn 1:
```bash
CLOUDTAP_DB=/tmp/claude-tap-kimi-code-deep-287.sqlite3 \
npm exec --yes --package @moonshot-ai/kimi-code@latest -- bash -lc \
'/tmp/claude-tap-pr287/.venv/bin/claude-tap --tap-client kimi-code --tap-no-open --tap-no-live --tap-no-update-check -- --print --final-message-only -p "Work only inside the current directory. Use your file or shell tools, not just reasoning. First inspect the current directory. Then create turn1_notes.md with two bullet lines, one containing KIMI_DEEP_TURN1. Then run a command that reports the file line count and sha256. Then read the file back. Reply with exactly one line starting with KIMI_DEEP_TURN1_OK and include line_count and sha256."'
```
Turn 2:
```bash
CLOUDTAP_DB=/tmp/claude-tap-kimi-code-deep-287.sqlite3 \
npm exec --yes --package @moonshot-ai/kimi-code@latest -- bash -lc \
'/tmp/claude-tap-pr287/.venv/bin/claude-tap --tap-client kimi-code --tap-no-open --tap-no-live --tap-no-update-check -- -r 276aae75-89fb-4093-a6d7-5861c1acc4ef --print --final-message-only -p "Continue the existing session. Use your file or shell tools again. Inspect turn1_notes.md, append a third bullet line containing KIMI_DEEP_TURN2, create turn2_report.json containing the final line count and sha256, then read that JSON file back. Reply with exactly one line starting with KIMI_DEEP_TURN2_OK and include line_count and sha256."'
```
Turn 3:
```bash
CLOUDTAP_DB=/tmp/claude-tap-kimi-code-deep-287.sqlite3 \
npm exec --yes --package @moonshot-ai/kimi-code@latest -- bash -lc \
'/tmp/claude-tap-pr287/.venv/bin/claude-tap --tap-client kimi-code --tap-no-open --tap-no-live --tap-no-update-check -- -r 276aae75-89fb-4093-a6d7-5861c1acc4ef --print --final-message-only -p "Continue the same session for a third turn. Use file or shell tools again. Read turn2_report.json, list the workspace files, run a Python command that verifies the sha256 of turn1_notes.md matches the report, and write turn3_verification.txt containing KIMI_DEEP_TURN3 plus the verification result. Then read turn3_verification.txt back. Reply with exactly one line starting with KIMI_DEEP_TURN3_OK and include verified=true."'
```
## Results
| Turn | claude-tap session | API calls | Status | Final response |
| --- | --- | ---: | --- | --- |
| 1 | `1ee9799d-d48f-404b-99c4-f7182fc825dc` | 5 | `complete` | `KIMI_DEEP_TURN1_OK line_count=2 sha256=0473e9a2b20bd4f62ad857f28ae8e4323859f23cfc9cffd6bc6d345b0ea99fd9` |
| 2 | `412db154-5fe6-4b91-9093-3f51f83f7e4c` | 6 | `complete` | `KIMI_DEEP_TURN2_OK line_count=3 sha256=c027e93c45bfbb4194e08d6f5d523949ff8bdf267a77a4f05d66bbf4b3b59367` |
| 3 | `0380294c-9875-4316-9564-809d94a7a4a9` | 5 | `complete` | `KIMI_DEEP_TURN3_OK verified=true` |
Workspace verification:
```text
turn1_notes.md:
- This is turn1_notes.md
- KIMI_DEEP_TURN1
- KIMI_DEEP_TURN2
turn2_report.json:
{"line_count":3,"sha256":"c027e93c45bfbb4194e08d6f5d523949ff8bdf267a77a4f05d66bbf4b3b59367"}
turn3_verification.txt:
KIMI_DEEP_TURN3 verified=True
```
## Interactive tmux E2E
I also ran Kimi Code as a real interactive TUI process under tmux and sent three consecutive prompts with `tmux paste-buffer` / `tmux send-keys`. This validates a single running Kimi Code process, terminal input handling, tool approval prompts, and continuous conversation state without relying on `--print` or `-r` for each turn.
- tmux session: `ctap-kimi-287`
- workspace: `/tmp/claude-tap-kimi-code-tmux-287-workspace`
- trace DB: `/tmp/claude-tap-kimi-code-tmux-287.sqlite3`
- claude-tap session: `b00ecd17-4c6c-40a6-829a-6f5fdbfc5719`
- Kimi Code session: `81668bcd-c11e-455f-ab55-280dc4da9031`
Interactive prompts sent:
1. Create and read back `tmux_turn1.txt`, then reply `KIMI_TMUX_TURN1_OK`.
2. Continue in the same TUI session, append `KIMI_TMUX_TURN2`, run `wc -l` and `sha256sum`, then reply `KIMI_TMUX_TURN2_OK`.
3. Continue in the same TUI session, list files, run Python verification, write/read `tmux_turn3_check.txt`, then reply `KIMI_TMUX_TURN3_OK`.
The trace captured:
- 12 total API records
- `client=kimi-code`, `proxy_mode=reverse`, `status=complete`
- final response: `KIMI_TMUX_TURN3_OK`
- tool evidence for `Shell`, `WriteFile`, and `ReadFile`
- real tool approval flow in the interactive TUI
Workspace verification:
```text
tmux_turn1.txt:
KIMI_TMUX_TURN1 KIMI_TMUX_287
KIMI_TMUX_TURN2
tmux_turn3_check.txt:
KIMI_TMUX_TURN3 verified=true
```
## Screenshots
All screenshots were taken from real exported viewer HTML generated from the SQLite trace DB. The scroll screenshots intentionally capture different detail-pane positions rather than only the first viewport.
The committed filenames include `viewer` so the PR evidence policy can identify them as runtime trace viewer screenshots.
- `kimi-code-deep-viewer-turn3-overview.png` - third-turn viewer overview with Kimi Code sidebar entries and tool labels.
- `kimi-code-deep-viewer-turn3-mid-scroll.png` - scrolled detail pane showing conversation history and tool-call context.
- `kimi-code-deep-viewer-turn3-tool-scroll.png` - deeper scroll showing Shell and ReadFile tool results plus resumed user input.
- `kimi-code-deep-viewer-turn3-final-response-scroll.png` - scrolled third-turn tool sequence before the final verification.
- `kimi-code-deep-viewer-turn3-bottom-final-response.png` - bottom scroll showing `turn3_verification.txt` readback and `KIMI_DEEP_TURN3_OK verified=true`.
- `kimi-code-deep-viewer-turn2-overview.png` - second-turn viewer overview.
- `kimi-code-deep-viewer-turn2-tool-scroll.png` - second-turn scrolled tool call/result evidence.
- `kimi-code-tmux-viewer-overview.png` - interactive tmux run overview with 12 records and tool labels.
- `kimi-code-tmux-viewer-turn1-tools-scroll.png` - first tmux prompt scrolled tool evidence.
- `kimi-code-tmux-viewer-turn2-history-scroll.png` - second tmux prompt scrolled history/tool evidence.
- `kimi-code-tmux-viewer-turn3-tools-scroll.png` - third tmux prompt scrolled tool evidence.
- `kimi-code-tmux-viewer-bottom-final-response.png` - bottom scroll showing `KIMI_TMUX_TURN3_OK`.
## Validation
```bash
uv run python scripts/check_screenshots.py .agents/evidence/pr/287
uv run python scripts/verify_screenshots.py /tmp/claude-tap-kimi-code-deep-287-turn2.html /tmp/claude-tap-kimi-code-deep-287-turn3.html
uv run python scripts/verify_screenshots.py /tmp/claude-tap-kimi-code-tmux-287.html
```
Results:
- screenshot quality: `PASS=12 WARN=0 FAIL=0`
- viewer HTML render verification: all exported HTML files passed
Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 211 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Some files were not shown because too many files have changed in this diff Show More