# 2026-05-13 cypher-executor multi-node chain context propagation 漏失 > **總耗時**:約 20 分鐘 debug(找 P0 #9 過程中順帶找到) > **根因**:4 個 edge type 沒 spread baseCtx 給下游節點,原始 context 從第 2 節點開始消失 > **修法**:ON_SUCCESS / ON_FAIL / IF / ON_CLICK 套用 PIPE / FOREACH 同模式 `{...baseCtx, ...result}` > **影響**:任何 chain workflow 從第 2 節點開始 interpolate context key 都失敗 --- ## 症狀 mira `acr run wiki_synthesis`(7 節點 workflow)回 `Unauthorized`。trace 顯示: - `load_schema`(節點 1)✓ 200,input 含 `api_key=ak_xxx` - `load_skill`(節點 2)✗ 401 Unauthorized,input 含 `api_key="{{api_key}}"` ← **模板原文未替換** ## 推測 → 驗證 **對照組**:2 節點 chain 看 input keys: | 節點 | input keys | |---|---| | n1 | `api_key, b1, b2, block_id` ✓ 全 context 在 | | n2 | `blocks, count, success, api_key, block_id` ✗ **`b1, b2` 不見**,`api_key` 是原文 `"{{api_key}}"` | n2 的 ctx **只有 n1 output spread**,原始 context(`b1`, `b2`, ...)全消失。 ## 根因 `graph-executor.ts` 在 outEdges 處理時,PIPE 跟 FOREACH 已有 baseCtx merge: ```typescript // PIPE (line 381-384) const pipeContext = { ...(context as Record), ...baseResult, }; // FOREACH (line 438-444) const baseCtx = ...; const itemContext = { ...baseCtx, ...(result as Record), [iteratorKey]: item, }; ``` 但 ON_SUCCESS / ON_FAIL / IF / ON_CLICK 直接傳 `result`: ```typescript case 'ON_SUCCESS': result = await this.executeNode(nextNode, graph, result, ...); // ← bug ``` `result` 是上游節點的 output,**沒有原始 context**。下游 interpolate 找不到原始 key 就原文留下。 ## 歷史脈絡 2026-05-07 commit `e8fca33`("FOREACH preserves outer context")已意識到問題並修了 FOREACH。但**沒同步處理另外 4 個 edge type**。今天才被 mira 7 節點 workflow 踩到。 → 教訓:架構級修法要全 edge type 一致掃過,不只修當下踩到的。 ## 修法 `cypher-executor/src/graph-executor.ts` 4 個 edge case 補: ```typescript // 改前 result = await this.executeNode(nextNode, graph, result, ...); // 改後 const baseCtx = (typeof context === 'object' && context !== null) ? context as Record : {}; const baseResult = (typeof result === 'object' && result !== null) ? result as Record : {}; const mergedCtx = { ...baseCtx, ...baseResult }; result = await this.executeNode(nextNode, graph, mergedCtx, ...); ``` 套用在 line 407 (ON_SUCCESS) / 415 (ON_FAIL) / 423 (IF) / 472 (ON_CLICK)。 ## 驗證 `acr run wiki_synthesis` 7 節點 workflow 端對端跑通: | 節點 | 修前 input | 修後 input | |---|---|---| | n2 (load_skill) | `api_key={{api_key}}` ✗ | `api_key=ak_xxx` ✓ | | n7 (emit_result) | 上游 spread only | baseCtx + 各上游 spread ✓ | 整條 16.2 秒(含 claude_api 真實 Claude 呼叫),結果 `{success: true, data: {...}}`。 ## Known Limitation `interpolateData()` 只展開 `node.data` top-level string values,**不遞迴 nested object**: ```yaml # emit_result 的 values 內 {{...}} 不會展開 emit_result: component: set values: text: "{{classify.data.text}}" # ← 原文傳,不展開 ``` 非阻擋 P1,SDD 待開 `interpolate-nested-config`。當前 workaround:直接看上游節點的 trace output。 ## 未來怎麼避免 1. **新 edge type 加進來時必須走 baseCtx merge 模式**——可以抽出 helper `mergeCtxForDownstream(context, result)` 強制所有 caller 用,避免漏 2. **interpolation 邊界要有測試**:寫一個 2 節點 chain 用 `{{api_key}}` 引用原始 context 的 e2e test,CI 跑過 3. **trace output 要顯示 input** —— 沒看 trace 內 `input` 欄位很難看出 interpolation 失敗(目前 acr CLI 不顯示 input,只顯示 result)。CLI 應加 `--verbose` 顯示每節點 input ## Reference - 對應 SDD:`matrix/arcrun/.agents/specs/arcrun/arcrun.md` P0 #10 - 相關 commit:`e8fca33` 2026-05-07(FOREACH 同類修法) - 受影響 SDD:`polaris/mira/.agents/specs/mira-app/tasks.md` 7B.3c - 同日另一個 incident:[2026-05-13-cypher-outbound-522.md](./2026-05-13-cypher-outbound-522.md)