回應太大就說「回應太大」——不再冒充「請求失敗」(Arcrun#92) #104

Open
Leo wants to merge 1 commits from fix/error-message-truth-92 into main
Owner

CP 三態:◐ 半通

修法已落地,但 runtime 一行都沒驗到。不要當成驗過的東西合併。

  • 真因定位:http_request 零件接收緩衝區寫死 64 KB,而 wasi-shim.ts:167writeOut 不知道也不問這個上限,照寫 → 寫出界
  • 兩條死法都不會講出「太大」:① 覆寫零件堆積體 → wasm trap(HTTP 500)② 超出 linear memory → RangeError → HTTP request failed(就是票上那句)
  • 修法:容量握手(零件先宣告 outBuf 容量 → host 讀它當上限 → 塞不下就不寫,回 HOST_TOO_LARGE=3),不動 host function 簽名、向後相容
  • 舊零件讀到 0(沒宣告)=維持舊行為。刻意不套 64 KB 預設值——各零件緩衝區大小不同,硬套會把 claude_api 原本正常的 900 KB 回應誤判成「太大」,那只是換一種說謊
  • 5 顆零件 tinygo build 全過,wasm 已重編進 .component-builds/

斷 — runtime 未驗

交件宣稱的「修之後」訊息是從程式碼推導的,作者已誠實標明。總管實跑重現腳本:

$ node scripts/repro-oversize-response.mjs 200000
WASM 執行中止   : unreachable
使用者看到       : HTTP 500 {"success":false,"error":"WASM component produced no output"}

$ node scripts/repro-oversize-response.mjs 1024      # 對照組,根本沒超限
WASM 執行中止   : unreachable                        # ← 一樣壞

對照組也失敗 ⇒ 是重現腳本/執行環境跑不起來,不是修法失效。 但這也意味著:
前後對照訊息目前沒有任何實測證據。 合併前要有人把腳本弄能跑,並貼出真的前後對照。

合併前還缺一步

node scripts/build-worker-artifacts.mjs 重編 .worker-builds/——arcrun-cypher-executor/worker.mjsarcrun-http-request/ 目前仍是舊 shim。不重編的話 self-hosted 安裝路徑拿到的還是會說「請求失敗」的版本。這正是 Arcrun#93 那道閘抓到的同款陷阱。

順手修的

component-loader.ts makeHttpRunnertry res.json() catch res.text()——零件回非 JSON 時 body 已被消費,第二次讀丟 Body has already been used

只講不修(留給後續)

  1. 引擎把上游錯誤 text.slice(0, 200) 截斷,長錯誤重點常在後半段
  2. auth_*kv_get 失敗(auth_recipe) 不帶狀態碼
  3. 零件 worker index.ts 把 wasm trap 原文(unreachable)當 error 回 500,對使用者零資訊
  4. 🔴 最根本的:64 KB 上限本身沒解。 訊息誠實了,但大回應仍然抓不回來——要真支援得做分頁/串流,不是把 buffer 調大

作者 subagent 的 git commitnodepnpmnpx vitest 全被權限層擋下,成果停在 index 上,由總管代為 commit + push。

## CP 三態:◐ 半通 **修法已落地,但 runtime 一行都沒驗到。不要當成驗過的東西合併。** ### ✅ 通 - 真因定位:`http_request` 零件接收緩衝區寫死 64 KB,而 `wasi-shim.ts:167` 的 `writeOut` 不知道也不問這個上限,照寫 → 寫出界 - 兩條死法都不會講出「太大」:① 覆寫零件堆積體 → wasm trap(HTTP 500)② 超出 linear memory → RangeError → `HTTP request failed`(就是票上那句) - 修法:容量握手(零件先宣告 outBuf 容量 → host 讀它當上限 → 塞不下就不寫,回 `HOST_TOO_LARGE=3`),不動 host function 簽名、向後相容 - 舊零件讀到 0(沒宣告)=維持舊行為。**刻意不套 64 KB 預設值**——各零件緩衝區大小不同,硬套會把 `claude_api` 原本正常的 900 KB 回應誤判成「太大」,那只是換一種說謊 - 5 顆零件 `tinygo build` 全過,wasm 已重編進 `.component-builds/` ### ❌ 斷 — runtime 未驗 交件宣稱的「修之後」訊息是**從程式碼推導的**,作者已誠實標明。總管實跑重現腳本: ``` $ node scripts/repro-oversize-response.mjs 200000 WASM 執行中止 : unreachable 使用者看到 : HTTP 500 {"success":false,"error":"WASM component produced no output"} $ node scripts/repro-oversize-response.mjs 1024 # 對照組,根本沒超限 WASM 執行中止 : unreachable # ← 一樣壞 ``` **對照組也失敗 ⇒ 是重現腳本/執行環境跑不起來,不是修法失效。** 但這也意味著: **前後對照訊息目前沒有任何實測證據。** 合併前要有人把腳本弄能跑,並貼出真的前後對照。 ### 合併前還缺一步 `node scripts/build-worker-artifacts.mjs` 重編 `.worker-builds/`——`arcrun-cypher-executor/worker.mjs` 與 `arcrun-http-request/` 目前仍是舊 shim。不重編的話 self-hosted 安裝路徑拿到的還是會說「請求失敗」的版本。**這正是 `Arcrun#93` 那道閘抓到的同款陷阱。** ### 順手修的 `component-loader.ts` `makeHttpRunner` 的 `try res.json() catch res.text()`——零件回非 JSON 時 body 已被消費,第二次讀丟 `Body has already been used` ### 只講不修(留給後續) 1. 引擎把上游錯誤 `text.slice(0, 200)` 截斷,長錯誤重點常在後半段 2. `auth_*` 的 `kv_get 失敗(auth_recipe)` 不帶狀態碼 3. 零件 worker `index.ts` 把 wasm trap 原文(`unreachable`)當 error 回 500,對使用者零資訊 4. 🔴 **最根本的:64 KB 上限本身沒解。** 訊息誠實了,但大回應仍然抓不回來——要真支援得做分頁/串流,不是把 buffer 調大 --- 作者 subagent 的 `git commit`/`node`/`pnpm`/`npx vitest` 全被權限層擋下,成果停在 index 上,由總管代為 commit + push。
Leo added 1 commit 2026-08-12 08:51:06 +00:00
真因:零件(main.go)給 host function 的接收緩衝區是固定大小(http_request 64KB、
claude_api 1MB)。回應超過這個大小時,wasi-shim 的 writeOut 照寫不誤:

    new Uint8Array(buf, outPtr, data.length).set(data)

data 比零件的 outBuf 大 → 覆寫零件堆積體,零件接著 outBuf[:outLen] 切片 panic;
或 writeOut 撞 memory 邊界丟例外 → 回 1 → 零件印一句 "HTTP request failed"。
使用者照那句去查連線/URL/防火牆,方向全錯。

修法(容量握手,不改 host function 簽名、向後相容):
- 零件呼叫前把 outBuf 長度預先寫進 *outLenPtr(宣告容量)
- host 在寫回前讀這個值當上限;塞不下就**不寫**(不再覆寫零件記憶體),回新的
  HOST_TOO_LARGE=3
- http_request host fn 收到 3 → 改寫一段講真話的 error envelope(實際大小+上限+
  「不是連線失敗」+分頁/篩選的具體做法+機器可讀 code/actual_bytes/limit_bytes),
  沿用既有 parsed["error"] 判定鏈原樣送到使用者面前
- 舊零件沒宣告容量(讀到 0)→ 維持舊行為。不可硬套 64KB 預設:各零件緩衝區大小不同,
  硬套會把原本正常的大回應誤判成「太大」,那只是換一種說謊

同一條路徑上另一個「訊息與真因脫節」一併修:component-loader 的 makeHttpRunner
`try res.json() catch res.text()`,在零件回非 JSON 時 body 已被消費 → 丟
"Body has already been used",與真因無關(同檔 readBodyOnce 的註解早就寫明這個坑)。
改成只讀一次。

驗證狀態(誠實標示,mindset §7):
- 通:5 顆零件 tinygo build 全過,wasm 已重編進 .component-builds/
  (claude_api 依 .gitignore 慣例不入庫,由部署端重編)
- 未跑:runtime 驗證。本 session 的權限層擋掉 node/vitest/wasmtime,
  before/after 實測輸出待人跑 scripts/repro-oversize-response.mjs
- 未做:.worker-builds/ 重編(需 node scripts/build-worker-artifacts.mjs),
  否則修法不會進 self-hosted 安裝路徑(Arcrun#93 同款陷阱)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This pull request can be merged automatically.
This branch is out-of-date with the base branch
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix/error-message-truth-92:fix/error-message-truth-92
git checkout fix/error-message-truth-92
Sign in to join this conversation.