版本號 3 和 3.3 之間,節點悄悄停止了工作

版本號 3 和 3.3 之間,節點悄悄停止了工作

超商 APP 點下「領取優惠券」,畫面跳出確認動畫,但結帳時優惠沒有套用——有時候不是網路問題,是 APP 版本太舊,那個觸發邏輯根本不在裡面,舊版本靜默地忽略了整件事。這個感覺,在處理低程式碼自動化工具的 Set 節點時,完整地再現了一次。

技術環境

n8n 自動化工作流平台(self-hosted),Set 節點用於執行鏈中的欄位轉換與賦值。工作流定義透過 n8n REST API 的 PATCH /workflows/{id} 端點更新,節點定義存於 DB 內的 workflow JSON 結構。觸發模式為 webhook-triggered(收到 webhook 立即同步執行),節點於 n8n execution context 內依序運行。問題發生在 n8n expression evaluation 層:v3.0 Set 節點在處理 ={{ }} 表達式時存在 silent no-op 缺陷,assignment 不被執行,且不拋出任何錯誤或警告——同一 workflow 中 typeVersion 不同的 Set 節點,行為完全不同。

節點在那裡,輸出不在

PATCH 確認成功,versionId 對齊,workflow 重新啟用,一切程序走完。執行之後,下游拿不到那個欄位。輸出欄位是空的。

反覆確認設定沒有消失。再次 deactivate、activate。還是空的。

這種情況最容易往快取方向懷疑——執行引擎還在跑舊版本的記憶體快取,PATCH 更新了資料庫但 active webhook listener 沒有刷新。確實,這是已知的坑:PATCH 之後必須 deactivate 再 activate,引擎才會重新載入定義。但這次流程都走了,問題沒有消失。

錯誤傳染鏈(時序)

Script/Sky        n8n REST API           DB              Executor
    |                  |                  |                  |
    |── PATCH workflow ─>|                  |                  |
    |                  |── save JSON ─────>|                  |
    |                  |<── 200 OK ────────|  (typeVersion: 3)|
    |<── 200 OK ────────|                  |                  |
    |── deactivate ────>|── update state ──>|                  |
    |── activate ──────>|── reload def ────>|                  |
    |                  |                  |── init executor ──>| (v3.0 loaded)
    |  [webhook fires] |                  |                  |
    |                  |                  |── run Set node ──>| (typeVersion: 3)
    |                  |                  |                  |── eval expr: no-op ← 問題在這
    |                  |                  |<── output: {} ────|
    |── check output ──>|                  |                  |
    |<── field: (empty)|                  |                  |

DB: workflow JSON updated ✓  /  Executor: expression silently skipped ✗

關鍵節點在 Executor 的 expression evaluation 步驟:v3.0 的 Set 節點收到表達式後靜默跳過,整條鏈路拿到空輸出卻不報錯,讓問題完全隱藏在「一切正常」的表象後面。

分界點藏在一個小數點裡

把這個節點和旁邊一個正常運作的同類節點擺在一起比對。設定看起來幾乎一樣。直到翻出 workflow JSON,才看到兩者唯一的差異:一個的 typeVersion3,另一個是 3.3

就這樣。

n8n 的 Set 節點,v3.0 的 expression evaluation 有一個 silent no-op 的問題——assignment 不會被執行,不報錯,不丟警告,只是安靜地什麼都不做。同一個 workflow 裡,typeVersion: 3.3 的節點正常運作,typeVersion: 3 的節點靜默地跳過所有賦值。而 PATCH 工具或 UI 在建立節點時,預設給的可能就是 3.0,不是 3.3。

typeVersion 從 3 改成 3.3、重新部署,欄位立刻出現在下游。

Code 對照:修法前後

修法前(typeVersion: 3 — 問題版本)

{
  "type": "n8n-nodes-base.set",
  "typeVersion": 3,          // ← 問題在這裡:v3.0 expression evaluation silent no-op
  "parameters": {
    "assignments": {
      "assignments": [
        {
          "id": "abc123",
          "name": "targetField",
          "value": "={{ $json.sourceField }}",
          "type": "string"
        }
      ]
    }
  }
}

修法後(typeVersion: 3.3 — 正常版本)

{
  "type": "n8n-nodes-base.set",
  "typeVersion": 3.3,        // ← 修正:升級至 3.3,expression evaluation 恢復正常
  "parameters": {
    "assignments": {
      "assignments": [
        {
          "id": "abc123",
          "name": "targetField",
          "value": "={{ $json.sourceField }}",
          "type": "string"
        }
      ]
    },
    "options": {
      "includeOtherFields": true  // ← 必須加:v3.3 預設不傳上游欄位
    }
  }
}

該被隔離的側效應類型

  • 下游欄位傳遞:Set 節點 silent no-op 後,所有依賴該欄位的下游節點拿到空值,鏈路靜默通過但業務邏輯全壞。
  • Webhook 對外投遞:HTTP Request 節點發出的 payload 若包含空欄位,遠端系統收到不完整請求,可能靜默接受也可能 4xx,外部無告警。
  • 資料庫寫入:欄位空值被寫進 DB,覆蓋原有資料或填入不合法值,回滾成本高且難以追蹤源頭。
  • 事件日誌 / Audit Log:日誌缺少關鍵欄位(如 user_id、operation_type),事後查案時找不到上下文,問題變得不可重現。
  • 推播通知:通知內容依賴 Set 節點組裝的欄位,空值導致發出空白訊息或觸發通知錯誤,使用者體驗直接破損。
  • 快取鍵失效:以 Set 節點輸出值組建的快取鍵變成空字串,所有請求命中同一個 key 或快取完全失效,讀取邏輯亂掉。
  • 搜尋索引更新:需要寫入 Elasticsearch 的欄位為空,索引更新被靜默跳過或寫入不完整文件,搜尋結果顯示舊資料。
  • Analytics 追蹤:送往 analytics pipeline 的 event payload 缺少維度欄位,報表數據無法切分,指標失去可信度。

判斷標準:如果這個節點的輸出欄位是空的,下游鏈路會繼續跑但結果是錯的——這種「靜默通過、邏輯破損」的模式,是比拋出 Error 更危險的故障形態,因為它沒有任何信號要你去修。

為什麼沒有立刻看出來

因為所有「可見的確認」都過了。PATCH 回傳 HTTP 200。versionId 對齊。UI 上的設定正確。activate 成功。這幾個檢查點一路綠燈,沒有任何一個環節主動說「你的 expression 沒有被評估」。

工具版本差異不會拋出錯誤。它只是用不同版本的邏輯在跑,然後把結果——在這個 case 裡是空的——傳給下游。下游也不會報錯,因為空值對它來說只是一個空值。整條鏈路靜默地通過,什麼都沒有觸發。

這種故障模式比報錯更難找。報錯有堆疊追蹤,有行號,有起點。靜默的 no-op 只留下一個空欄位,等你自己去解釋。

下次值得注意的一件事

PATCH 完之後,「看 versionId 有沒有對齊」不夠。要看節點的 output 裡有沒有出現預期的欄位——不是設定面板,是實際執行之後的輸出資料。如果 Set 節點的 output 比 input 少了東西,或者新加的欄位沒有出現,先把所有 Set 節點的 typeVersion 拉出來看一眼。任何 typeVersion 低於 3.3 的,都值得懷疑。

另外,v3.3 預設不傳上游欄位,記得補 parameters.options.includeOtherFields: true,不然修完版本號又少了另一批欄位。

— 邱柏宇

延伸閱讀


Between 3 and 3.3, the Node Went Quietly Silent

Tap “Claim Coupon” in a convenience store app, watch the confirmation screen animate, then get to checkout and find the discount never applied. Sometimes it’s not a network issue — the app version is old, the trigger logic did not exist yet, and the old version silently ignored the whole thing. That same feeling showed up again while working with a Set node in a low-code automation tool.

Technical Environment

n8n workflow automation platform (self-hosted). Set node used for field transformation and assignment within automated execution chains. Workflow definitions are updated via the n8n REST API PATCH endpoint, with node definitions stored as JSON in the database. Trigger mode is webhook-triggered — the workflow executes synchronously on receipt. The problem lives in n8n expression evaluation layer: the v3.0 Set node has a silent no-op defect when processing expressions. Assignments do not execute, no error is thrown, no warning is raised — and within the same workflow, nodes at different typeVersions behave completely differently.

The Node Was There. The Output Was Not.

PATCH confirmed successful. versionId aligned. Workflow reactivated. Every step completed. Then the downstream node received nothing — the output field was empty.

Settings were still there. Tried deactivate, activate again. Still empty.

The natural suspicion is cache: the execution engine running an old in-memory version, the webhook listener never reloading after PATCH. That is a known issue — after PATCH, you must deactivate and reactivate before the engine picks up the new definition. The cycle ran. The problem did not clear.

Error Propagation Sequence

Script/Sky        n8n REST API           DB              Executor
    |                  |                  |                  |
    |── PATCH workflow ─>|                  |                  |
    |                  |── save JSON ─────>|                  |
    |                  |<── 200 OK ────────|  (typeVersion: 3)|
    |<── 200 OK ────────|                  |                  |
    |── deactivate ────>|── update state ──>|                  |
    |── activate ──────>|── reload def ────>|                  |
    |                  |                  |── init executor ──>| (v3.0 loaded)
    |  [webhook fires] |                  |                  |
    |                  |                  |── run Set node ──>| (typeVersion: 3)
    |                  |                  |                  |── eval expr: no-op ← failure point
    |                  |                  |<── output: {} ────|
    |── check output ──>|                  |                  |
    |<── field: (empty)|                  |                  |

DB: workflow JSON updated ✓  /  Executor: expression silently skipped ✗

The failure point is the Executor expression evaluation step: the v3.0 Set node receives an expression and silently discards it. The entire chain passes with empty output and no signal raised anywhere.

One Decimal Point Was the Dividing Line

Placing this node side by side with a working node of the same type, the configurations looked nearly identical. Then the workflow JSON came out, and there it was: one node had typeVersion: 3, the other had typeVersion: 3.3.

That was it.

In n8n Set node, v3.0 has a silent no-op bug in expression evaluation — assignments do not execute, no error thrown, no warning surfaced, nothing happens. Within the same workflow, nodes at typeVersion 3.3 ran fine; nodes at typeVersion 3 silently skipped every assignment. The PATCH tool or UI may have defaulted to 3.0 rather than 3.3 when the node was created.

Changing typeVersion from 3 to 3.3 and redeploying brought the field up immediately in downstream output.

Code Diff: Before and After

Before (typeVersion: 3 — broken)

{
  "type": "n8n-nodes-base.set",
  "typeVersion": 3,          // ← problem here: v3.0 expression evaluation is a silent no-op
  "parameters": {
    "assignments": {
      "assignments": [
        {
          "name": "targetField",
          "value": "={{ $json.sourceField }}",
          "type": "string"
        }
      ]
    }
  }
}

After (typeVersion: 3.3 — fixed)

{
  "type": "n8n-nodes-base.set",
  "typeVersion": 3.3,        // ← fix: upgrading to 3.3 restores normal expression evaluation
  "parameters": {
    "assignments": {
      "assignments": [
        {
          "name": "targetField",
          "value": "={{ $json.sourceField }}",
          "type": "string"
        }
      ]
    },
    "options": {
      "includeOtherFields": true  // ← required: v3.3 does not pass upstream fields by default
    }
  }
}

Side Effects That Should Be Isolated

  • Downstream field propagation: After a silent no-op, every downstream node receives an empty value. The chain passes silently but the business logic is entirely broken.
  • Outbound webhook delivery: HTTP Request nodes sending payloads with empty fields deliver incomplete requests to external systems — which may silently accept them or return 4xx with no internal alert.
  • Database writes: Empty values written to DB overwrite valid data or insert illegal values. Rollback costs are high and the root cause is hard to trace.
  • Event log / audit trail: Log entries missing key fields make incidents impossible to reconstruct. The problem becomes non-reproducible.
  • Push notifications: Notifications built from Set node output send blank messages or trigger formatting errors — direct user-facing breakage.
  • Cache key invalidation: Cache keys assembled from empty field values collapse to an empty string. All requests hit the same key or cache fails entirely, corrupting read logic.
  • Search index updates: Fields meant for search indexing are empty; updates are silently skipped or written as incomplete documents. Search surfaces stale data.
  • Analytics event tracking: Event payloads sent to analytics pipelines are missing dimension fields. Reports lose segmentation capability; metrics become unreliable.

The test: if a node output field is empty and the downstream chain still runs without error — silently, as if everything is fine — you are looking at a more dangerous failure mode than a thrown exception. An exception has a stack trace. A silent no-op has nothing. Just an empty field, waiting.

Why It Was Not Caught Sooner

Because every visible confirmation passed. PATCH returned HTTP 200. versionId matched. Settings displayed correctly in the UI. Activation succeeded. None of those checkpoints said your expression was never evaluated.

Version differences in tools do not throw errors. They run different logic quietly and hand the result — in this case, nothing — to the next node. The next node does not complain about an empty value. It just receives an empty value. The whole chain passes silently, nothing triggered.

That failure mode is harder to trace than an error. Errors have stack traces, line numbers, a starting point. A silent no-op leaves an empty field and waits for someone to notice.

One Thing Worth Checking Next Time

After a PATCH, verifying that versionId aligned is not enough. Check whether the node actual output contains the expected fields — not the settings panel, the execution output. If a Set node output is missing something that should be there, pull the typeVersion of every Set node in the workflow first. Any typeVersion below 3.3 is suspect.

Also: v3.3 does not pass upstream fields by default. Remember to add parameters.options.includeOtherFields: true, or fixing the version number creates a different missing-field problem immediately after.

— 邱柏宇

Related Posts