If you're using Claude Code or Codex to write application code, point it at @n8n/cli too. Authenticate with N8N_URL and N8N_API_KEY as environment variables, and your agent can pull a live workflow as JSON, script structural edits with a throwaway Python pass, validate the graph before pushing, and — this is the part that actually changes how you work — pull real execution data with execution get --includeData to see exactly which node failed and why, instead of guessing from the outside. Editing n8n workflows stops being a canvas you click through and becomes an artifact you can read, diff, and debug like any other code.
I got here the ordinary way: iterating on an n8n workflow built around an AI Agent node, and hitting a wall that had nothing to do with my prompt engineering. The workflow would occasionally return a broken reply in production, and the only way to find out why was to open the n8n UI, click into the executions tab, and manually expand node outputs one at a time. That's fine for one bug. It stops being fine once you're iterating fast and the agent itself is misbehaving in ways that don't show up as an obvious crash. So I stopped treating n8n as a UI-only tool and started treating it as something Claude Code could read and write directly, the same way it reads and writes a repo.
This isn't a Claude Code vs. Codex piece — the pattern is identical for either. What matters is that your coding agent already knows how to run a CLI, read JSON, and reason about structured output. @n8n/cli gives it exactly that surface for a system that's normally locked behind a browser.
Authenticate with environment variables, not login
@n8n/cli ships an interactive login command that saves your instance URL and API key to a local config file. Skip it. Every subcommand also reads N8N_URL and N8N_API_KEY directly from the environment:
bash
# File: (shell session, not a repo file)export N8N_URL="https://your-n8n-instance.example.com"export N8N_API_KEY="your-api-key"
npx @n8n/cli workflow list --format=json
Using env vars instead of login keeps the credential scoped to the current shell session rather than persisted to disk in a CLI config file your agent (or you, six months later) might forget is there. If your n8n API key is instance-wide rather than scoped to one project — common on a shared or self-hosted instance — this also means you're not leaving a standing credential file behind after the session ends.
Look at the whole instance before you touch anything
The first command I run in a fresh session is always the same, and it's not the one that touches the workflow I actually care about:
bash
npx @n8n/cli workflow list --format=json
On a shared n8n instance, this single call tells you something a lot of people skip checking: how many other workflows exist, who they belong to, and whether your API key's reach extends beyond the one project you're supposed to be working on. I've worked on instances where one API key genuinely could see and edit every client's workflows. That's not a reason to avoid the CLI — it's a reason to run workflow list first, confirm the workflow ID you're about to touch, and keep every subsequent command scoped to that ID explicitly. An agent that only ever calls workflow get <id> and workflow update <id> for one confirmed ID is safer than one that goes hunting.
Pull the workflow as JSON and back it up first
bash
npx @n8n/cli workflow get <workflow-id> --format=json > backups/workflow-id.$(date +%Y%m%d-%H%M%S).json
This is the step people skip because it feels like ceremony. It isn't. A live n8n workflow isn't a file in your repo with git history behind it — it's a row in n8n's own database, and n8n's built-in version history is not something I'd rely on as the only safety net when an agent is scripting structural changes to it. A plain JSON snapshot, timestamped, sitting next to your other project files, costs one command and gives you an actual rollback path if an edit goes wrong. Do this before every structural change, not just the first one.
Script the edit — don't hand-edit the JSON
Once you have the workflow JSON, the temptation is to open it and start editing fields by hand. Don't. A single n8n workflow can easily have ten or more nodes, each with its own parameters, credentials, typeVersion, and a connections graph that has to reference node names exactly. Hand-editing that JSON is how you end up with a dangling connection to a node you just renamed.
Instead, have your agent write a short Python script that loads the JSON, makes the specific structural change, and writes a new file:
This one check catches the most common mistake an agent (or a human) makes when editing a node graph by script: renaming or removing a node and forgetting one of the connections that pointed at it. It costs nothing to run and it runs before the workflow ever reaches n8n.
Push safely: deactivate, update, reactivate — as three separate calls
If the workflow you're editing is active, structural changes — adding or removing nodes, rewiring connections — are safer applied while it's briefly deactivated, then reactivated once the update succeeds. Doing this as three separate CLI calls, rather than assuming workflow update alone is sufficient, gives you a clear point to stop and inspect the result before flipping the workflow back on. After the update, fetch it fresh with workflow get and confirm the node count and connections match what you expected — don't trust the update command's own echoed response as the only confirmation.
The actual payoff: debugging with live execution data
Everything above is table stakes. This is the part that changes how you work once you've done it a few times.
n8n keeps a full execution history, and @n8n/cli can pull it with node-level detail:
bash
npx @n8n/cli execution list --workflow=<workflow-id> --status=error --format=json --limit=5
npx @n8n/cli execution get <execution-id> --includeData --format=json
The second command returns the complete runData for every node in that execution — every tool call, every input, every output, in order. This is the same information you'd normally have to click through node by node in the n8n UI, except now your agent can grep it, diff it across runs, and reason about it the way it reasons about a stack trace. Three real bugs came out of exactly this workflow in one session:
An AI Agent hit its iteration ceiling by calling the same tool sixteen times with identical arguments. Pulling the execution data showed every one of those sixteen calls had the exact same parameters — the model wasn't exploring different scenarios, it was just repeating itself until it ran out of budget. The fix was a one-line addition to the system prompt: never call the same tool twice with identical parameters, since the result will be identical every time. Obvious once you see the actual tool-call sequence; invisible from the outside.
A tool-call parameter format mismatch that sometimes self-corrected and sometimes didn't. A custom tool expected a parameter as a JSON-encoded string; the model occasionally sent it as a plain object instead, got a validation error back, and usually retried correctly — except when it didn't, at which point it burned through its remaining iterations on the same mistake. execution get --includeData surfaced the exact validation error text on the first bad call, which is the only way to know this is happening at all, since a self-correcting model looks completely fine most of the time.
The worst one: a silent empty response marked as success. When the agent exhausted its iteration budget after the above, n8n didn't return an error — the execution's own status field said "success" while the actual output was an empty string. That's a strictly worse failure mode than a crash, because nothing downstream knows to treat it as a problem; a user just gets a blank reply. The fix was a small Code node placed right after the AI Agent node, checking whether the output is empty and substituting a graceful fallback message before anything gets returned:
javascript
// File: (n8n Code node, placed after the AI Agent node)const output = ($json.output ?? '').toString().trim();
if (!output) {
return [{ json: { output: 'Something went wrong on my end — please try again in a moment.' } }];
}
return [{ json: { output } }];
None of these three bugs were visible from the chat interface sitting in front of the workflow. All three were visible in the first execution's full runData once I actually pulled it.
A shared-memory gotcha worth knowing about
One more failure mode, because it's easy to reproduce without realizing it: if you build a small classifier sub-agent (say, one whose only job is to extract a structured value from the latest message) and wire it to the same conversation-memory node as your main persona agent, the classifier can start "seeing" the full chat history — including the main agent's own prior replies — and drift into responding conversationally instead of returning strict structured output. It's not a prompt problem so much as a wiring problem: a classifier that only needs the current message doesn't need conversational memory attached at all. Removing that connection fixed it outright. If you have a sub-agent whose job is narrow and mechanical, keep its inputs narrow and mechanical too.
Prefer a deterministic node over an LLM judgment call
The last change worth calling out isn't a bug fix, it's a design correction. The workflow originally used an LLM call to answer a yes/no question — "is this value over a fixed threshold?" — before branching. That's outsourcing a comparison a plain IF node can do exactly right, every time, for free, with no latency.
The fix: have the LLM do the part only an LLM can do — pull a normalized number out of a sentence like "my budget is around forty-five thousand" — and hand that number to a plain n8n IF node for the actual comparison. Same outcome, less surface area for the model to get wrong, and a branch condition you can read and reason about without wondering what the model was thinking.
FAQ
Do I need n8n's paid/enterprise tier to use @n8n/cli?
No — the CLI talks to any n8n instance's REST API using an API key, which is available on self-hosted and cloud instances alike. What matters is that the API key has access to the workflows you want to manage.
Is it safe to let an agent edit a workflow that's actively handling production traffic?
Treat it the way you'd treat a production database migration: back up first, deactivate before structural changes, reactivate only after you've verified the pushed version with a fresh workflow get. Non-structural changes (like a system-prompt tweak) are lower risk, but the deactivate-update-reactivate sequence costs almost nothing and removes an entire category of "half-applied edit while live" failure.
Why not just use n8n's REST API directly instead of the CLI wrapper?
You can — @n8n/cli is a thin wrapper around the same REST API. The CLI is worth using because it gives your agent a stable, documented command surface (workflow get, execution list, and so on) instead of hand-rolling HTTP requests and reading n8n's OpenAPI spec from scratch every session.
What if the agent introduces a bug during editing — how do I roll back?
This is exactly why you back up the JSON before editing. workflow update <id> --file=<backup>.json restores the previous state as directly as the original edit was applied. Keep every backup file, not just the most recent one, since you may need to go back more than one step.
Does this replace testing the workflow manually?
No. Execution data tells you what happened in a run that already occurred — it doesn't replace deliberately sending test messages through the workflow and checking the result. Use both: script the edits, then exercise the workflow with real inputs before you trust it.
Where this leaves you
The n8n UI is still where I'd go to eyeball a workflow's shape or drag a new node in for the first time. But once a workflow exists and you're iterating on it — tuning a system prompt, fixing a branch condition, chasing down why an AI Agent occasionally does something strange — @n8n/cli plus an agent that can script JSON edits and read execution data is a faster, more reviewable loop than clicking through the canvas and the executions tab by hand. The debugging technique alone — pulling full runData for a failed execution instead of guessing from the chat transcript — is worth adopting even if you never script a single structural edit.
Let me know in the comments if you have questions, and subscribe for more practical development guides.
Thanks,
Matija
open
"backups/workflow-id.latest.json"
as
for
in
"nodes"
if
"name"
"AI Agent"
"parameters"
"options"
"systemMessage"
"\n\nNever call the same tool twice with identical parameters "
"— the result will be identical. Vary at least one real parameter."
with
open
"edited-workflow.json"
"w"
"utf-8"
as
False
2
# File: scripts/validate-workflow.py
import
with
open
"edited-workflow.json"
as
"name"
for
in
"nodes"
for
in
"connections"
if
not
in
f"unknown source: {source}"
for
in
for
in
for
in
if
"node"
not
in
f"unknown target: {target['node']}"
print
"errors:"
or
"none"
Approach
When it's tempting
Trade-off
LLM judgment call (yes/no or classification)
The condition seems to need "understanding" the message
Adds latency and cost, and a small but real chance of a wrong answer on a question that has one correct answer
LLM extracts a value, plain node decides
You need natural-language understanding and a reliable branch
Slightly more setup (a parser/schema for the extracted value), but the branching itself is deterministic and reviewable