Fatal V8 crash - Check failed on startup once active workflow count crosses a threshold ~30

Hi all,

I logged in today to a bizarre error - 500/502 on all of my workflow calls (API and MCP). After two hours of debugging I came to the conclusion that I can only run ~29 workflows without crashing my instance. Details below.

Environment:

  • n8n version: 1.123.73 (also reproduced on 2.35.3 and 2.36.1 before pinning back)
  • Deployment: self-hosted, Docker (official n8nio/n8n image)
  • Node.js version bundled in image: v24.13.1 / v24.18.1 (varies by exact tag)
  • Database: SQLite
  • OS/host: Ubuntu 24.04, single VPS

Symptom:
On startup, once n8n reaches the “Start Active Workflows” phase and begins registering trigger nodes (webhooks/schedules/pollers), the whole process crashes with:

Fatal error in , line 0

Check failed: isolate_->IsOnCentralStack().

#FailureMessage Object: 0x…

----- Native stack trace -----

No further stack trace is printed. The container restarts (via Docker’s restart policy) and crashes again in a loop. docker inspect confirms OOMKilled: false - this is not a memory-limit kill, it’s a native V8 engine assertion failure.

What we ruled out (via extensive isolated testing across ~2 hours of reproduction):

  1. Not a specific workflow’s configuration. Deactivating the workflow that appeared last in the activation log before each crash did not stop the crash - it just moved to whatever workflow was next in the (now shorter) active list.
  2. Not the task runner. Reproduced identically with N8N_RUNNERS_MODE=internal, external (with N8N_RUNNERS_AUTH_TOKEN set), and N8N_RUNNERS_ENABLED=false (fully disabled).
  3. Not ulimits. Reproduced identically after raising memlock to unlimited and nofile to 65536 via Docker ulimits:.
  4. Not SQLite file state. Reproduced identically after cleaning stale -wal/-shm files and fixing file ownership.
  5. Not the specific Node/n8n version pairing - reproduced on both the 1.x and 2.x lines, both bundling Node 24.

What we confirmed via binary search (systematically deactivating/reactivating workflows in batches, each time doing a clean container stop → CLI update (n8n update:workflow --id --active false/true) → start → observe):

  • 0 active workflows: stable, boots cleanly.
  • ~26-29 active workflows: stable, boots cleanly, restart count stays flat.
  • 31 active workflows: crashes every time, at the exact same point in the log (immediately after the last successfully-activating trigger registers).
  • The exact threshold appears to sit between 29 and 31, but we didn’t narrow further than that once we had a workable number.
  • This instance has ~200 total workflow definitions (many are dormant/dev/duplicate), of which ~77 were active before this incident began - well past the crash threshold we’ve now identified.

Our working theory: something in the trigger-activation loop (webhook route registration, cron scheduling, or the interaction with V8’s stack-switching machinery used by worker_threads for the task runner and/or the vm sandbox n8n uses for Code node execution) is either leaking a resource per registered trigger, or hitting a fixed-size internal limit, that manifests as a native crash rather than a clean error once a threshold count of triggers is being registered in quick succession during startup.

Questions:

  1. Is there a known internal limit (V8 isolate count, worker thread pool size, file descriptor pool, etc.) tied to trigger/webhook registration during startup that could produce this specific assertion failure?
  2. Is this related to the deprecation of the legacy in-process Code execution and the move to mandatory task runners in the 2.x line? (We saw the same crash on 1.123.73 with runners fully disabled, which argues against this, but flagging in case there’s a shared code path.)
  3. Is there a recommended way to stagger/throttle trigger activation on startup (e.g. an env var to activate workflows in batches with a delay) as a workaround while we wait for a fix, rather than us manually capping active workflow count?

Happy to provide the full startup log, a minimal reproduction (we can likely trim this down to a smaller workflow set that still reproduces it), or any other diagnostic info needed.

Hey @EnzoPython3, while you wait for a response, here are some things that might help:

Suggested resources

Automatically matched to your question.

Docs:

Forum:

@Danilov_Vovka, @Jim_Le, @kjooleng - you’ve helped with similar issues before, can you take a look?

Automatically suggested by n8n’s community bot. It’s a pilot - please share feedback here.

Thanks for letting us know about this, We have created CAT-4134 as the internal dev ticket to look into it.

Exceptionally well documented report - the binary search especially. One thing I’d like to highlight from your environment data, then a workaround which serves as a test as well.

Node version is not excluded.

You reproduced on 1.123.73, 2.35.3 and 2.36.1, but you’ve also mentioned that they all use Node v24.13.1 / v24.18.1. Thus, you varied n8n and left Node version constant. IsOnCentralStack() is a V8 stack switching assertion. The V8 in Node 24 changed significantly. Node becomes a key untested variable.

Cheapest decisive test: run an older tag of n8n which uses Node 20/22, start 40 workflows and see if it starts successfully. If it does, you have narrowed it down to Node 24 regression and CAT-4134 would become a lot easier for them to investigate.

Workaround - and it answers your question 3.

There is no stagger env variable, but you can stagger it manually.

Start with ~20 active workflows. Once it becomes healthy, turn on the rest through the public API: PATCH /api/v1/workflows/{id}/activate, 10 workflows at a time with 30 second interval between each batch. No need to restart it.

Two possible outcomes, both are valuable:

  • If it reaches 77 and still works fine - it’s a startup burst issue, not a maximum amount of active workflows. It has to do something with registering many triggers simultaneously, not their number. It’s a completely different problem and it has to be included into the ticket.
  • If it dies at ~30 workflows regardless of timing - it’s a hard limit indeed and your theory about the fixed-size internal limit is correct.

In any case, you will make your workflows work today without waiting for the fix.

Another experiment, worth 15 minutes:

Make 35 dummy workflows — schedule trigger, one NoOp node, that’s it —
and boot with only those. If 35 dummy workflows boot fine, the threshold is not
a number of triggers, it is cumulative resource usage per workflow (Code nodes,
sub-workflows, credentials). If 35 dummies fail as well, it is all on the registration
side. One result would halve the search space for whoever gets CAT-4134.

But if you need the capacity now: run two containers with separate SQLite
databases, less than 29 active workflows each, distribute them between two containers.
It is not pretty and you will need to update webhook URLs from the senders,
but it is “can do in an hour” kind of ugly.

In the long run, I will switch to Postgres anyway at 200 workflow definitions —
it is another code path, and it takes DB out of variable space the next time
something like that happens.

@n8n — is CAT-4134 trackable in public, or is the only channel of its status this thread?
Would be useful to know whether to look for updates here or elsewhere.

Post your startup log if possible — the last 30 lines before assertion would tell
whether it crashes in the middle of registrations or right after finishing the process.

HI @EnzoPython3

Try this:

  1. Pass the stack flag and bump libuv threads via NODE_OPTIONS.
    In your Docker-compose.yml file, add the following:

    environment:
      - NODE_OPTIONS=--no-wasm-stack-switching
      - UV_THREADPOOL_SIZE=64
    
    
  2. Pin to Node 22 (LTS)
    If the flag doesn’t bypass the check on your specific image build, the best fix is to run n8n on a Node 22 base (or roll back to an n8n release tagged with Node 22 LTS). Node 22 does not verify this claim.

Why:

Node 22 handles native asynchronous callback trampolines and thread-pool execution without the strict central stack assertions added in Node 24 / V8 12.6+. Setting UV_THREADPOOL_SIZE=64 also prevents problems when lots of triggers register event loops at the same time when you first start the program.

Hope this help.

Correction to my last update - UV_THREADPOOL_SIZE=64 does NOT fix this,
sorry for the premature report.

What happened: with UV_THREADPOOL_SIZE=64 set, I staggered-activated 85
workflows via the API (29 → 93 active, zero crash) and cold-restarted
twice with 93 active - both boots came up clean. That looked decisive,
so I reported it as fixed here.

A few hours later, activating a second smaller batch (9 more workflows,
this time including some webhook-triggered ones alongside cron-triggered)
crashed the instance with the same IsOnCentralStack assertion, and it
crash-looped on every restart attempt after that.

Recovery took three rollback rounds: reverting the 9 new workflows still
crashed (~94 active). Reverting all 85 from the earlier “successful” test
too - down to 30 active - still crashed. Only stopped crash-looping once
I got back to the exact same 29 workflow IDs that were active before any
of this testing started.

So: UV_THREADPOOL_SIZE=64 didn’t prevent the crash, and it’s not a clean
active-count threshold either - 93 booted clean twice, but 30 (one below
the previously “safe” 29) crashed. My current guess, unconfirmed, is
that workflow composition matters (specifically the webhook-triggered
ones I added in the second batch), not just count, but I don’t have
solid evidence for that yet - could also be partly non-deterministic.

Back to square one on root cause. Has anyone run with UV_THREADPOOL_SIZE=64
for longer and still seen it, or found something in workflow type/trigger
kind that correlates?

@EnzoPython3

Thanks for the update! This means that the UV_THREADPOOL_SIZE only delays the issue of contention for the thread pool when it is first started. It does not fix the underlying V8 stack boundary assertion (IsOnCentralStack). Webhooks and event-driven triggers register asynchronous hooks differently than cron schedules, which trigger off-stack execution checks under Node 24 / V8 12.6+.

Here are some things to check next:

Did you pass the test on not using wasm stack switching? Make sure you set NODE_OPTIONS=--no-wasm-stack-switching with the thread pool size. Without it, V8 still enforces central stack checks.

Change to Node 22 (LTS): This is a Node 24+ / V8 assertion bug, so the best way to fix it is to pin n8n to a Node 22 base image. Node 22 doesn’t do this check.

Isolate Webhooks: If you want to help find the reason, try activating only webhook-triggered workflows at a low count (~5–10) to confirm if they start the crash loop.

Thanks for staying with this. I want to give the full picture, because I got the root cause wrong twice on this thread before I got it right, and your latest reply gives me a clean way to lay out what actually turned out to be happening.

First, the correction. Two of my earlier calls here were wrong. The “hard ceiling at around 29 to 31 active workflows” was a coincidence, and UV_THREADPOOL_SIZE=64 was never the fix. On that last point you and I agree completely; it was set for both the clean boots and the crashing ones, so it was never load-bearing. I have left it in place because it does no harm, but it does nothing for this.

What was actually causing it. One single workflow had a self-referential expression in its webhook trigger node:

"httpMethod": "={{$parameter.httpMethod}}"

Resolving httpMethod requires evaluating $parameter.httpMethod, which is the expression evaluating itself. That recurses with no exit, and under Node 24 V8 converts the recursion into the fatal assertion:

# Fatal error in , line 0
# Check failed: isolate_->IsOnCentralStack().

The workflow had been sitting there for months. What changed on the day it started crashing was that our image tag was floating on latest, and an auto-update pulled a Node 24 based build. So your read on Node 24 is right in one sense (Node 24 is what made this fatal), but the trigger was a workflow configuration error, not the number of active workflows and not webhook registration as a class.

Why it looked like a count challenge. Workflow registration order at boot is stable. Every binary-search subset that happened to include the poisoned workflow crashed, and every subset that excluded it booted fine, so “29 stable, 31 crash” was simply where that one workflow fell in the ordering. My later staggered-activation test that reached 93 active “cleanly” had, by luck, filtered that workflow out.

The trap that cost me the most time, in case it saves someone else theirs: an activation API call that returns a 502 when the process dies mid-request can still flip the workflow’s active flag in the database. My rollbacks kept landing back on a crashing state because one workflow silently stayed active. I only caught it by diffing the actual database state offline, with the container stopped (n8n list:workflow --active).

Now to your specific suggestions, because I did test them.

On isolating webhooks. This is the part I would gently push back on. The theory that webhook and event-driven triggers register asynchronous hooks that trip the central-stack check under Node 24 does not survive the current state of my instance. I am running 17 event-driven workflows active right now on n8n 2.35.5 and Node v24.18.1 (16 with n8n-nodes-base.webhook triggers, 1 with a formTrigger), and the container has cold-booted cleanly four times in a row with all of them registering. A sample:

Reply Email Forwarder            (webhook)
Global Client Request Responder  (webhook)
Mailbox Assignment Engine        (webhook)
Follow-Up Dismiss Handler        (webhook)   <- the one that was crashing, now fixed
Client Call - Approval Handler   (webhook)
Mailbox Assignment Request Form  (formTrigger)
... and 11 more

If webhook registration itself tripped the assertion under Node 24, none of these could be active on Node 24. They all are. So it is not the trigger class.

The cleaner version of your isolation test, which I ran, is a single-variable one. Same webhook workflow, one line changed:

  • with "httpMethod": "={{$parameter.httpMethod}}" it crashes at registration, every single time
  • with "httpMethod": ["GET","POST"] it boots clean, every single time

Same trigger type, same Node 24, same n8n version. The only variable is the self-referential expression. That is what recurses into the V8 assertion, not the webhook.

On --no-wasm-stack-switching. I could not apply it the way you have described, because Node refuses to start with it in NODE_OPTIONS:

node: --no-wasm-stack-switching is not allowed in NODE_OPTIONS

It is not on Node’s NODE_OPTIONS allow-list; it is only accepted as a direct CLI flag to node, which the n8n entrypoint does not expose cleanly. So as an environment variable it is a non-starter regardless of the underlying behaviour.

On pinning to Node 22. I do not think it is needed anymore, and this is the part I am most confident about. To prove the fix was real rather than something masked by an old image, I upgraded from 1.123.73 all the way to 2.35.5, which runs on Node v24.18.1, the exact Node line that was supposedly the challenge. Result: zero IsOnCentralStack crashes across four boots, 43 workflows active, restart-stable, and two of the three workflows that could not activate on the old image now activate. Downgrading to Node 22 would have “worked” too, but only by hiding the configuration error rather than resolving it.

The fix itself, for anyone who lands here later:

"multipleMethods": true,
"httpMethod": ["GET", "POST"]

Two suggestions for the @n8n team on CAT-4134, which I think stand regardless of whose theory is closer:

  1. A per-workflow configuration error in expression evaluation should not be able to take down the whole process at startup. A try/catch around individual trigger registration would turn this into a single-workflow failure rather than a full outage.
  2. Log the workflow name before registration, not only after it succeeds. Right now the last log line points at the innocent workflow (the last success), and the one that actually crashes never gets a line, which is exactly what sent me chasing a phantom count threshold for two days.

Genuinely open to being wrong if you are seeing this assertion on a workflow that has no self-referential expression anywhere in it. If so, that is a different bug to mine, and I would be glad to look at the workflow JSON with you. In our case, though, it came down to one expression that referenced itself, and Node 24 turning a previously survivable infinite recursion into a hard process kill.

Appreciate the back and forth on this one.

Great finding! Good share @EnzoPython3