Back to Nodes

ClaudeCode

Last updated Oct 2, 2026

Five n8n nodes for Claude Code v2 via the Claude Agent SDK: the agent (attachments, effort, thinking, Ultracode, tool restriction, spend cap, graceful timeouts, diagnostics), a Chat Model and two Agent tools for n8n AI Agents, plus a free usage read for p

887 Weekly Downloads
1,414 Monthly Downloads

Included Nodes

ClaudeCode
ClaudeCodeUsage
ClaudeCodeChatModel
ClaudeCodeTool
ClaudeCodeUsageTool
ClaudeCodeAgent
ClaudeCodeSubagent
CodeReviewKit
ClaudeCodeVideoFrames

Description

Claude Code for n8n

Run the Claude Code agent as a step in an n8n workflow: give it a prompt and a project directory,
and it reads, writes and runs things the way it does in a terminal — then hands the result to the
next node.

The package installs two nodes. Claude Code runs the agent. Claude Code Usage reads how much
of the account’s plan is left and when each window resets, so a workflow can check capacity before
it starts spending.

![n8n](https://n8n.io/)
![Claude Code](https://claude.ai/code)
![npm](https://www.npmjs.com/package/@joaoveiga/n8n-nodes-claudecode)
![License](LICENSE.md)

Built on the Claude Agent SDK, for Claude Code v2. Requires Node 22+. Originally derived from the
work of Adam Holt and John Lindquist — see Credits.

What it gives you

Control over what a run costs and how long it takes. Effort from low to max, plus Ultracode
(xHigh with dynamic workflow orchestration). A Max Budget (USD) limit, because Max Turns and
Timeout bound how long a run goes, not what it costs; it is checked between turns, so one turn
can go past it. Model and fallback model, thinking depth,
and a thinking display mode.

Timeouts that still report what happened. A run that overruns is interrupted rather than killed,
so it reports the tokens, cost and session it actually used and hands over what it finished.
Killing the process makes the SDK report none of that. See Timeouts.

A real bound on the tool set. Restrict Built-in Tools is the allowlist that actually limits
what the agent can reach. The SDK’s own Allowed Tools is an auto-approve list — it pre-approves
tools rather than restricting them, which is a distinction worth knowing before you rely on it.
Disallowed Tools removes tools from the model’s context entirely.

Evidence of what ran. Every run reports the model the CLI resolved to, the effort it actually
applied after its own downgrades, which models were billed, the session id, and whether Ultracode
orchestration was available. A failed run reports its real cost instead of $0.

Failures that behave like failures. Stopping the n8n execution stops the agent, instead of
leaving it running and spending with its output discarded. A failed run reaches the error output
branch instead of hiding behind a green execution.

Plan capacity you can branch on. The Usage node reads the same data behind the CLI’s /usage
and sends no prompt, so nothing is billed. Gate a batch on remaining capacity, wait for a reset, or
alert before hitting the wall. A CLAUDECODEOAUTH_TOKEN session — the usual Docker setup — cannot
read that endpoint at all, so there is an opt-in fallback that reads the two main windows off
inference response headers for about $0.001. See Usage & Plan Limits.

Files, not descriptions of files. Attach the binary data already on the n8n item — a Monday
screenshot, a CSV export, an HTML capture, a PDF — and the model receives the file itself. Images go
in as images, so vision works without any tool being enabled; a file too large or of a type no
content block can carry is written to a temporary directory the agent can read from instead. See
Attachments.

Video, as frames Claude can read. Claude cannot watch a video. Claude Code Video Frames samples
one into timestamped images, single frames for a clip and labelled mosaics for an hour-long
recording, with ffmpeg (installed on the n8n server). The images then go to the AI Agent, the Claude Code node or
the Claude Code Agent like any other image. See Claude Code Video Frames.

Sessions you can resume. A Session ID on Continue, so concurrent executions stop sharing
one conversation.

One output envelope. The three output formats share a shape; the format picks which optional
sections you get. See Output Formats.

Contents

Start here — Install · Your First Workflow · Templates · What people build with it

Reference — Authentication · Features · Attachments · Timeouts · Output Formats · Claude Code Chat Model · Agent Tools · Claude Code Agent · Claude Code Subagent · Code Review Kit · Video Frames · Usage & Plan Limits · Node versions · Configuration Examples

Also — Notes worth knowing · Development & Contributing · Credits

What people build with it

Five templates ship in workflow-templates/, ready to import:

| Template | Shape |
|—|—|
| Automatic Bug Fixer | GitHub issue webhook → agent diagnoses and writes a fix → opens a PR |
| Documentation Generator | Schedule → agent reads the codebase → writes docs → commits |
| Customer Support Automation | Support ticket → agent reproduces the issue → drafts a fix and a reply |
| Plan Limit Guard | Usage node checks remaining capacity → branches before the agent spends anything |
| Claude Code Review Team | Code Review Kit reads a diff → Agent with three reviewer subagents returns a verified review JSON → Kit anchors and fingerprints the items |

Other things that fit the shape well: turning a Slack command into a pull request, triaging error
logs into issues with a diagnosis attached, generating a migration script from a described schema
change, or reviewing a diff against standards that live in the repo’s own CLAUDE.md.

One caution, since the templates make it look easy. These workflows let an agent write files and
run commands. Keep a human review step before anything merges or deploys, keep Max Budget (USD)
set, and use Restrict Built-in Tools to bound what a given workflow can reach. An agent that can
open a PR is useful; one wired straight to production is a liability.

Install

Prerequisites

1. Claude Code CLI (required on your n8n server):

   npm install -g @anthropic-ai/claude-code
   claude  # Authenticate (requires Claude Pro/Team subscription)
   

Install in n8n

#### Option 1: Via n8n UI (Recommended)
1. Open your n8n instance
2. Go to Settings → Community Nodes
3. Click Install a community node
4. Enter: @joaoveiga/n8n-nodes-claudecode
5. Click Install
6. Restart n8n when prompted

#### Option 2: Manual Installation

cd ~/.n8n/nodes
npm install @joaoveiga/n8n-nodes-claudecode

Restart n8n

#### Option 3: Docker

docker run -it --rm 
  -p 5678:5678 
  -e N8NCOMMUNITYNODE_PACKAGES=@joaoveiga/n8n-nodes-claudecode 
  -v ~/.n8n:/home/node/.n8n 
  n8nio/n8n

Note: For Docker, you’ll need to ensure Claude Code CLI is installed inside the container. Consider creating a custom Dockerfile.

📦 NPM Package: @joaoveiga/n8n-nodes-claudecode

Authentication

By default both nodes run as whoever the n8n host is logged in as — the account behind
claude login on that machine, or an ANTHROPICAPIKEY / CLAUDECODEOAUTH_TOKEN exported into
the container. That is one identity for the whole instance: one bill, one set of plan limits, and no
way to run two workflows on two accounts.

The Authentication parameter, on both nodes, changes that per execution.

| Mode | What it does | Credential |
|—|—|—|
| Host (default) | Runs as the account the n8n container itself is logged in as. Exactly what every run did before this parameter existed. | none |
| API Key | Runs this execution on an Anthropic API key, billed as API usage. | Claude Code API |
| OAuth Token | Runs this execution on a Claude Code OAuth token (claude setup-token), billed against that account’s Claude plan. | Claude Code OAuth Token API |

Three workflows can therefore run side by side on three different accounts, each with its own
credential, rotated in n8n rather than in the container.

How it works, and why it actually overrides the host

The node hands the credential to the Claude Code CLI subprocess through its environment —
ANTHROPICAPIKEY for a key, CLAUDECODEOAUTH_TOKEN for a token — and the CLI only falls back
to the host’s ~/.claude/.credentials.json when neither of those is set. So the credential wins,
and the host login is never read, written or refreshed for that execution.

Every other authentication variable the SDK recognises is removed from that environment first,
including ANTHROPICAUTHTOKEN and the Bedrock, Vertex and Foundry keys. A container that exports
ANTHROPICAPIKEY globally cannot leak it into a run you pointed at an OAuth token. Everything
that is not authentication — PATH, HOME, proxy variables — is passed through untouched.

A run that used a credential says so, in diagnostics.auth ("apiKey" or "oauthToken"). A
host-mode run has no such field at all, which is why adding this feature moved no existing
workflow’s output and needed no new node version.

What is not per-credential

The credential changes who pays and who is rate-limited. It does not partition anything else:

  • ~/.claude is still the host’s, so settings.json, MCP servers and plugins apply to every run
  • whichever credential it uses. That is deliberate — a workflow’s tools should not change with its
    billing.

  • The session store is shared. Continue with no Session ID still resolves “the most recent
  • conversation in this working directory” across every execution on the instance, regardless of
    credential. Set an explicit Session ID for concurrent runs, as before.

    Notes

  • A credential that is selected but empty fails the item. It does not fall back to the host —
  • running on an account you explicitly pointed away from is worse than stopping.

  • The Claude Code API credential has a Test button. The Claude Code OAuth Token credential
  • does not: those tokens have no documented HTTP endpoint to test against, and a test built on a
    guess would put a red cross on a working credential. The first run is the test.

  • An API key authenticates against the API, not a Claude plan, so the Usage node reports no plan
  • limits for it — see Usage & Plan Limits.

  • A rejected credential takes minutes to say so. The CLI retries a 401 with backoff rather than
  • giving up. Measured: with the default 300s Timeout the run fails after ~184s with
    Failed to authenticate. API Error: 401 API key is invalid. — clear, but slow. With a Timeout
    shorter than the retry window (20s, measured) your own timeout fires first and the run reports
    0 assistant turns and an unknown cost, saying nothing about authentication at all. So: a
    credentialed run that times out with no turns is a credential to check, and Debug shows the
    authentication_failed responses in the n8n log either way.

    Features

    Project path

    Point the node at a directory and the agent works there: it reads the existing code, picks up the
    conventions already in it, and loads whatever CLAUDE.md, .claude/settings.json and .mcp.json
    that directory carries. This is the difference between an agent that guesses and one that follows
    the project it is editing.

    In Docker, the directory has to be mounted into the container — a path that exists only on the host
    fails with a spawn ENOENT the SDK misattributes to an architecture mismatch, so the node checks it
    up front and says so plainly instead.

    Tools

    The agent gets the Claude Code tool set: reading, writing and editing files, running shell commands,
    searching a codebase, fetching web pages, managing todos, and launching subagents. Three selectors
    shape what a given workflow can use:

    | Selector | What it actually does |
    |—|—|
    | Restrict Built-in Tools | the real allowlist — an empty selection means the full set |
    | Allowed Tools | the SDK’s auto-approve list; pre-approves, does not restrict |
    | Disallowed Tools | removes tools from the model’s context entirely |

    Permission modes

    bypassPermissions is the default, because n8n runs headless and cannot answer a prompt — anything
    else would silently deny every tool that is not pre-approved. plan produces a plan and writes
    nothing; pair it with Allow Plan Execution if you want the agent to be able to leave plan mode
    and act, since without a permission callback registered plan mode has no exit.

    MCP servers

    The node does not configure MCP itself. It loads whatever the target directory already declares in
    .mcp.json and .claude/settings.json, which means a workspace set up for interactive Claude Code
    works unchanged here — database access, GitHub, Slack, or anything custom. See
    examples/project-with-mcp/.

    Attachments

    Binary data on the incoming item can go to Claude with the prompt. The typical case is fixing a bug
    from evidence: a Monday item arrives carrying a screenshot of the broken screen, a CSV export of the
    failing rows and an HTML capture of the response, and all three reach the model in the same request.

    Two parameters select what goes:

    | Parameter | |
    |—|—|
    | Attach All Binaries | send every binary property on the item, ordered by property name — Auto / On / Off, defaulting to Auto |
    | Binary Properties | a comma-separated list, when you want only some of them — available only when Attach All Binaries is Off |

    Both sit under Project Path, since they describe what goes into the request rather than how it
    runs.

    Attach All Binaries defaults to Auto, which is on for a node you add now — an item that
    carries a file usually carries it for a reason, and the property names often come from upstream and
    vary, since Monday emits data0, data1, data_2 with no fixed count. An item with no binary
    data is unaffected: nothing is collected and nothing is sent. Set it to Off and name properties
    instead when you want to leave a heavy attachment out and not pay tokens for it, or Off with an
    empty list for a text-only request.

    Upgrading changes nothing in a workflow you already built. Auto means on from node version
    1.3 and off below it, and a node keeps the version it was created with — so an existing node stays
    off until you say otherwise. To turn it on there without recreating it, set it to On, which
    overrides the version either way.

    Video attachments

    Claude cannot watch video, so from Claude Code 1.5 and Claude Code Agent 1.2 a video on the
    item is converted to timestamped images before the request, by the same extraction as
    Claude Code Video Frames. It is recognised from its type or extension,
    and its bytes are streamed to a temporary file and never loaded into memory. Short clips become
    single frames and long recordings become 3×3 mosaics. Each image follows a caption with its times,
    the time is drawn on it, and a text subtitle track goes in as a document. The temporary files are
    gone before Claude starts.

    Four options, in Additional Options on Claude Code and in Options on the Agent:

    | Option | Default | |
    |—|—|—|
    | Video Attachments | Auto | Auto converts from those versions and stages the file below them (what earlier versions always did). Convert to Frames and Stage the File override the version either way. |
    | Video Sampling | Auto | Frames, Mosaic, or Auto, which picks frames when each covers 5 s or less |
    | Video Max Images | 15 | for all the item’s videos together; the request never carries more than 20 images in all, counting attached images, because past 20 the API shrinks every image |
    | Max Video Size (MB) | 2048 | replaces Max Attachment Size for a video |

    diagnostics.attachments.videos[] reports each video: the sampling mode, the strategy, the
    coverage, the images and their times, and the ffmpeg that ran. It is absent when no video was
    converted. A video that cannot be converted fails the item, naming the property. It never falls
    back to staging, which would mean an answer given without the video. It needs ffmpeg on the n8n
    server (Installing ffmpeg); this path finds it through FFMPEG_PATH or the
    PATH. The rest of What to know applies here too.

    Subagents see the video through files. A subagent receives only the prompt the orchestrator
    writes for it, never the images in the orchestrator’s message. So from Agent 1.3, when at least
    one subagent is enabled, copies of the images are also written to the temporary directory, one
    .jpg per image named with the time it shows (rec-frame-003-00-01-05.jpg). They come with an
    index, rec-frames.json, that maps each file to its times. The orchestrator is told the paths and
    that a subagent can open them with Read, so it can hand a stretch of a long recording to each
    subagent. diagnostics.attachments.videos[].stagedFrames says where the files are and lists, under
    subagentsWithoutRead, any enabled subagent whose tools leave out Read and so cannot open them.

    Attached directly, or staged on disk

    Each file takes one of two routes, decided by its type and its size.

    Attached directly — the file is part of the request. The model has it immediately, no tool is
    involved, and it works even with Restrict Built-in Tools set to something that excludes Read.

    | Type | Ceiling |
    |—|—|
    | image/png, image/jpeg, image/gif, image/webp | 5 MB — an API limit |
    | application/pdf | 20 MB — an API limit |
    | text/*, plus JSON, XML, YAML, SVG, SQL, JS/TS | Inline Text Size Limit (KB), default 256 |

    Staged on disk — everything else: a file over one of those ceilings, or a type with no matching
    block (.xlsx, .zip, .heic, .docx). It is written to a temporary directory, that directory is
    made readable to the agent, and the prompt tells it which files are there. The agent reads what it
    needs with Read, and can grep a 40 MB log rather than swallowing it.

    The temporary directory is removed when the item finishes — on success, on error, and on a timeout.

    Narrowing by file type

    Allowed Extensions in Additional Options is a multi-select of ~120 extensions. Leave it empty —
    the default — and every file is considered. Select some and only those are sent; anything else on
    the item is skipped and the run continues.

    That is deliberately different from the failures below. A size cap or a property that is not on the
    item is a refusal of something you asked for, so it stops the item. The extension filter is you
    saying which types you want, so not sending the rest is doing what you asked — it never fails
    anything.

    It pairs with Attach All Binaries being on: attach everything, then narrow to the types that
    matter.

    Attach All Binaries   [x]
    Allowed Extensions    PNG, CSV, PDF

    item carries shot.png, export.csv, archive.zip → shot.png and export.csv sent → archive.zip skipped, reported, run continues

    Two details worth knowing. The filter runs on the derived filename, so a binary that arrives
    with no filename at all is still judged on the extension its MIME type implies. And it runs before
    the count and size checks, so a file you told it to ignore can never be what trips **Max Attachment
    Count**.

    Everything skipped is listed under diagnostics.attachments.skipped with the property name, the
    derived filename and the extension — because “ignore and continue” is exactly the pattern that goes
    wrong quietly, and you should be able to see it did.

    Choosing the text limit

    This is the one size knob that is a real trade rather than an API limit. An attached file sits in the
    context on every turn, so 256 KB of CSV is roughly 64k tokens per turn. A staged file costs
    nothing until the agent reads it — but the agent has to decide to read it, and might not.

    Attach when the file is the point of the request. Stage when it is reference material the agent
    should search. Set the limit to 0 to stage every text file.

    Two things that will bite you

    A permission mode other than bypassPermissions can make a staged file unreachable. Reading from
    the staged directory is a Read call, and under default or dontAsk a call that is not
    pre-approved is denied — n8n runs headless and cannot answer a prompt. The node’s default is
    bypassPermissions, so this only applies if you changed it; if you did, add Read to **Allowed
    Tools. A tool restriction is handled for you: when files are staged and Restrict Built-in
    Tools** is non-empty, Read is added to it, because staging a file the agent cannot read means the
    run answers without the evidence and still reports green.

    A file that cannot be sent fails the item. A binary property that is not on the item, a file over
    Max Attachment Size (MB), or more attachments than Max Attachment Count each stop that item
    with a message naming the property. This is deliberate: the alternative is an answer about a file the
    model never saw. Continue On Fail still applies, and routes the item to the error output.

    What the run reports

    When at least one attachment was sent or skipped, diagnostics.attachments says what happened:

    {
      "count": 2,
      "totalBytes": 42123456,
      "skipped": [
        { "propName": "data_3", "fileName": "archive.zip", "extension": "zip" }
      ],
      "inline": [
        { "name": "shot.png", "mimeType": "image/png", "bytes": 245760, "as": "image" }
      ],
      "staged": {
        "dir": "/tmp/n8n-claude-9f3c1a7b2e5d4088",
        "files": [{ "name": "dump.csv", "mimeType": "text/csv", "bytes": 41877696 }]
      }
    }
    

    count is what was sent — a skipped file is reported separately rather than folded in, because
    “3 attachments” meaning “2 sent and 1 dropped” is how a wrong answer gets missed. skipped is always
    present so an expression can read .skipped.length without guarding, and staged is null when
    everything was attached directly. The whole key is absent on a run with no attachments and no skips,
    which is why this feature needed no new node version.

    Timeouts

    A long agentic run that hits its Timeout used to report nothing but a string:

    Operation timed out after 900 seconds. Consider increasing the timeout in Additional Options.
    

    Fifteen minutes of real spend, and no record of how much it cost, how far it got, or which session
    to resume. That is because killing the Claude Code process outright makes the SDK emit no accounting
    at all.

    Timeout Wrap-Up Grace (Seconds) fixes this. Instead of killing the process at the deadline, the
    node interrupts it a little earlier and asks Claude to stop and hand over what it did. Interrupting
    is what makes the SDK report the run, so a timed-out node now returns real numbers and a usable
    answer.

    Claude Code timed out after 900s (wrap-up summary returned) — 47 turns, $4.81 spent, session c0ffee…
    

    How the grace window works

    The grace is taken out of the Timeout, never added to it. A node set to timeout: 900 with
    Timeout Wrap-Up Grace: 60:

    | At | What happens |
    |—|—|
    | 840s | Claude is interrupted. The SDK emits the run’s cumulative cost, tokens and session ID within ~100ms. |
    | 840s–900s | Claude writes a handover: what it finished, what is unfinished, how to resume. |
    | 900s | Hard abort, unconditionally — a wrap-up that hangs cannot push the run past your Timeout. |

    Set it to 0 to kill the process at the Timeout instead. You then get the session ID, the tool
    timeline and the last thing Claude said, but no tokens and no cost — the SDK never reports them
    for a killed process, and this node will not invent them.

    The grace is clamped to half the Timeout, so a large grace on a short Timeout cannot swallow the run.

    > On timing: the interrupt and the abort both fire exactly on schedule, but after an abort the
    > SDK spends about two seconds killing the subprocess. So a hard-aborted node returns up to ~3s after
    > its Timeout, while the graceful path returns under it. Claude stops working on time either way.

    Node versions

    Anything that changes what a node emits is gated behind its version, never switched on by a
    package upgrade. A node keeps the version it was created with, so upgrading the package never
    changes an existing workflow. New nodes start on the current default, 1.5.

    | | 1 | 1.1 | 1.2 | 1.3 | 1.4 | 1.5 (default) |
    |—|—|—|—|—|—|—|
    | Timeout Wrap-Up Grace default | 0 — killed at the Timeout | 60 | 60 | 60 | 60 | 60 |
    | Failure item shape | flat report at the top level | { error, message, details } | same as 1.1 | same as 1.1 | same as 1.1 | same as 1.1 |
    | Failure items on the error output | stay on the main output | routed to the error output | same as 1.1 | same as 1.1 | same as 1.1 | same as 1.1 |
    | Output shape | one per format | one per format | one envelope | one envelope | one envelope | one envelope |
    | Attach All Binaries Auto | off | off | off | on | on | on |
    | A subagent in the background | answers from the first result | same as 1 | same as 1 | same as 1 | answers from the final result | same as 1.4 |
    | Video Attachments Auto | staged | staged | staged | staged | staged | converted to frames |

    All of them get the diagnostics, the session ID and the self-describing error message.
    Attachments work on every version; only what Attach All’s Auto means differs.

    1.4 and background subagents. When Claude sends a subagent to the background, the CLI writes a
    result for the turn that launched it (“I’ve launched the agent, I’ll wait”) and another once the
    subagent reports back. Below 1.4 the item’s result can be that first, interim text. From 1.4 the
    answer, success, errorText and the diagnostics come from the final result, and the graceful
    timeout waits for a subagent still out instead of treating the interim result as the end of the
    run. The Chat Model and the Task Tool got the same change as their version 1.1.

    To give an existing node the newer output shape without recreating it, set Output Envelope to
    Unified in Additional Options. It defaults to Auto, which routes by version and changes nothing.

    That option exists because n8n has no version picker: a node keeps the version it was created with,
    and there is no UI to move it. Without the override, adopting the new shape meant deleting the node
    and configuring a replacement from scratch. The override only works in that direction — a new node
    that wants the old shape is rare, and can pin "typeVersion": 1.1 in the workflow JSON.

    Reading the timeout data in the next node

    Set the node’s On Error to Continue (using error output) — or *Continue (using regular
    output)* — and the timeout item carries the whole report under details:

    {{ $json.error }}                      // one-line summary: timed out, turns, cost, session
    {{ $json.message }}                    // token breakdown and how to resume
    {{ $json.details.totalcostusd }}     // 4.812 — cumulative across the whole run
    {{ $json.details.num_turns }}          // 47
    {{ $json.details.usage.outputTokens }}
    {{ $json.details.session_id }}         // feed to the Continue operation to resume
    {{ $json.details.result }}             // the handover summary
    {{ $json.details.timedOut }}           // true
    

    > Why details and not the top level? n8n sends an item to the error branch when the item has a
    > top-level error field, or when its json holds nothing beyond error, message and details.
    > The top-level field looks like the natural choice, but n8n then rewrites the item’s json to just
    > { error: } and every metric is lost. Using the three permitted keys keeps the routing
    > and the report. On node version 1 the report stays flat at the top level, and never reaches the
    > error branch at all.

    Everything inside details:

    | Field | Meaning |
    |—|—|
    | error | Self-describing message string, same convention as every other n8n node |
    | errorType / timedOut | 'timeout' / true — a timeout is never mistakable for a generic failure |
    | terminationReason | 'timeoutgraceful' or 'timeouthard_abort' |
    | timeoutSeconds, wrapUpGraceSeconds, wrapUpSucceeded | What was configured, and whether the handover made it |
    | result, resultSource | The handover text, and whether it came from the wrap-up or from the last thing Claude said |
    | totalcostusd, num_turns, usage, modelUsage | Cumulative spend. null when the process was killed |
    | usageReliable | false when the numbers are unavailable, so you can branch on it |
    | session_id | Resume the run with the Continue operation |
    | toolTimeline, toolUseCount, toolTimelineTruncated | The last 100 tool calls, and the true total |
    | duration_ms, assistantTurns, messageCount, diagnostics | Everything else about the run |

    With On Error left at its default the execution still fails, as it should. What you see in the
    error panel is the message and the description:

    Problem in node ‘Claude Code‘
    Claude Code timed out after 25s (wrap-up summary returned) — 6 turns, $0.0651 spent, session dd05ec45…
    Grace window: 10s. Tokens: 650 in / 1080 out / 100893 cache read / 3022 cache write.
    Models: claude-haiku-4-5, claude-sonnet-5. Tools used: 3.
    Resume with the Continue operation and session id dd05ec45….
    

    The full report is also attached to the error object and saved with the execution, but n8n 2.34.6
    does not render it in the panel — Other info there shows only the node type, versions, time and
    stack trace. That is why the message and description are written to stand on their own. Use one of
    the continue modes above when a following node needs the numbers.

    An Error Workflow gets more than the panel does. The Error Trigger receives the whole report:

    {{ $json.execution.error.message }}                    // the one-line summary
    {{ $json.execution.error.context.totalcostusd }}     // 0.0257
    {{ $json.execution.error.context.session_id }}
    {{ $json.execution.error.context.usageReliable }}
    {{ $json.execution.lastNodeExecuted }}                 // "Claude Code"
    

    So an alerting workflow can report what a timed-out run cost without any extra wiring. The message
    still carries the headline numbers inline, because that is the line a human reads in a Slack alert.

    Output Formats

    The Claude Code node offers three (the Usage node has one fixed shape — see
    Usage & Plan Limits):

  • Structured: everything — the answer, the metrics, a run summary, the transcript
  • Messages: the transcript, for debugging
  • Text: just the answer and the metrics, for chaining
  • Since node version 1.2 all three share one envelope, and Output Format selects which optional
    sections come with it:

    {
      result: 'pong',              // the answer, whatever it took to recover it
      success: true,               // only an explicit success counts
      errorText: '',               // separate from result, so a partial answer is distinguishable
      metrics: {
        duration_ms: 4821,
        num_turns: 2,
        totalcostusd: 0.0412,    // null when the run produced no result — never a fabricated 0
        usage: { / … / },
        modelUsage: { / per model / },
        session_id: '1e76098f-…',  // feed into Session ID to resume
      },
      diagnostics: { / resolved model, applied effort, whether Ultracode fired / },
      messages: [ / messages + structured only, and only with Include Raw Transcript on / ],
      summary: { / structured only / },
    }
    

    Existing workflows are untouched. A node keeps the typeVersion it was created with, so nodes
    built before 1.2 keep emitting exactly what they always did — a flat duration_ms and
    totalcostusd on Text, messageCount on Messages, a nested metrics on Structured. A newly
    added node starts on the current default, which keeps this envelope. To move an old one, set
    Output Envelope to Unified, or delete and re-add it.

    What 1.2 changes, for anyone porting a workflow across:

    | | 1 / 1.1 | 1.2 |
    |—|—|—|
    | where the metrics live | flat on Text, nested on Structured, absent on Messages | always metrics |
    | an unknown cost | 0 on Text | null |
    | messageCount | on Messages only | dropped — use messages.length |
    | error text | folded into result with a [PARTIAL - …] prefix | result plus a separate errorText |
    | summary.toolUseCount | counts a tool only if it opened the turn | counts every tool use |
    | metrics on a graceful timeout | the interrupt’s per-turn numbers | the cumulative ones |

    Claude Code Chat Model

    The third node plugs Claude Code into n8n’s AI Agent as its Chat Model. Add an AI Agent,
    click the Chat Model port, pick Claude Code Chat Model — Memory and Tool sub-nodes connect as
    usual, and the Chat Trigger streams the answer token by token.

    What you get over the native Anthropic Chat Model:

  • Your Claude plan, not an API key. The same per-execution Authentication
  • as the other two nodes — Host, API Key, or a Claude Code OAuth Token billed against that
    account’s plan.

  • A project behind the model. Set Project Path and the run loads that directory’s
  • CLAUDE.md, MCP servers and settings; Claude Code’s own tools (Bash, Read, Glob, …) are
    available alongside the Agent’s, governed by the same Restrict / Allowed / Disallowed Tools,
    Effort, Thinking, Max Budget and Timeout options as the main node.

    How the Agent’s tools run. Tools you connect to the Agent are handed to Claude Code as
    in-process tools (mcpn8n) and executed inside its session, so the Agent sees
    one model turn per call while each Tool sub-node still logs its executions. Two Agent features
    therefore do not apply — human-in-the-loop tool approval and Return Intermediate Steps.

    Version 1.1 (the default for a new node) answers from the run’s final result when Claude sent a
    subagent to the background, and its graceful timeout waits for that subagent; version 1 can answer
    with the interim “I’ve launched the agent” text. See Node versions.
    Require Specific Output Format works: the model hands back the formatting call the Agent’s
    parser expects.

    Two ways to remember, and you pick which. The Conversation Memory parameter chooses:

    | Mode | Where the history comes from |
    |—|—|
    | Auto (default) | Session when Session ID is filled in, Memory sub-node otherwise |
    | Claude Code Session | The session — real multi-turn, tool results included. Requires a Session ID; an empty one fails the node instead of running stateless. Any connected Memory is ignored |
    | n8n Memory Sub-Node | The connected Memory, flattened into the prompt. Portable across containers and workers; the model reads the conversation rather than continuing it. Session ID is hidden — and cleared when the workflow saves, so note your key before switching |

    Never both at once — that would put every prior turn in the context twice.

    With a Memory sub-node, each Agent call is a fresh Claude Code session that reads the
    conversation so far. For real multi-turn memory, set Session ID to any **stable conversation
    key** — a Discord/WhatsApp/user ID, straight off the webhook ({{ $json.body.sessionId }}). The
    node hashes the key into a deterministic Claude Code session ID, creates the session on the
    conversation’s first message and resumes it on every next one — prior turns and tool results
    included, with no storage anywhere: not in the workflow, not in the caller. A raw session
    UUID from a previous run also works. When Session ID is set, connected Memory history is not
    re-sent, so the Memory node becomes unnecessary. Sessions live on the n8n container’s disk —
    recreate the container and conversations start over. responsemetadata.sessionstate says what
    happened on each call: created, resumed, or new.

    What it costs. Every Agent call spawns the Claude Code CLI (a few seconds) and carries Claude
    Code’s system prompt. This is not a cheap chat model — it is Claude Code with an Agent plugged
    into it. For a plain LLM call on an API key, the native Anthropic Chat Model is the better tool.

    Claude Code Tools for AI Agents

    Two purpose-built ai_tool sub-nodes, for any Agent chat model — the native Anthropic or
    OpenAI models included:

    Claude Code Task Tool — hands the Agent a full coding agent as a tool. Fixed contract: the
    Agent sends one task string, Claude Code runs it in the configured Project Path (reading files,
    running commands, writing code), and the result comes back as text. Timeout, budget cap, tool
    restrictions and per-execution Authentication are all per-tool options. Failures — timeouts
    included — return as text the Agent can read and react to, never as a dead run. Version 1.1 (the
    default for a new node) returns the final result when Claude sent a subagent to the background;
    version 1 can return the interim text. See Node versions.

    Claude Code Usage Tool — a zero-argument tool that returns the account’s plan usage
    (utilisation and reset time per window) as a JSON report, with the same scope-retry and opt-in
    probe escalation as the Usage node.

    Both derive their tool name from the node’s name on the canvas, so rename the node and the Agent
    sees the new name. Avoid names that collide with Claude Code’s own tools — a node called
    “Task” makes the model reach for its built-in Task subagent instead of yours.

    Both also log every call under the node, with the input and the result, so a tool that ran is
    visible on the canvas rather than silent.

    > Alpine note: the official n8nio/n8n image ships no bash, so Claude Code’s Bash tool
    > fails there with “No suitable shell found”. Disallow Bash on the tool and let it use Glob
    > and Read, or run n8n on an image that has a shell.

    Measuring what a sub-node spent

    The main Claude Code node puts metrics and diagnostics in its output, so a following node
    reads them with an expression. A sub-node cannot do that — its output is not on the main
    chain, and $('Claude Code Chat Model').item.json.metrics has nothing to resolve against.

    So the numbers leave by calling a workflow. On the Chat Model and the Task Tool, under Options:

    | Option | What it does |
    |—|—|
    | Report Usage to Workflow | after every call, hands the run to this workflow |
    | Process Name | sent as process_name, so rows from different workflows stay apart |

    Each call sends one item:

    {
      "process_name": "support-assistant",
      "run_key": "1274:Claude Code Chat Model:1",
      "callerworkflowid": "wF1aBcD2eFgH3iJk",
      "callerexecutionid": "1274",
      "node_name": "Claude Code Chat Model",
      "metrics":     { "durationms": 7329, "numturns": 2, "totalcostusd": 0.0070098,
                       "usage": { … }, "modelUsage": { … }, "session_id": "b575370d-…" },
      "diagnostics": { "requestedModel": "…", "resolvedModel": "…", "appliedEffort": "…", … }
    }
    

    metrics and diagnostics are the same objects the main node emits — a test asserts the
    metrics are deep-equal to what the main node’s output builder produces — so a collector written
    for the main node consumes a sub-node’s run with no changes.

    run_key identifies a call, not a session. A conversation that resumes a Claude Code session
    carries the same session_id across executions, so keying a table on it would make every message
    overwrite the last, and totalcostusd is per run rather than cumulative. The session id is an
    ordinary field instead, which is what makes “what did this conversation cost” a GROUP BY.

    Three things that will bite you, all measured:

  • The collector must be published. n8n 2.x has a draft/publish model, and executeWorkflow
  • resolves the published version — an unpublished one is refused with *”Workflow is not active
    and cannot be executed”*. Marking it active is not enough.

  • Its trigger must accept the payload. An Execute Workflow Trigger that declares fields
  • rejects anything else with “At least 1 field is required”; declare the fields you want or set
    the trigger to passthrough.

  • Reporting never costs you the answer. The call is made with doNotWaitToFinish and any
  • failure is swallowed into the debug log — a collector that is down loses a metric, not a run.

    The Usage Tool has no reporting, deliberately: it performs a plan read, so there is no
    session, no turns and no tokens. A row from it would be a line of zeros in a table of runs.

    > Why not the auto-generated variants? n8n also synthesizes “tool” wrappers from the regular
    > nodes (usableAsTool). Those expose every node parameter and — for the main node — a
    > zero-argument schema unless you hand-write $fromAI() expressions. They keep working, but the
    > dedicated tools above are the supported path.

    Claude Code Agent

    A root node that runs Claude Code over each item, as the Claude Code node does, and opens it to the
    canvas: n8n tools plug into it, Claude Code Subagent nodes give it a team, and a run can be made to
    end in a JSON object that matches a schema. The per-run settings of the Claude Code node (Project
    Path, Model, Effort, Max Turns, Timeout, Authentication, attachments, the three tool selectors, Max
    Budget, Timeout Wrap-Up Grace) work the same here.

    It is a separate node rather than a new version of Claude Code, so no stored workflow changes.

    Connections

    | Input | Takes | |
    |—|—|—|
    | main | the items to run on | one run per item |
    | Subagents (ai_agent) | Claude Code Subagent nodes, any number | see Claude Code Subagent |
    | Tools (ai_tool) | any tool sub-node: Code Tool, Call Workflow Tool, a node used as a tool, MCP Client Tool | handed to Claude Code as mcpn8n |
    | Parser (ai_outputParser) | one Structured Output Parser | read only when Output Mode is Output Parser |

    Connected tools run inside Claude Code’s session over the same bridge as the Chat Model, and each
    tool node logs its own calls. An MCP Client Tool arrives as a toolkit, and n8n names its tools
    _ with spaces turned into underscores: a node called MCP Client exposing
    lookup becomes mcpn8nMCPClientlookup. Restrict Built-in Tools cannot unplug a
    connected tool, because the bridged names are always added on top. With subagents connected it also
    gets Agent and Task on its own, the delegation tool the subagents are reached through.
    diagnostics.bridgedTools lists what was bridged.

    Keep connected tools read-only. The Agent usually reads code it did not write, and that code can
    carry text written to steer a model. A tool that can merge, post or delete gives such text somewhere
    to go. Publish the result from a node after the Agent, where the workflow decides and the model does
    not.

    Parameters that matter

    | Parameter | What it does |
    |—|—|
    | Output Mode | Text (default), JSON Schema or Output Parser. The two schema modes emit the object as structured; result keeps the text |
    | JSON Schema | the schema, in JSON Schema mode. Its top level must be "type": "object"; anything else fails the item before a process starts |
    | Instruction Files | one path per line, relative to Project Path, appended to the system prompt |
    | Read Instruction Files From Ref | shown once Instruction Files is set: a branch, tag or commit (origin/main) to read those files from with git, instead of from disk. Empty (the default) reads the working tree |
    | Session | New (default) or Resume |
    | Session ID or Key | with Resume: a session UUID, or any stable key (a ticket, chat or user id) hashed into a deterministic session id. The first run with a key creates the session, later runs resume it |
    | Subagent Orchestration | Auto (default, Claude decides) or Required |
    | Verification | a second run that tries to refute the structured items; see below |
    | Options → Allow Claude.ai Connectors | off by default; see Cost |
    | Options → Report Usage to Workflow, Process Name | the same collector call as the sub-nodes, after every run including failures and timeouts |
    | Options → Include Transcript | adds messages, off by default |

    Output Parser mode unwraps. n8n’s Structured Output Parser wraps your schema as { output: … }.
    The Agent sends the inner schema, so structured is your object in both schema modes and
    $json.structured. reads the same either way.

    Structured output can fail in two ways, and both fail the item. The model can run out of retries
    (the SDK stops after 5 attempts with errormaxstructuredoutputretries), or it can give up and
    answer in prose, which the CLI still reports as a success, only without the object. The Agent counts
    a run as successful only when the object is present. Both failures carry errorType:
    'structured_output'
    with metrics and diagnostics, reach the error output under *Continue (using
    error output)*, and diagnostics.structuredOutput.attempts says how many times the model tried.

    A second delivery is accepted when it is valid. With subagents in the background the CLI writes
    one result per turn, and a later turn can deliver the object again; the CLI refuses a delivery only
    when it fails the schema, and the rejection goes back to the model with the validator’s message. The
    object emitted is the last one accepted. So a model that tries to replace its object with one the
    schema cannot hold (a finding with no line where line is required) leaves the earlier object in
    place. From version 1.1 the Agent says so: diagnostics.structuredOutput carries accepted,
    rejected, rejections (the validator’s last five messages) and superseded: true when a rejected
    delivery came after the emitted object with no accepted one after it. Findings that have no line
    belong in a field of the schema that does not require one.

    Instruction Files are for rules that are not in CLAUDE.md. The repository’s CLAUDE.md loads
    on its own. Each file is wrapped in a tag naming it and joined, after the node’s System Prompt, into
    the one text appended to Claude Code’s system prompt. A missing file is skipped and listed in
    diagnostics.instructions.missing, so one list works across repositories that do not all have it. A
    path that leaves the Project Path, directly or through a link, or a file over 256 KB, fails the item.

    Instruction Files can come from another ref than the one under review. With **Read Instruction
    Files From Ref** set to, say, origin/main, each listed path is read with git at that ref, still
    relative to Project Path, and the working tree is not consulted. That is how a review keeps its rules
    to the target branch: a pull request that edits .review/rules.md is reviewed by the rules it is
    trying to change, not by its own version of them, and no Execute Command node is needed (n8n 2.x
    leaves it out by default). The rules are the working tree’s: a path absent at the ref is listed in
    diagnostics.instructions.missing, one with .. or an absolute path, a directory, a symbolic link
    or a file over 256 KB fails the item. diagnostics.instructions.ref names the ref when one was used.
    The clone must contain the ref. A ref that is refused (-x, a range such as a..b) or that the
    clone does not have fails the item before Claude Code starts, and says to fetch it; a shallow CI
    clone often lacks the base branch, so fetch it (git fetch origin main) before the Agent runs. The
    option needs git on the PATH of the n8n process, as the Code Review Kit does.

    Required orchestration asks, it cannot force. It adds a line to the prompt asking Claude to
    delegate to every enabled subagent at least once. diagnostics.subagents shows whether each one
    ran, and the workflow decides what to do about one that did not. It keeps an entry for a disabled
    subagent too, marked enabled: false, so “switched off” and “not called” read differently.

    **To run only some subagents in an execution, switch the others off rather than naming them in
    the prompt.** Required lists every enabled subagent, whatever the prompt says, and a subagent that
    is off is not in the session at all.

    From version 1.1 the prompt ends by saying the run is unattended: nobody reads the conversation
    or answers questions, so the model decides and delivers rather than ending with a question. With a
    schema and subagents it also asks for one delivery, after every subagent has reported, and to fix and
    resend a rejected one.

    Versions

    A node keeps the version it was created with; new nodes get 1.3.

    | | What it changed |
    |—|—|
    | 1 | the original |
    | 1.1 | metrics.durationms, numturns and usage summed over every result of the run (the CLI reports them per turn; cost and modelUsage were already cumulative); diagnostics.structuredOutput reports each delivery; diagnostics.subagents[].model; the unattended line in the prompt |
    | 1.2 | a video attachment is converted to timestamped frames (Video attachments); everything else as 1.1 |
    | 1.3 | with a subagent enabled, copies of those frames are also written to disk for it to Read; everything else as 1.2 |

    Verification

    With Output Mode JSON Schema or Output Parser, Verification checks the items of one array in the
    structured object before they leave the node. A second run resumes the same session, receives the
    selected items with their indices, tries to refute each one from the repository, and answers
    { keep, drop }. The node applies that verdict, not the model: a dropped item leaves the array, and
    an item the verifier did not mention is kept and listed in unjudged.

    | Setting | |
    |—|—|
    | Enabled | off by default |
    | Items Path | dot path to the array inside structured, e.g. items or review.comments. Required |
    | Only Items Where Field, Equals Any Of | check only items whose field has one of these comma-separated values, e.g. severity and blocker. The rest are kept unchecked |
    | Verifier Instructions | what the second run is told. The default asks it to read the code and give a concrete reason for every drop |

    A verification that fails (an error, a timeout, no verdict) drops nothing: the item keeps the
    unverified array and verification.status is failed with the reason. An empty selection skips the
    second run (status: 'skipped'). An Items Path that does not name an array can only be found out
    after the first run has been paid for, so it also ends as failed and the answer is kept.

    The second run has its own full Timeout, and costs a second run.

    Output

    The Claude Code node’s 1.2 envelope (result, success, errorText, metrics, diagnostics),
    plus the fields below, each absent when unused. Shortened from real runs:

    {
      "result": "{"summary":"…","verdict":"REQUEST_CHANGES","items":[…]}",
      "success": true,
      "errorText": "",
      "structured": {
        "summary": "Adds a slug helper and uses it in the post routes.",
        "verdict": "REQUEST_CHANGES",
        "items": [
          { "path": "src/slug.ts", "line": 12, "type": "bug", "severity": "blocker", "body": "…" }
        ]
      },
      "verification": {
        "status": "verified",
        "checked": 2,
        "kept": 1,
        "dropped": 1,
        "unjudged": [],
        "droppedItems": [
          { "index": 1, "reason": "Lines 7-8 throw on an empty or blank title.", "item": { … } }
        ],
        "costUsd": 0.032469
      },
      "metrics": { "durationms": 9471, "numturns": 5, "totalcostusd": 0.061199,
                   "usage": { … }, "modelUsage": { … }, "session_id": "6c56ca50-…" },
      "diagnostics": {
        "resolvedModel": "claude-haiku-4-5",
        "subagentToolUses": 2,
        "subagents": [
          { "name": "alpha", "invocations": 1, "completed": 1, "totalTokens": 8707, "toolUses": 0, "durationMs": 1885 },
          { "name": "beta",  "invocations": 1, "completed": 1, "totalTokens": 8658, "toolUses": 0, "durationMs": 1495 }
        ],
        "bridgedTools": ["mcpn8nsecretcode", "mcpn8nMCPClientmcpsecret"],
        "instructions": { "loaded": [".review/rules.md"], "missing": [] },
        "structuredOutput": { "mode": "jsonSchema", "attempts": 1 },
        "sessionState": "resumed"
      }
    }
    

    | Field | Meaning |
    |—|—|
    | structured | the validated object, in the schema modes. After Verification, without the dropped items |
    | verification | the verdict applied, and costUsd, the second run’s own share of the cost |
    | diagnostics.subagents | one entry per connected subagent, in name order, from the run’s task events. One that never ran shows invocations: 0. From 1.1, model is the model it was configured with, inherit resolved to the Agent’s |
    | diagnostics.structuredOutput | mode and attempts; from 1.1 also accepted, rejected, rejections and superseded |
    | diagnostics.subagentToolUses | delegations, counted under both tool names the CLI uses (Agent and Task) |
    | diagnostics.sessionState | with Resume only: resumed, or created when there was no session under that id yet |

    The answer is the last result. Subagents run in the background by default, so the CLI writes a
    result for the turn that launched them (“I’ve launched the agents, let me wait”) and another once
    their work is in. The Agent reads the last one.

    Cost

  • Every item is a full Claude Code run. Subagents are separate contexts billed on top, and
  • metrics.totalcostusd includes them. Verification is a second run.

  • Max Budget (USD) is checked between turns, not during one. A single large turn can overshoot
  • it: measured, a run capped at 0.50 ended at 1.31. Treat it as a stop condition. Timeout and Max
    Turns are the hard limits.

  • claude.ai connectors are off by default. A full claude.ai login connects the account’s cloud
  • connectors (mail, drive and the like) into every run, even when no settings are loaded, and their
    tool schemas are paid for on every turn: one measured run carried about 320k extra tokens. The
    Agent turns them off unless Allow Claude.ai Connectors is on. MCP servers configured in the
    project are not affected.

  • A resumed session reports a cumulative cost. On a run that resumes a session,
  • totalcostusd and modelUsage include every earlier run of that session, while num_turns,
    duration_ms and usage cover the run alone. Measured: a key’s first execution cost 0.0031 and
    its second reported 0.0053, of which 0.0022 was its own. **A collector must not sum
    totalcostusd across executions of one session**; keep the latest value per session_id, or
    subtract consecutive ones. The Claude Code node’s Continue and the Chat Model’s Session ID behave
    the same way. Inside one execution the Agent handles it for Verification: metrics counts both
    runs once, and verification.costUsd is the difference.

  • Report Usage sends one report per CLI run. A resume that found nothing and then created the
  • session is two runs, and two reports. A verification run is not reported on its own; the item’s
    report carries the combined metrics.

    Limits

  • Sessions live in the n8n process’s ~/.claude. A session resumes from any Project Path on the
  • same machine, but it does not follow a run to another queue-mode worker: there, Resume finds
    nothing and creates a new session under the same id (sessionState: 'created'), without the
    earlier conversation. Keep session work on one worker, or share ~/.claude between them.

  • Parallel work inside one run means subagents. n8n runs the branches of one execution one after
  • another, so three Agent nodes on three branches take three times as long. Three subagents on one
    Agent can work at the same time.

  • The Alpine note above applies. The official image has no bash; disallow Bash or use an
  • image with a shell.

    Claude Code Subagent

    A sub-node that defines one specialist for a Claude Code Agent: its name, when to use it, its
    instructions, model and tools. It runs nothing by itself. Connect it to the Agent’s Subagents
    input and the Agent hands the definition to Claude Code, which delegates to it as it works. A
    subagent runs in its own context inside the Agent’s session, with the Agent’s authentication and
    Project Path.

    It connects over n8n’s ai_agent connection type, so the editor offers it only on the Agent’s
    Subagents input and refuses to wire it into n8n’s own AI Agent.

    | Parameter | |
    |—|—|
    | Name | lowercase letters, digits and hyphens, unique among one Agent’s subagents. Two with the same name fail the item before anything runs |
    | When to Use | what the orchestrator reads to decide when to delegate |
    | Instructions | the subagent’s system prompt |
    | Model | Inherit From Agent (default) or any model in the list |
    | Enabled | on by default. Off leaves the subagent out of the session, so the Agent cannot delegate to it and Required orchestration does not list it. Takes an expression, to decide per execution. A value other than true or false fails the item |
    | Options → Effort | Inherit From Agent or a level |
    | Options → Max Turns | per delegation. 0 inherits |
    | Options → Allowed Tools | the built-in tools this subagent may use. Leave it and the next one empty to inherit every tool the Agent has |
    | Options → Additional Allowed Tool Names | comma-separated names added to the allowlist. A tool connected to the Agent is mcpn8n |
    | Options → Disallowed Tools | comma-separated names this subagent may not use |
    | Options → Skip Project CLAUDE.md | run without the CLAUDE.md files, for a subagent that gets everything from its delegation |

    Each delegation appears on the Subagent node’s own execution log: the prompt it was handed as input,
    and the status, summary, tokens, tool uses and duration as output. The CLI does not stream a
    subagent’s own messages, so that summary is the record of what it did.

    Code Review Kit

    The deterministic half of a review bot. It has no model: it runs git in a clone and reshapes
    findings.

    | Operation | Takes | Returns |
    |—|—|—|
    | Diff Context | Project Path, Base Ref, Head Ref | mergeBase, files[] (path, status, additions, deletions, plus oldPath on a rename), addedLines, and with Include Patch the zero-context patch as patch and patchTruncated |
    | Validate Anchors | Items, Added Lines | valid[], and moved[] as { item, reason } |
    | Fingerprint | Project Path, Ref, Items | items[], each with a fingerprint |
    | Dedupe | New Items, Previous Items | new[], repeated[] as { item, previous }, resolved[] |

  • Diff Context starts at the merge base of Base Ref and Head Ref, as a pull request does.
  • addedLines maps each file to the new-file line numbers of its added lines, which are the lines
    GitHub accepts a review comment on (the RIGHT side).

  • Validate Anchors discards nothing. An item on an added line goes to valid; any other goes to
  • moved with the reason: file not in the diff or deleted, line not added, path or line missing.

  • Fingerprint is a sha256 of the path, the type and the anchor line with Context Lines
  • (default 2) either side, read at Ref, trimmed and with whitespace collapsed. Lines inserted above
    the snippet leave it unchanged; editing the snippet changes it. An item that cannot be
    fingerprinted keeps its place with fingerprint: null and an error.

  • Dedupe matches on fingerprint. repeated pairs each new item with its previous one, so a
  • posted comment’s id travels with it, and resolved lists previous items still open (status
    open by default) whose fingerprint no longer appears.

    The field names (path, line, type, fingerprint, status and the open value) are parameters, so the
    Kit is not tied to one bot’s JSON. Refs are checked before git runs: letters, digits and
    . / _ @ ^ ~ -, never starting with -. git runs with an argument list and no shell, and nothing in
    the clone is modified. In Docker the clone has to be inside the container.

    The Claude Code Review Team template wires the
    three new nodes together.

    Claude Code Video Frames

    Claude cannot watch video: the API takes images, PDFs and text, nothing else. This node turns a
    video binary into images Claude can read, each with the moment it shows drawn in its top-left
    corner. It runs ffmpeg and no model, so it costs no tokens itself. **ffmpeg must be installed on the
    n8n server**: see Installing ffmpeg.

    Read/Write Files (video) → Claude Code Video Frames → AI Agent + Claude Code Chat Model
                                                        → Claude Code / Claude Code Agent
    
  • Before the AI Agent, turn on Automatically Passthrough Binary Images in the Agent. n8n’s
  • AI Agent forwards only images, PDFs and text to its model; a video on its input is dropped
    without a word, which is why this has to be a node of its own. The Agent passes the pixels
    without their file names, so put {{ $json.promptHint }} in the prompt: it says how many images
    there are, how mosaic tiles are ordered and which stretch they cover.

  • Before the Claude Code node or the Agent, leave Attach All Binaries on. The images arrive
  • inline, like any other image.

    | Mode | What you get | For |
    |—|—|—|
    | Auto (default) | Frames when each covers 5 s or less, mosaics beyond | Anything |
    | Frames | One image per moment, 1280 px on the long edge | Small text on screen, short clips |
    | Mosaic | 3×3 moments per image (2×2 or 4×4 in Options), 1920 px | Long recordings: 15 mosaics cover 135 moments |

    Max Images (default 15) is how many images come out, whatever the length. 15, plus a subtitles
    file, fits the 16 attachments the Claude Code node and the Agent accept by default; the Agent’s
    limit cannot be raised. Above 20 images the API shrinks every image in the request. Fifteen 1080p
    mosaics are about 40k input tokens. Moments are spread evenly, never closer than **Minimum
    Interval, and Start Time / End Time** look closely at one stretch.

    Each image is a binary property, frame000, frame001 and so on, in time order. Its file name
    carries the time too (frame00300-04-10.jpg). The JSON lists every image’s timestamps, the
    sampling mode, the extraction strategy, the coverage and the ffmpeg that ran. A text subtitle
    track inside the file comes out as subtitles (.srt). The AI Agent forwards text files on its
    own. The video itself is dropped from the item unless Keep Input Binary is on, so a later
    Attach All does not pick it up again.

    Every time it reports is read from the frame it extracted, not computed from the moment it
    asked for. ffmpeg’s usual sampling filter picks a different frame than the one requested and
    relabels it, which put the obvious approach 2 to 14 seconds off in testing. How the frames are
    pulled depends on the file’s keyframes: keyframes only when they are dense enough (under a second
    for any length), one seek per moment, or one full decode, about 4 minutes per hour of 1080p on 2
    CPUs. Timeout (default 300 s) bounds it.

    What to know

  • No speech: audio is not transcribed. A recording whose content is what is said needs a
  • transcription step of its own.

  • What decodes depends on the ffmpeg you install. The static 7.1 build below reads H.264, HEVC,
  • VP9, MPEG-4 and AV1; a codec the build lacks is a clear failure naming it.

  • A mosaic tile is 640 px wide. Large content reads well; a form field or a log line may not. Use
  • Frames, a smaller grid, or a narrower Start/End Time for those.

    Installing ffmpeg

    The video features run the ffmpeg of the machine n8n runs on. The package cannot bring its own: n8n’s
    community-node installer removes optionalDependencies and runs no install scripts, which are the
    two ways an npm package can ship a platform binary.

    n8n’s Docker image has no package manager (apk and apt are absent), so copy a static build in:

    FROM n8nio/n8n:latest
    COPY --from=mwader/static-ffmpeg:7.1 /ffmpeg /usr/local/bin/
    

    Or mount one at run time: -v /path/to/ffmpeg:/usr/local/bin/ffmpeg:ro, which is what the e2e rig
    does (measured on the official image, arm64: every filter the node uses, the time labels drawn with
    the image’s own fonts, and AV1).

    Elsewhere: apt-get install ffmpeg (Debian, Ubuntu), apk add ffmpeg (Alpine), brew install
    ffmpeg
    (macOS), winget install ffmpeg (Windows).

    The node looks at FFmpeg Path (Video Frames only), then the FFMPEG_PATH environment variable,
    then ffmpeg on the PATH, and reports which one ran in ffmpeg.source. If it finds none, the item
    fails with these instructions.

  • Videos stored in n8n’s binary store (filesystem or S3 mode) are streamed to a temp file, never
  • held in memory. Max Video Size defaults to 2 GB.

    Usage & Plan Limits

    The package ships a second node, Claude Code Usage, for the question the query node cannot
    answer: how much of the plan is left, and when it comes back. It is the data behind the CLI’s
    /usage, as workflow items.

    It costs nothing by default: the node opens a session, asks two control requests, and closes. No
    prompt is sent, no tool runs, no turn is billed — measured on the Claude Agent SDK 0.3.202 at 1–3s
    per read, $0.00, no assistant message. One fallback, off by default, does pay for a single turn; it
    is the last of the notes below and it exists because some credentials cannot read the usage endpoint
    at all.

    Search the nodes panel for usage, limits, cota or consumo to find it.

    Schedule Trigger → Claude Code Usage → IF maxUtilization > 85 → Slack alert
                                                                 → else → Claude Code
    

    Parameters

    | Parameter | Default | What it does |
    |—|—|—|
    | Project Path | current dir | Directory the read runs in. Auth is account-wide, but settings, env and hooks resolve per directory — a path with slow SessionStart hooks makes the read slower. |
    | Timeout | 60 | Seconds for the whole read: CLI startup, hooks, both control requests, and the probe turn when one runs. |
    | Error If Limits Unavailable | false | Fail the item when no plan windows come back. Turn on when the workflow gates on capacity and running blind is worse than failing. |
    | Declare Profile Scope for Token Sessions | true | Retry the read declaring CLAUDECODEOAUTHSCOPES when auth comes from CLAUDECODEOAUTHTOKEN. Without it such a session reports no plan limits even on a Max or Team account — see below. |
    | Probe With a Minimal Prompt If Unavailable | false | Last resort when no windows come back: send one trivial Haiku turn so its response headers carry the utilisation. Costs about $0.001 per read — the only paid path in this node. |
    | Include Account Email | false | Adds account.email. Off by default — the organisation and plan already identify the account, and n8n saves node output with every execution. |
    | Include Raw Limits | false | Adds limitsRaw, the server’s own limits[] with kind, group, severity, scope and is_active. |
    | Path to Claude Code Executable | bundled | Same as on the query node. |
    | Debug Mode | false | Logs the request timings, the window count, undeclared bucket names, whether the read was reused from the batch, and whether the scope retry or the probe ran. |

    One item in, one item out. With several input items the node reads **once per distinct Project
    Path** and gives every item served by that read the same fetchedAt — plan capacity is
    account-wide, so a 3-item batch has no reason to open three sessions.

    Output

    {{ $json.maxUtilization }}          // 72 — the fullest window, 0-100
    {{ $json.maxUtilizationKey }}       // 'five_hour'
    {{ $json.nextResetInSeconds }}      // 3477 — feed straight into a Wait node
    {{ $json.nextResetAt }}             // '2026-08-19T06:10:00.394384+00:00'
    {{ $json.rateLimitsAvailable }}     // true — there are numbers to read
    {{ $json.windows[0].utilization }}  // windows are sorted fullest-first
    {{ $json.account.organization }}    // 'Acme'
    

    | Field | Meaning |
    |—|—|
    | fetchedAt | When the read happened. Every countdown in the item derives from it |
    | account | organization, subscriptionType, apiProvider, tokenSource, apiKeySource, plus email when enabled |
    | authenticated | false when the CLI has no login at all. An unauthenticated CLI does not fail — it answers with tokenSource: "none" and no plan data, which otherwise looks exactly like a healthy API-key session |
    | subscriptionType | 'pro', 'max', 'team', 'enterprise', or null |
    | rateLimitsAvailable | True only when at least one window came back. Branch on this |
    | planLimitsApply | Whether plan limits apply to this login at all — false for API key, Bedrock, Vertex |
    | windows[] | One entry per window: key, utilization, resetsAt, resetsInSeconds, limitDollars, usedDollars, remainingDollars. Sorted by utilization descending |
    | maxUtilization, maxUtilizationKey | The binding constraint, so an IF node needs no array walking |
    | nextResetAt, nextResetInSeconds | The soonest reset among windows actually consuming quota |
    | extraUsage | Extra-usage credits: isEnabled, monthlyLimit, usedCredits, utilization, currency, plus disabledReason and spendLimitReached — the actionable half of isEnabled: false |
    | claudeCodeVersion | The CLI version, from the session’s init message. Only present on a probe read — a session with no turn never emits one |
    | session | The node’s own session, opened to ask the question. Zero unless the probe ran, in which case it is what the probe cost |
    | limitsRaw | Only when Include Raw Limits is on |
    | unsupported | true when the SDK no longer exposes the usage request at all |
    | diagnostics | initMs, usageMs, unknownBucketKeys, limitsPayloadMissing, plus scopeRetried when a token session needed a second read and probed / probeCostUsd when the paid fallback ran |

    windows[].key is whatever the server called the bucket — fivehour, sevenday,
    sevendayopus, and codenames like nimbus_quill or spend that no SDK type declares. The node
    walks the payload instead of reading a fixed field list, so a bucket added server-side shows up as
    data rather than vanishing. Undeclared keys are listed in diagnostics.unknownBucketKeys.

    What to know

    session is not account spend. It describes the session this node just opened, which does no work
    unless the probe runs — then it is the probe’s own cost. There is no API for month-to-date account
    spend; for per-run cost use the query node’s totalcostusd.

    An empty read is not an empty plan. planLimitsApply: true with rateLimitsAvailable: false
    means the account has limits and this read came back without them — observed lasting several
    minutes on a live Team account. diagnostics.limitsPayloadMissing marks exactly that case. Retry;
    never treat it as unlimited. This is why rateLimitsAvailable follows the numbers rather than the
    server’s own ratelimitsavailable flag, which stayed true throughout.

    API key, Bedrock and Vertex sessions have no plan limits. They are billed per token, so
    planLimitsApply is false, windows is empty, and the account fields still tell you which login
    n8n is using. Leave Error If Limits Unavailable off in that case.

    A CLAUDECODEOAUTH_TOKEN session hides its own limits. This is the usual headless and Docker
    setup, and out of the box it reports planLimitsApply: false with no windows — on a Max or Team
    account that has them. Measured against Claude Code CLI 2.1.219, the payload’s own flag is

    ratelimitsavailable = scopes.includes('user:inference') && scopes.includes('user:profile')
    

    and for a token session the CLI synthesises the scope list from CLAUDECODEOAUTH_SCOPES,
    defaulting to ['user:inference'] alone. So it never asks the usage endpoint — it censors itself
    before the request.

    Declare Profile Scope for Token Sessions (on by default) handles it: the node retries the read
    with CLAUDECODEOAUTH_SCOPES="user:inference user:profile" in the CLI’s environment, and
    diagnostics.scopeRetried marks the items that needed it. Declaring the scope grants nothing — the
    token still has exactly what the server issued it — so read the result to know where you stand:

    | Result | Meaning |
    |—|—|
    | windows populated | the credential can read the profile; you have the numbers |
    | planLimitsApply: true, rateLimitsAvailable: false, diagnostics.limitsPayloadMissing: true | the request was made and refused — the token cannot read the account profile |

    A claude setup-token token cannot read the usage endpoint. The CLI says so itself: tokens from
    setup-token or CLAUDECODEOAUTH_TOKEN “are limited to inference-only for security reasons”. The
    retry makes the CLI ask; the server declines. There is still a way to the two main windows, and two
    credentials that open the full payload.

    The probe: buy the numbers for a tenth of a cent. Every inference response carries
    anthropic-ratelimit-unified-5h-utilization / -reset and the 7d pair, and the CLI reports those as
    utilisation when the endpoint is closed to the credential. **Probe With a Minimal Prompt If
    Unavailable** (off by default) sends one trivial turn to Haiku so those headers exist, then reads them:

    // with the probe on, from a container whose only credential is CLAUDECODEOAUTH_TOKEN
    {{ $json.windows }}              // [ sevenday 20%, fivehour 11% ]
    {{ $json.nextResetInSeconds }}   // 14491
    {{ $json.session.totalCostUsd }} // 0.001136  ← what this read cost
    {{ $json.diagnostics.probed }}   // true
    

    It is the one part of this node that is not free, which is why it is opt-in and why the amount lands
    in the item. A batch pays once. Only fivehour and sevenday arrive this way — extra_usage,
    limits and the per-model buckets exist only in the endpoint payload.

    For those, two credentials work:

  • Interactive login inside the container — claude auth login there (device flow), with
  • ~/.claude on a volume so the record survives restarts. The stored record carries the real scopes.

  • Refresh-token login — set CLAUDECODEOAUTHREFRESHTOKEN to the refresh token from an
  • interactive login’s credentials, plus CLAUDECODEOAUTH_SCOPES="user:profile user:inference
    user:sessions:claudecode user:mcpservers"
    (the CLI refuses the refresh-token login without it).
    This route is documented by the CLI but not verified here.

    If none of the three fits, treat the node as an account-identity and liveness check on that instance
    and gate on rateLimitsAvailable instead of on utilisation.

    subscriptionType stays null on token sessions regardless: the CLI reads it from
    CLAUDECODESUBSCRIPTION_TYPE, which the node deliberately does not invent. Set it in the container
    if you want the field filled; the windows do not depend on it.

    Not being logged in looks like success. An unauthenticated CLI answers both control requests
    without an error — it just reports tokenSource: "none" and no plan data, which is otherwise
    identical to an API-key session. Branch on authenticated, and note that this is the most likely
    outcome when n8n runs in Docker: the container has its own HOME, so ~/.claude has to be mounted
    into it. With Error If Limits Unavailable on, the node names this case in the error and tells you
    to run claude login as the user n8n runs as.

    Configuration Examples

    Simple Code Analysis

    {
      "operation": "query",
      "prompt": "Analyze this codebase and suggest performance improvements",
      "projectPath": "/path/to/your/project",
      "model": "sonnet"
    }
    

    Advanced Database Operations

    {
      "operation": "query",
      "prompt": "Create an optimized query to find users who haven't logged in for 30 days",
      "projectPath": "/path/to/project",
      "model": "opus"
    }
    

    Customer Support Automation

    {
      "operation": "query",
      "prompt": "Customer reports: 'Login button not working on mobile devices'nnAnalyze this issue, find the root cause, and create a fix",
      "projectPath": "/path/to/web-app",
      "model": "opus",
      "allowedTools": ["Read", "Write", "Edit", "Bash", "Grep"],
      "additionalOptions": {
        "systemPrompt": "Focus on mobile compatibility issues. Check responsive CSS and JavaScript event handlers."
      }
    }
    

    Advanced Configuration with SDK Options

    {
      "operation": "query",
      "prompt": "Refactor this legacy code to use modern patterns",
      "projectPath": "/path/to/legacy-app",
      "model": "opus",
      "allowedTools": ["Read", "Write", "Edit", "MultiEdit", "Grep"],
      "disallowedTools": ["Bash"],  // Prevent command execution for safety
      "additionalOptions": {
        "permissionMode": "plan",  // Claude plans and executes no tools
        "fallbackModel": "sonnet",  // Auto-switch if Opus is overloaded
        "maxThinkingTokens": 50000,  // Allow deep reasoning for complex refactoring
        "systemPrompt": "Preserve all existing functionality while modernizing the code"
      }
    }
    

    With MCP configuration (.mcp.json):

    {
      "mcpServers": {
        "postgres": {
          "command": "npx",
          "args": ["-y", "@modelcontextprotocol/server-postgres", "${DATABASE_URL}"]
        }
      }
    }
    

    Your First Workflow

    First, check the CLI is installed and authenticated on the machine n8n runs on — the node uses that
    login, and there is no credential to configure in n8n:

    claude --version
    

    Then:

    1. New workflow → add a Manual Trigger.
    2. Add the Claude Code node.
    3. Set Prompt to something read-only for a first run — List the files in this directory and
    describe what the project does
    — and Project Path to a real directory. In Docker, one that
    is mounted into the container.
    4. Leave Model on Sonnet and Output Format on Structured. Execute.

    The item that comes back carries the answer in result, what the run cost in metrics, and what
    actually ran in diagnostics — including the model the CLI resolved to and the effort it applied,
    which are not always what you asked for.

    Two things worth doing before pointing it at anything that matters: set Max Budget (USD) in
    Additional Options, and turn on Debug Mode for the first few runs so the n8n log shows the
    message stream.

    From there: the templates are working workflows rather than snippets,
    examples/ covers project configuration, and MCP servers add
    database and API access.

    Notes worth knowing

    Bound what a workflow can do, in the project itself

    .claude/settings.json in the target directory applies here exactly as it does in a terminal:

    {
      "permissions": {
        "allow": ["Read()", "Write()", "Bash(npm test)"],
        "deny": ["Bash(rm -rf *)"]
      }
    }
    

    This travels with the repository rather than the workflow, which is usually where you want it — the
    constraint belongs to the codebase, not to whoever wired the node.

    Check capacity before a fan-out

    Put a Claude Code Usage node ahead of a batch and branch on maxUtilization. Ten agent runs
    that all fail at 100% cost more than one read that says “not now”, and nextResetInSeconds feeds a
    Wait node directly.

    Continue shares a conversation unless you say otherwise

    Continue with no Session ID resolves “the most recent conversation in this directory”, which
    every execution on the instance shares — so two concurrent runs collide. Pass the sessionId from a
    previous run’s diagnostics to resume a specific one.

    Support

  • Issues — for this package. The earlier
  • projects it derives from are separate; please do not file issues about this one there.

  • Repository
  • Development & Contributing

    Commit Conventions

    This project uses Conventional Commits:

  • feat: New features (minor version bump)
  • fix: Bug fixes (patch version bump)
  • docs: Documentation changes
  • chore: Maintenance tasks
  • test: Adding or updating tests

Use npm run commit for an interactive commit message builder.

Tests

npm test    # 1504 tests — node:test, no framework; 10 need an ffmpeg and skip without one

The gate for any change is npm run lint && npm run build && npm test.

Two things worth knowing before you touch the node:

tests/fixtures/ holds 48 golden fixtures — byte-for-byte recordings of what node versions 1
and 1.1 emit, across 8 message streams and 3 output formats. If they fail, behaviour moved, and
that is the point: those versions are what every existing workflow reads. Regenerate only
deliberately with UPDATE_GOLDEN=1 npm test, read the diff, and say in the commit message which
fixture moved and why. Fixes to old behaviour belong in a new node version, not in the old one.

nodes/ClaudeCode/output/legacy.ts is frozen. It preserves several quirks on purpose, each
marked FROZEN QUIRK in the tests with the finding it corresponds to. Improvements go in
v12.ts.

There is also a Docker suite that runs real n8n with the node installed and asserts 90 named
behaviours against real executions — including that an attached image, PDF or document actually
reaches the model, which no unit test can prove. It lives in scripts/e2e/, which is versioned;
only what it generates (workflows/, results.json, run-*.log, .pack/) is gitignored. It costs
real API spend, so it is not part of the gate. scripts/e2e/README.md has the details, including
which cases to re-run before believing a failure.

Release Process

Releases are published manually. There is no CI — validate locally first:

npm run lint
npm run build
npm test
npm publish --dry-run        # check the file list and version

Then bump the version and publish. npm requires a 2FA code:

npm version patch            # or minor / major
npm publish --otp=123456
git push && git push --tags

npm version writes package.json and creates the matching git tag.

Credits

This package builds on earlier work, and the lineage is worth stating plainly:

1. Adam Holt created the original n8n Claude Code node —
holt-web-ai/n8n-nodes-claudecode.
2. John Lindquist forked and maintained it —
johnlindquist/n8n-nodes-claudecode.
This package’s Claude Agent SDK migration started from his version.
3. This package is maintained by João Veiga —
JVVeiga/n8n-nodes-claudecode. It has since
diverged: its own architecture, versioning and release line.

The idea, the original node structure, and the n8n integration groundwork are theirs. Thanks to both.

License

MIT, throughout the lineage. The original copyright notice is kept intact in
LICENSE.md.

The video features run ffmpeg as a separate program, with an argument list. The package neither
contains nor installs ffmpeg; the build on the server is the operator’s choice, under its own
license (the mwader/static-ffmpeg build suggested above is GPLv3).