Skip to content

v0.11.0

Choose a tag to compare

@buttjer buttjer released this 03 Sep 10:31
· 14 commits to main since this release
76ae1de

Changed

  • Breaking: a compiled .ts node now identifies itself on line 1, not on
    its last line.
    Open a bundled node in n8n and you used to see
    var __n8n_node = {} followed by a few hundred lines of inlined helpers,
    with the only trace of where it came from — // @ts-n8n sha256:… — at the
    bottom, where nobody looks, naming n8n rather than the tool that put it
    there. Push now writes one self-describing line where the editor opens:

    // n8n-decanter · workflows/orders/code/normalize-lines.ts · do not edit here · @ts-n8n sha256:39af5ea6… · v0.10.1 ca3c201 2026-08-20T09:14Z

    Nothing you already pushed has to change. The old trailing form is read
    forever and counts as fully in sync — push, diff and preflight say
    nothing about it, and no write is queued just to relocate a marker. Nodes
    adopt line 1 on their next real code push; there is no migration and no mass
    re-push. The break runs the other way: an older n8n-decanter will not
    recognise a node pushed by this version
    (its reader only looks at the last
    line) and would pull such a node down as a plain .js file. Pre-1.0, that is
    accepted rather than shimmed — upgrade the CLI everywhere that pulls the same
    instance. The @ts-n8n token itself is unchanged, so one grep -r @ts-n8n
    still finds both forms.

  • The layout guard now rejects a marker line in a node source file in either
    position, and in .ts files too
    (it was trailing-only and .js-only).
    That line is written by push; it is never source. Surfaces as before via
    preflight's layout check.

Fixed

  • An auth failure no longer sends you in a circle, and throttling is no
    longer called an expiry.
    Every failed OAuth token refresh used to print
    the same sentence — MCP session expired … re-run: n8n-decanter init — and
    both halves of it could be wrong. init reuses .decanter-auth.json
    whenever the host matches and never re-mints, so following the advice
    re-probed with the same dead credentials and finished with "credentials
    written anyway"; the only apparent way out was deleting a credential file
    that was often perfectly fine. The three cases are now told apart:

    • the refresh token really is spent (invalid_grant) — named as such,
      and pointed at init --reauth, a command that actually re-mints;
    • n8n is rate-limiting (429) — now retried with the same backoff the
      MCP endpoint has always had (honouring Retry-After, five attempts), and
      if it still fails it says the credentials are fine and never mentions
      init. OAuth discovery got the same retry; both sat on bare fetch calls;
    • anything else — the reason is named without a diagnosis, and nothing
      is suggested for discarding.
  • init's closing connection check acts on a spent token instead of
    shrugging.
    It had the failure in hand and printed "credentials written
    anyway". On a terminal it now offers to re-authorize on the spot; off one it
    names init --reauth. Deliberately narrow — a network error, a 401, or a
    403 "MCP access is disabled" still report as before, because none of them
    says anything about your credentials.

  • A successful first OAuth consent no longer warns "no MCP credentials
    yet".
    The check looked for a pre-existing auth file, so a browser
    authorization that had just succeeded still ended with advice to re-run
    init with a token.

  • <verb> --help prints help instead of running the verb. --help was
    only recognised in argument slot 0, and every --flag is stripped before
    dispatch, so n8n-decanter init --help was indistinguishable from a bare
    init — a request for help scaffolded a sync dir into whatever directory
    it was asked from. --help (and -h) now wins from any position, before any
    verb, any namespace and the picker. It prints that verb's own block rather
    than the whole listing, with only the notes that apply to it; help <verb> is
    the same question, and a bare help/--help still prints everything.

Added

  • init refuses to scaffold on top of a sync dir that already exists below
    the target.
    Run from the root of a bigger repo whose sync dir lives in
    n8n/, init used to drop a second decanter.config.json, the template,
    workflows/, shared/, tsconfig.json and the agent configs into the root.
    It now looks a few levels down first and stops, naming the sync dir it found
    and the --dir=/N8N_DECANTER_DIR form that addresses it from where you are
    — the same advice every read verb already gives. A terminal gets it as a
    question (y scaffolds anyway); a piped or flag-driven run exits 1 having
    written nothing, not even the target directory. Re-running init inside
    an existing sync dir is unchanged.

  • The provenance line carries the build stamp, not just the hash: the
    node's source path relative to the sync dir, "do not edit here", the CLI
    version, the git commit and the push time. The commit is HEAD at build time
    and renders as ca3c201+dirty when the sync dir has uncommitted changes —
    which, with commitOnPush on, is the normal case and reads correctly as
    "built from working-tree state on top of ca3c201". Fields that cannot be
    known (no git repo, unreadable package.json) are simply left out. None of
    it is hashed
    , so a rename, a new commit or a CLI upgrade never makes a node
    look changed — only the code below line 1 does.

  • Credentials now resolve in a git worktree. Both .env and
    .decanter-auth.json are gitignored, so a fresh linked worktree had neither
    and every credentialed verb died on N8N_HOST must be set — including the
    mcp connect guard, which left agents in a worktree with no n8n-instance
    tools at all. A worktree without its own credentials now reads the main
    checkout's copies (same path, resolved from git's own worktree pointer, no
    git subprocess). A local file still wins, so a worktree deliberately
    aimed at another instance keeps its own, and nothing else is redirected —
    workflows/, .decanter.json and decanter.config.json stay worktree-local.
    For .decanter-auth.json the shared file is the only correct shape: the
    refresh token is single-use and rotates, so copying it into a worktree
    (what Claude Code's .worktreeinclude would do) forks it into two token
    chains and kills the loser. New Git
    worktrees
    section covers the two
    remaining worktree gaps decanter cannot fix — a missing node_modules and the
    per-path MCP approval — and the troubleshooting entry for missing
    n8n-instance tools gained the worktree case.