There’s a specific failure mode in n8n that looks like success: the workflow shows active: true, every execution finishes green, and nothing happens. No error, no output, just silence.
The root cause: versioned workflows
n8n 2.x does not run from workflow_entity.nodes the way older versions did. The scheduler executes whatever version workflow_history[activeVersionId] points at. When you hand-edit the workflow JSON directly in SQLite, you update the draft row but not the activeVersionId pointer — so the scheduler keeps running the old snapshot while the UI shows your new nodes.
The classic signature is versionId != activeVersionId: your edits land in the draft (versionId), but the active version (activeVersionId) still points at the previous snapshot. Everything looks updated, and the scheduler ignores all of it.
The correct deploy path
- Stop n8n.
docker compose run --rm --no-deps -T n8n import:workflow --input=/home/node/.n8n/x.jsondocker compose run --rm --no-deps -T n8n publish:workflow --id=X- Start n8n.
The import step writes the workflow, and publish:workflow flips the active-version pointer correctly. That ordering is what makes the change actually take effect for the scheduler.
The lesson
When state lives in two places (a draft and an active snapshot), editing one and not the other is a silent no-op. If you’re tempted to fix a workflow by editing SQLite, stop — the CLI import + publish path exists precisely so you don’t have to know the version-pointer internals. And when a workflow is “active but does nothing”, check whether versionId and activeVersionId match before you debug the workflow logic.
Related
- Fixing ECONNRESET on a Self-Hosted HTTP Bridge
- Repairing a Corrupt SQLite Database with .dump and COMMIT
- Docker Compose .env Changes Need Container Recreation
Guides & explainers
Filed under: Ops & Automation, Automation, n8n, Self-Hosting, SQLite