v0.8.0
Added
-
The interactive picker offers a
--forceretry when apushhits the drift
guard. Previously the error was printed and you were dropped back at the
menu, having to leave the picker and re-runpush --forceby hand — even
though the CLI had just told you--forcewould fix it. Now it asks:✗ remote code changed since last sync — pull first (or repeat with --force to overwrite the draft) retry with --force and overwrite the remote draft? [y/N]The default is No: a bare
Enter, or anything other thany/yes,
returns to the menu exactly as before. Answeringyre-runs the same menu row
(flags included) with--force, overwriting the n8n draft only — the
published version is untouched.It only appears for failures
--forcecan actually fix. A layout
compliance error never prompts, because forcing does not bypass it. And this
is the interactive picker session only: piped and non-interactive runs never
prompt — they print the--forcehint and exit non-zero, unchanged. -
diff— the new verb for "show me the actual changed lines". It is the
promoted half ofstatus --diff: per-node unified line diffs of your local
code against the n8n draft,.tscompiled first (bundlingshared/*, so a
helper edit shows every importing node). Nodes that are in sync are omitted
entirely, and a clean tree says so in one line. Multi-ref likepull/push;
no workflow on a terminal opens the picker.It always exits 0.
diffis an inspection view, likegit diff— the
gate ispreflight. See the migration note under Removed. -
preflight --viewer(with--simulate): leaves a browsable throwaway
n8n running so you can open the replayed run in the UI — the interactive half
of the oldsimulateverb, now explicit instead of implied by a TTY. It does
not relax preflight's safety contract: the graded run stays headless with
--network-none, and the viewer is a second, separate container. A workflow
with a multi-batch loop reportssimulateas skipped under--viewer
("a preview, not a pass/fail check"), never as a pass. -
preflight --no-typecheckskips thetypescheck — the escape hatch the
retiredcheckverb had. -
The agent guard now logs a startup line and an audit trail (
mcp connect
andmcp servealike, on stderr):guard: connected to <host> — forwarding all n8n MCP tools, blocking jsCode writes in update_workflow guard: forwarded search_workflowsPreviously the guard spoke only when it blocked something, so an empty log
meant either "ran, blocked nothing" or "never started" — indistinguishable,
and opposite in meaning. The startup line settles that; the per-call lines
make the guard the one place that can answer what did an agent actually do
to my n8n instance?, since every MCP call passes through it.Tool names only — arguments are never logged, so the log stays safe to
attach to a bug report. -
Every
preflightfinding can now carrydetails[]— the full list behind
the one-line message: every layout violation, everytscerror, the
drifted node names, the viewer URL. Printed indented under the check line,
and present in--json. This is how the informationcheckprinted in full
survives its removal; without it, foldingcheckintopreflightwould have
truncated a 12-violation layout failure to its first line.
Changed
-
The picker lists pulled workflows newest-synced first, instead of the
folder's alphabetical order — the workflow you last pulled or pushed is under
the cursor when the picker opens. Unpulled remote rows keep their place after
the local ones. The signal is each workflow folder's sync timestamp, so it is
local activity, not committed history: right after a freshgit clone
everything looks equally recent and the list falls back to alphabetical until
your first pull or push. Scriptedlistoutput is deliberately unchanged. -
The CLI banner's
n8nwordmark now uses the brand orange, matching the
website (#E18528, derived from the site's accent color) rather than ANSI
red. It degrades gracefully — a 256-color terminal gets the nearest orange, a
16-color one keeps the old red — and piped output andNO_COLORstay plain,
exactly as before. -
Breaking:
preflight's profiles are replaced by two orthogonal flags.
--fulland theProfilemodel are gone. Depth is now--simulate
(additive — appends the local-engine run of your code) and--offline
(subtractive — drops the instance-reads tier), and they compose:preflight static + instance reads (the default gate) preflight --simulate + a local-engine run of your code preflight --offline static only — no instance contact preflight --offline --simulate static + local engine, no instanceMigration:
--full→--simulate. And read the next entry carefully —
--offlinestill exists but means something narrower. -
Breaking:
preflight --offlineno longer runs the local-engine replay.
It used to mean "static + engine, no instance"; it now means "static only".
The flag name is unchanged, so nothing will error — an air-gapped CI job
onpreflight --offlinesimply stops running the engine and quietly loses
that coverage. Migration:preflight --offline --simulateis the old
--offline. (This narrowing is also what makes--offlinefast enough for
the per-edit hook: it now spawns no Docker container.) -
Breaking:
preflight --jsonreplacesprofilewithflags. Where the
report carried"profile": "default" | "full" | "offline"it now carries
"flags": {"simulate": false, "offline": false}. Agents key on this. Every
other field is unchanged, and each entry inchecks[]gains an optional
details: string[]. -
preflightwith no workflow and an empty"workflows"config now checks
every pulled workflow instead of erroring with "no workflow ids" — the
behaviour thecheckverb had, kept now thatpreflightabsorbs it. -
A workflow folder with an unreadable
.decanter.jsonno longer fails your
gate.checkscanned folders, so a corrupt state file anywhere under
workflows/was a hard error for the whole run.preflightgrades resolved
workflows, and a folder whose state won't parse can't resolve to one — so
it is named in a warning (corrupt .decanter.json (…) — skipping this folder) and skipped, while every healthy workflow is still graded. The fact
is still reported; it just no longer blocks work on unrelated workflows. -
preflight --simulateaccepts multiple workflows. The oldsimulate
verb took exactly one; preflight loops, so a multi-ref run spins one engine
container per workflow, serially. -
The scaffolded template now runs
preflight --offlinewhere it ran
check— the PostToolUse verify hook, bothpackage.jsonscripts, and the
agent-facing prose inAGENTS.md/CLAUDE.md. Existing sync dirs keep
their files: re-runn8n-decanter initto be offered the refresh, and note
that init leaves locally-modified files alone, so a hand-edited hook or
package.jsonstill invokes a removed verb until you update it yourself. -
The scaffolded agent permission allowlist swaps its
check/status/
simulaterules for adiffpair (both the bare andnpxshapes);
preflight --simulateis already covered by the existingpreflight:*rule. -
The interactive picker's action menu is now
preflight,preflight --simulate,diff,pull,push,watch,executions— a menu row may
carry flags, which is how the browsable local-engine run survives the fold. -
The scaffolded agent contract now treats
pushas part of finishing the
work, and reserves "ask the user first" forpublish. A push lands on the
workflow's draft and never changes what is running; onlypublish/
push --publish/unpublishdo. The old rule gated both behind "only when
the user asks", so an agent handed "build me an hourly job that tags orders"
would build the structure in n8n, write and verify all the Code, and then
stop — leaving every Code node empty on the instance and the real code in
the repo, reporting "ready to push". Correct by the old rule, and not what
anybody asked for. Agents are now told to push once offline checks pass, to
say what landed, and to still ask first when the workflow is published/active
or a teammate is editing it. Going live remains a deliberate, user-requested
step. Affectstemplate/AGENTS.md.example(copied into new sync dirs by
init) and the/docs/agentssurfaces. (Surfaced by the Plan 35 blind field
test, where the "failing" agent was following the old contract exactly.) -
The scaffolded agent contract now follows the
preflight → push → test → publishflow.preflightis local-only (it no longer runs on the instance),
sotest— which runs the workflow's draft — is only meaningful after a
push. The old contract framedtestas a pre-push runtime check and ended its
loop atpreflight → push; both are now reconciled to the new order. Affects
template/AGENTS.md.exampleand the/docs/agentssurfaces. (Same surface
the Plan 60 verb reorder changed — kept in lockstep so the blind field test
grades agents against a contract that matches the tool.) -
Breaking:
preflightno longer runs the instance-sideteststage. It
rantest_workflowagainst n8n's draft, while every other stage graded
your local files — so whenever a push was pending, one score described
two different versions of the workflow, flagged only by a-10parity warn.
A report could read caution, 90/100 while its runtime evidence was about
code you weren't shipping.preflightnow grades local code only; the
instance is read for sync facts and never executed.The documented flow is
preflight→push→test→publish: verify
local code, make it the draft, run what you actually pushed, then go live.
Nothing was removed from the toolbox — the instance run moved to where it
means something.Migration: a CI job that gates on a plain
preflightno
longer gets an instance run inside that gate — the draft is never executed by
preflight. To keep instance-run coverage, addn8n-decanter testas its own
step after your push step (--require=testusers get a hard error with
this guidance; default-profile users get this note). -
Breaking:
preflight --require=testis rejected, with a message pointing
at the new flow rather than a bare "unknown check". Thetestid is gone
from--require, from--jsonchecks[], and fromcoverage. -
preflightauto-fetches a capture only under--simulate— the flag
that adds the one stage which consumes a capture. Without it there is no
runtime stage, so preflight no longer fetches, and a missing or stale capture
is reported asinforather thanwarn— nothing would consume one
(--offlinenever reaches the instance to fetch either way). -
The
paritywarn is reworded. It was a caveat about the runtime tier
grading the wrong artifact; that's no longer possible, so it is now the plain
next step: "local code differs from the draft in N node(s) — push to make it
the draft, then test". -
The local-engine replay is the sole runtime stage (
preflight --simulate)
and needs Docker. For runtime evidence without Docker, push and then run
test. -
Node-file type checking moved off TypeScript's legacy
node10module
resolution tomoduleResolution: "bundler"(withmodule: "preserve"), in
the scaffoldedtsconfig.json. This matches what push actually does —.ts
nodes are compiled with esbuild in bundling mode — and keeps the documented
extensionless import style working (import { total } from "../../shared/money"). It also unblocks TypeScript 6, which turnsnode10
into a hard error (TS5107), and TypeScript 7, which removes it outright.
node16/nodenextwere not chosen: they reject extensionless relative
imports (TS2835) and would force every node file to be rewritten with.js
extensions. No change to which Node.js versions are supported. Existing sync
dirs keep their currenttsconfig.json— re-runn8n-decanter initto be
offered the refresh; if you have hand-edited yours (or created it before
template baselines existed), init reports it and leaves it alone, so apply
the two-line change yourself when you move to TypeScript 6+.
Removed
-
Breaking: the
check,status, andsimulateverbs. All three were
variations on "check my thing", and telling them apart was the single most
confusing part of the surface. They fold into two:You used to run Now run check [workflow…]preflight --offline [workflow…]check --no-typecheckpreflight --offline --no-typecheckstatus [workflow…]preflight [workflow…](the scored summary)status --diffdiff [workflow…](the changed lines)simulate <workflow>preflight <workflow> --offline --simulatesimulate --network-nonepreflight --simulate(always network-none)Each removed verb exits non-zero naming its replacement, so a stale script
fails loudly rather than silently. Note thesimulaterow: the verb
needed no credentials, and a barepreflight --simulatestill runs the
instance tier — add--offlinefor the credential-free equivalent.Nothing was lost with the verbs. The compliance guard still gates every push
and watch save (only the standalone view is gone, and preflight'slayout
finding now lists every violation); the publish state, live-lags-draft note
and snapshot-stale hint are preflight'slifecycleandsnapshotfindings. -
Breaking:
status's CI exit codes.statusexited 1 on a code conflict
or remote drift, and pipelines gated on that.diffalways exits 0.
Migration: gate onpreflight, whosedriftcheck fails on a
CONFLICT and warns on remote drift (add--fail-on=warnto gate on the warn
too). -
simulate --network-none.preflightalways runs the graded engine
replay with no network, so the flag had nothing left to turn off. -
Breaking: the
preflight --quickprofile. With theteststage gone it
was byte-identical to the default profile, and rather than redefine it into a
meaning users would have to learn and then unlearn, it is gone. Static-only
checking is nowpreflight --offline; the local-engine replay is
preflight --simulate(see the profile→flags entry above, which retired
--fulland the whole profile vocabulary in the same release).Neither
--quicknor--fullis recognized any more — they are simply
gone, not rejected with a migration. The CLI ignores flags it does not
know, sopreflight --fullnow runs the default gate (no engine) and
preflight --quickruns it too, both exiting 0. If you have either in a CI
job, update it in the same step as this upgrade — nothing will tell you at
runtime.--full→--simulate;--quick→--offline. -
Breaking:
preflight --trigger <node>. It existed only to feed the
removed instanceteststage; since that stage's removal it parsed and did
nothing.test --trigger <node>(the post-push instance run) keeps the flag
— that is where trigger selection acts.
Fixed
-
Renaming a
.tsnode in n8n no longer leaves it stuck on "push pending".
A.tsnode that imports fromshared/(or an opted-in npm package) is
compiled with esbuild's bundler, and esbuild labels every bundled module with
a// <path>comment. That label used the node's own filename, so it
landed inside the compiled bytes — and inside the@ts-n8n sha256:marker.
Rename the node in n8n,pullrenamescompute.ts→ümläut-nödé.ts, and
the artifact changed even though not one line of your source did:diff
reported a difference that was purely the comment line, and the node read
"local changes — push pending" until you pushed a no-op. The entry label is
now a fixed name, so a pure rename round-trip is byte-stable and comes back
clean.One-time effect when you upgrade: because the compiled bytes changed, the
firstdiff/pullafter upgrading reports "modified, not yet pushed" for
every.tsnode that has imports — a comment-line difference only. One
pushper workflow clears it, and it is a plain push, not a conflict: the
remote code is untouched, so the drift guard does not trip and--forceis
not needed..tsnodes without imports compile through a different path
that never embedded the name, and.jsnodes are unaffected. -
The scaffolded MCP guard now starts under a local install, not only a
global one.init's.mcp.json/opencode.jsonspawned the guard as a
baren8n-decanter mcp connect, which only resolves when the CLI is on the
agent'sPATH— i.e. a global install. With decanter installed as a
local project dependency the command silently failed to start, so the
agent got no guarded route and fell back to whatever other n8n MCP it had,
unguarded. The scaffolded command is nownpx --no-install n8n-decanter mcp connect, which resolves the localnode_modulesbin and a global
install;--no-installnever downloads from npm, so a genuinely missing
install fails loudly instead of silently. (Plan 58.) -
The scaffolded agent permissions now cover
npx n8n-decanter …— including
thepush --forcedenial. The same local-install gap applies to the CLI
calls an agent makes in a shell: under a local (devDependency) install a bare
n8n-decanter <verb>is not onPATH, so the working form is
npx n8n-decanter <verb>. The permission matcher keys on the command prefix,
so that form previously matched neither the allow rules (every safe call
would stop to ask) nor thepush --forcedeny rule — meaning the
force-push guard rail could be sidestepped simply by invoking throughnpx.
Both lists now carry thenpxforms (Claude Codesettings.jsonand
opencode), and the scaffoldedAGENTS.mdtells agents to add the prefix when
the bare command is not found. Installing globally is not required — a
per-sync-dir devDependency remains fully supported. (Plan 58.)