N8n v1.91.3 - Nodes (Code/Set) Fail to Output with 0-Item Input Despite 'Always Output Data' / 'Run Once for All Items' - Results in Empty API Response

Hello Again n8n Community,

I’m encountering a different persistent issue with an n8n workflow on version 1.91.3 (self-hosted, Docker on Linode) and would greatly appreciate any insights or known workarounds.

My Goal: I have an API endpoint (Webhook trigger) that checks if a text segment exists in a PostgreSQL database.

If the segment exists, it should respond with {"status": "ok"}. (This part works).
If the segment does NOT exist (Postgres SELECT finds 0 rows), it should insert the segment and respond with {"status": "created"}. The problem is that this “not found” path consistently returns an empty string "" to the HTTP client instead of the desired JSON response.

Core Problem: When my Postgres SELECT node outputs 0 items (segment not found), subsequent nodes intended to create a default item (e.g., containing a found: false flag) for a downstream IF node are failing to produce any output items themselves. This happens even when these “normalizer” nodes are configured with settings that should guarantee an output.

Troubleshooting Steps & Observed Behaviors:

  1. Postgres SELECT Node:
    This node correctly executes its query.
    When no segment is found, it correctly outputs 0 items.
  2. Attempt 1: Using a “Code” Node as a Normalizer:
    Configuration: Placed after Postgres SELECT; Mode: “Run Once for All Items”.
    JavaScript was designed to return [{ json: { found: false, ... } }] if ($input.all()).length === 0.
    Symptom: When Postgres SELECT outputted 0 items, this Code node also produced 0 output items (n8n UI showed “Execute this node to view data”).
    Debugging: console.log() statements at the script’s start did not appear in n8n Docker logs, suggesting the script didn’t execute.
  3. Attempt 2: Using a “Set” Node as a Normalizer (Named “CheckIfSegmentFound”):
    Configuration: Placed after Postgres SELECT; Settings Tab: “Always Output Data” = ON; Parameters Tab: “Keep Only Set” = ON.
    Test 2a (Dynamic expression): Value found = expression {{ $items("Postgres_Node_Name").length > 0 }}.
    Symptom: Produced 0 output items (Execution #100).
    Test 2b (Static value): Value found = static boolean false.
    Symptom: Still produced 0 output items (Execution #102).
    Test 2c (Isolated Set node execution - input disconnected, static value):
    Result: This WORKED. The Set node correctly produced one output item. This suggests “Always Output Data” can function in isolation but fails when triggered by a 0-item input stream from a connected node.
  4. Attempt 3: “Merge before Normalize” Strategy (from AI suggestions):
  • Structure: Webhook & Postgres (SELECT)MergeInputStreams node (Mode: Append) → NormalizeAndCombineData (Code node) → IF node.
    The NormalizeAndCombineData (Code node) was set to “Run Once for All Items”. Its JavaScript used $items("NodeName") to get data from Webhook, Code1 (my original hash calc node), and Postgres (SELECT node). The intended JavaScript is below.
    Symptom (Execution #105): The Postgres (SELECT) node itself errored with “Referenced node is unexecuted. An expression references the node ‘Code1’, but it hasn’t been executed yet…” This occurred even though the n8n UI execution path showed “Code1” immediately preceding it and highlighted as green (successfully executed). This specific error prevented full testing of the Merge strategy.

JavaScript for NormalizeAndCombineData (Attempt 3):

`JavaScript// — Configuration: Node names used —
const POSTGRES_SELECT_NODE_NAME = “Postgres”; // Actual name of Postgres SELECT node
const WEBHOOK_NODE_NAME = “Webhook”; // Actual name of Webhook node
const HASH_CALC_NODE_NAME = “Code1”; // Actual name of hash calculation node (e.g., “Code1”)
// — End Configuration —

let outputJson = {
found: false,
segment_id: null,
language_code: null,
content: null,
hash: null,
debug_messages:
};

try {
outputJson.debug_messages.push(“NormalizeAndCombineData script started.”);
console.log(“NormalizeAndCombineData: Script execution started.”);

const pgSelectItems = $items(POSTGRES_SELECT_NODE_NAME);
const webhookItems = $items(WEBHOOK_NODE_NAME);
const hashCalcItems = $items(HASH_CALC_NODE_NAME);

outputJson.debug_messages.push(pgSelectItems count: ${pgSelectItems.length});
outputJson.debug_messages.push(webhookItems count: ${webhookItems.length});
outputJson.debug_messages.push(hashCalcItems count: ${hashCalcItems.length});

if (pgSelectItems.length > 0 && pgSelectItems[0].json && pgSelectItems[0].json.segment_id !== undefined) {
outputJson.found = true;
outputJson.segment_id = pgSelectItems[0].json.segment_id;
outputJson.debug_messages.push(Segment FOUND in DB. ID: ${outputJson.segment_id});
} else {
outputJson.found = false;
outputJson.debug_messages.push(“Segment NOT FOUND in DB by Postgres SELECT.”);
}

if (webhookItems.length > 0 && webhookItems[0].json &&
webhookItems[0].json.body && webhookItems[0].json.body.body) {
const webhookBodyData = webhookItems[0].json.body.body;
outputJson.language_code = webhookBodyData.language_code;
outputJson.content = webhookBodyData.content;
outputJson.debug_messages.push(Webhook data retrieved: lang='${outputJson.language_code}', content='${outputJson.content ? outputJson.content.substring(0, 30) + "..." : "null"}');
if (outputJson.language_code === undefined) outputJson.debug_messages.push(“Warning: ‘language_code’ is undefined from Webhook.”);
if (outputJson.content === undefined) outputJson.debug_messages.push(“Warning: ‘content’ is undefined from Webhook.”);
} else {
outputJson.debug_messages.push(“ERROR: Webhook data structure error.”);
console.error(“NormalizeAndCombineData: Webhook data error. webhookItems:”, JSON.stringify(webhookItems));
}

if (hashCalcItems.length > 0 && hashCalcItems[0].json && hashCalcItems[0].json.hash !== undefined) {
outputJson.hash = hashCalcItems[0].json.hash;
outputJson.debug_messages.push(Hash retrieved: ${outputJson.hash});
} else {
outputJson.debug_messages.push(“ERROR: Hash data not found from '” + HASH_CALC_NODE_NAME + “'.”);
console.error(“NormalizeAndCombineData: Hash data error. hashCalcItems:”, JSON.stringify(hashCalcItems));
}

} catch (error) {
outputJson.debug_messages.push(EXCEPTION: ${error.message}. Stack: ${error.stack});
console.error("NormalizeAndCombineData: EXCEPTION - ", error);
outputJson.found = outputJson.found || false;
}

outputJson.debug_messages.push(“NormalizeAndCombineData script finished.”);
console.log(“NormalizeAndCombineData: Script finished. Returning:”, JSON.stringify(outputJson));

return [{ json: outputJson }];`

Summary of Unexplained Behaviors in n8n v1.91.3:

  1. Code Node (“Run Once for All Items”): Appears not to execute its script when direct input is 0 items.
  2. Set Node (“Always Output Data: ON”): Fails to output (even static values) when direct input from a connected node is 0 items (though works in isolated test with no input).
  3. Node Data Referencing: A node (Postgres SELECT) reported its successfully executed direct predecessor (Code1) as “unexecuted” (Execution #105).

Questions for the Community / Support:

  1. Are these known bugs or limitations in n8n v1.91.3?
  2. Could environmental factors (Docker, Node.js, N8N_RUNNERS_ENABLED) cause this?
  3. Besides upgrading, are there other robust workarounds for v1.91.3 to ensure an IF node reliably gets an item if a database query returns 0 rows?
  4. Why would a node report its successfully executed predecessor as “unexecuted”?

Any insights for n8n v1.91.3 would be greatly appreciated. This is a critical blocker. Thank you.

Quick update: After upgrading to n8n v1.93.0, my initial tests show that a ‘Set’ node (now ‘Edit Fields’) with ‘Always Output Data: ON’ does produce an output item when its input stream has 0 items. This is different from my experience in v1.91.3 where it produced 0 items. I’m now proceeding to test if this resolves my full API ‘false path’ scenario where it should return {"status": "created"}. I will update further once that is tested.