A failure that showed up only at the very last step
The run had, by every visible measure, gone well: six video segments rendered, both Shorts uploaded and published, the news log appended with the day's story. The failure only appeared at the final step, uploading the long-form video to YouTube, with ExpressionError: Node 'Build Chapters' hasn't been executed. Everything before that step had worked, which made a wiring problem in an early node an unlikely first guess — the graph clearly wasn't broken in any way that would have stopped rendering.
A node that looked wired in, and wasn't
Looking at the workflow's canvas, "Build Chapters" appeared to sit reasonably inside the graph — positioned right alongside the node that builds the render's concat list, visually part of the same cluster of nodes. What the canvas view didn't make obvious at a glance: it had zero outgoing connections. It was wired as a sibling off "Build Concat List," parallel to a second sibling, "SSH - Write Concat List," which is the branch that actually continues into the real render pipeline. "Build Chapters" itself just stopped there — nothing downstream ever consumed its output through an actual connection.
The upload node only reached it through an expression, not a wire
The long-form upload node's description field was built from {{ $('Build Chapters').first().json.chaptersText }} — a cross-reference expression that pulls a named node's output directly, regardless of whether that node sits anywhere on the actual path leading to the node using it. That kind of reference reads, at first glance, like it guarantees the referenced node has run: it names the node explicitly, after all. It doesn't. n8n executes nodes by following real graph connections forward from the trigger; a node with no outgoing connection of its own is never scheduled to run at all, no matter how many other nodes plan to read from it by name later.
Why it worked, seemingly, before this run
Confirming this specific run's execution list against the full set of nodes that actually fired showed "Build Chapters" genuinely absent — not merely producing empty output, but never executed at all. Its sibling branch, "SSH - Write Concat List," happened to run because it does have a real forward connection continuing the render, so every stage that branch feeds into (a multi-minute ffmpeg render, download, upload) proceeded normally, right up to the one place that expected the parallel dead-end's output.
The fix, and the lesson underneath it
The fix itself was straightforward once the actual problem was clear: rewire "Build Chapters" into the real serial path — from the ffmpeg concat step, through "Build Chapters," into the download step that precedes the upload — instead of leaving it as a disconnected leaf off an earlier node. The broader lesson is the part worth carrying into any other workflow: a node referenced only through a $('NodeName') expression, with no real outgoing connection of its own, is not guaranteed to execute just because the graph shows it positioned as a sibling of a node that does run. If something depends on a node's output, that dependency needs to exist as an actual connection somewhere in the graph — not only as an expression reference that assumes the node ran.
Recovering the run that had already partly succeeded
Because the Shorts uploads and the sheet log had already gone out for real by the time the failure hit, simply re-triggering the whole workflow risked duplicating that already-completed work. The safer path: confirm the rendered long-form file still existed on disk, pull the real title, description, tags, and chapter data straight from the failed execution's own captured node output, and run a one-off recovery workflow that repeated only the missing steps — download, upload, thumbnail, notification — rather than the entire pipeline from the start.
n8n Automation Hub