Skip to content

feat(pi): add opt-in request capture for relay 400 diagnosis - #98

Open
CtriXin wants to merge 1 commit into
devfrom
issue-97-pi-capture-proxy
Open

feat(pi): add opt-in request capture for relay 400 diagnosis#98
CtriXin wants to merge 1 commit into
devfrom
issue-97-pi-capture-proxy

Conversation

@CtriXin

@CtriXin CtriXin commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Closes #97

为什么

mmf 启动的 pi 会间歇性收到上游 400(invalid character '\xNN' in string literal / 400 "Invalid JSON")。这一轮先做了诊断,结论是现有的修复方向是错的

  • 该报错文案可以用"body 里带一个真实未转义控制字节"逐字复现(含重复的"无效的请求,无效的请求")
  • 正常转义的控制字符在 content / tool_calls.arguments / tool result 上,/v1/messages/v1/chat/completions 都返回 200
  • 抓包实测 pi 的 openai-completions / openai-responses / anthropic-messages 三条路径,含真实 \x00\x16\x1b 的 tool result 全部被正确转义
  • 用真实 pi 0.83.0 回放失败 session 的 620KB 上下文,字节干净、200 OK
  • 失败字符近似覆盖整个字节空间(含 \t{§Û),是随机字节损坏,不是"内容里有控制字符"

也就是说 scripts/pi-retry-extension.mjs 里的 before_provider_request 归一化不可能修好这个问题(JSON.stringify 本来就会转义),而且它会改写用户内容里字面的 \xNN / \a / \e / \v / \0。回退它另开一轮处理,不叠进本 PR。

合成压测复现不出来(大 body、走/不走本地代理、undici 连接池并发、中断流后复用连接,全部 0 失败),所以只能在真实 session 上抓现场。

改了什么

MMS_PI_CAPTURE_PROXY 开关,把本次启动的 provider baseUrl 指向本地 recording proxy,原样透传字节并标出原始控制字节。

判定规则:raw_control_bytes 非空 → pi 侧缺陷;为空但 4xx → relay 或网络路径。

边界

  • 默认零行为变化:未设开关时不读不写 models.json,不加任何 env
  • 未动 provider / account 决策优先级、runtime 与 auth_mode 语义、bridge 路由、config schema、HOME/XDG 隔离
  • mms_pi_support.py 只加 2 行(import + 调用)
  • 保留 keep-alive 与 chunked 流式转发,避免抓包本身改变要观察的传输特征
  • 任何一步失败都 fail-open 回正常启动
  • proxy 跟随发起启动的进程退出,不留孤儿

验证

  • py_compile 覆盖所有改动的 Python 文件
  • tests/test_pi_capture_proxy.py 12 passed
  • tests/test_pi_launcher.py + tests/test_pi_retry_extension.py 40 passed
  • fresh-user gate --quick PASS;完整 gate 390 passed / 1 failed
  • 完整 gate 的那个 failed 是 test_overlay_web_access_session_entries_merges_session_and_web_access_skill,在干净 dev 上同样失败,既有红灯,与本改动无关
  • 端到端:真实 pi 0.83.0 → 自动拉起的 proxy → 真实 Tokyo relay,抓到 2 个请求、流式正常、0 个原始控制字节、父进程退出后无孤儿

过程中修掉两个自身缺陷:转发用 read(65536) 会阻塞 SSE(改 read1);抓包记录原本在转发结束后才落盘,挂住就没有证据(改成收到响应头即落盘)。

残余风险

抓包目录里的原始 body 是明文完整对话(header 不落盘,避免记录 API key)。仅诊断期开启。

https://claude.ai/code/session_01VA2vRxuCpZsLmXR8sLeUgG

mmf-launched Pi intermittently gets a 400 from the relay complaining about a
raw control byte inside a JSON string literal. Diagnosis established that the
relay accepts every properly escaped payload on all three protocols, that Pi's
captured traffic escapes control characters correctly, and that the failing
byte values are effectively uniform across the whole byte space. That is random
body corruption rather than payload content, and synthetic replays do not
reproduce it, so the only way to settle where the bad bytes enter is to record
what leaves Pi's socket during a real session.

MMS_PI_CAPTURE_PROXY routes the launch's providers through a local recording
proxy that forwards bytes verbatim and flags raw control bytes. Default-off:
with the variable unset, models.json and the launch environment are untouched.
Keep-alive and chunked streaming are preserved so the instrument does not
change the transport it observes, and every failure path falls back to a normal
launch.

Refs #97

Agent-Model: claude-fable-5
Agent-Family: anthropic
Agent-Session: 2d6d2047-8869-4b65-9345-349181832167
Agent-Step: 0.1.0

Claude-Session: https://claude.ai/code/session_01VA2vRxuCpZsLmXR8sLeUgG
@github-actions

Copy link
Copy Markdown

Digger: no new failures

  • Diff: 5 files, +764/-0.
  • Validation: no new failures; existing baseline: python3 -m pytest -q.
  • Findings: [P2] Existing baseline validation failures remain. No new head failure was introduced, but the base still has failing validation.
  • MMS semantic review: skipped.
  • Details: see Actions artifact digger.

Digger automatic review. Full packet: Actions artifact digger.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant