n8n workflows are typically managed through a click-driven UI, which makes version control, review, and automation difficult. Teams end up exporting JSON, losing history, and re-creating workflows by hand. The real pain is that AI agents lack the structured knowledge to work with n8n accurately — they guess at node schemas, produce broken workflows, and can't integrate with a live instance.
What This Does
n8n-as-code gives AI agents and developers a typed, scriptable interface to n8n. It bundles 537 node schemas, 7,700+ templates, and TypeScript workflow definitions into a local, searchable knowledge base. The core is a CLI (packages/cli) that syncs workflows between a local directory and a live n8n instance — think git pull/push for workflows. It also ships integrations for VS Code (packages/vscode-extension), Claude Code (.claude-plugin), and MCP (packages/mcp).
The transformer (packages/transformer) converts n8n JSON workflows to TypeScript and back, so you can write workflows as code. The skills package (packages/skills) provides the AI context — node schemas, docs, and workflow examples — to ground agent responses.
How It Is Wired
Execution starts at run in packages/cli/src/commands/convert.ts, which reaches 193 functions. The main flow: the CLI parses commands, loads config via config-service.ts, and calls the n8n API through n8n-api-client.ts. The sync-manager.ts orchestrates the git-like pull/push, using workflow-state-tracker.ts to map local files to remote workflow IDs.
The most critical modules are the hub — packages/cli/src/core/types.ts (17 modules depend on it) and packages/cli/src/core/index.ts (12 depend on it). Changes here have wide blast radius. The parse function is called from 34 places; getApiKey from 16. These are the load-bearing functions.
The system makes outbound network calls (n8n API), reads/writes files (workflow JSON), and runs external commands (npx). A typical sync: run -> getHealth (network) then main -> generate -> injectOrUpdate (filesystem). The wiring to the live instance is direct — few hops from CLI to API.
How To Use It
Setup: Clone and install dependencies. The repo uses pnpm (implied by pnpm/action-setup in CI). There is no lockfile committed, so builds are not reproducible — run pnpm install once and commit the lockfile.
Configuration: Copy .env-example to .env and set your n8n instance URL and API key. The config-service.ts reads local config; the CLI expects a workspace with workflow files.
Running it: Use the CLI directly — npx @n8n-as-code/cli sync pull or push. For AI agents, install the Claude plugin via /plugin marketplace add https://github.com/EtienneLescot/n8n-as-code. The VS Code extension activates from the command palette (n8n: Configure).
Real-World Use
A team maintains workflows in a Git repo. A developer writes a workflow in TypeScript, commits it, and CI runs n8n-as-code sync push to deploy to a staging instance. An AI agent, grounded by the bundled schemas, proposes a new workflow — the developer reviews the diff, merges, and the same sync pushes it to production. No manual JSON editing, no drift between environments.
High - Deep nesting in n8n-api-client.ts, workflow-state-tracker.ts (max indentation depth 8). Control flow is hard to follow.
High - Duplicated code — 180 repeated 6-line blocks across 61 files. Extract shared helpers.
Medium - Empty catch blocks in proxy-service.ts, webview-html.ts, create-test-workflows.mjs — errors silently swallowed.
Medium - Hub modules — types.ts and index.ts are chokepoints; keep them stable.
SDLC observations: no lockfile (non-reproducible builds), GitHub Actions pinned to mutable tags (supply-chain risk), and large JSON assets (29MB+) in the repo bloat every clone. CI exists with tests (87 test files), which is good.
The Bottom Line
This is a well-architected tool for teams that want GitOps for n8n and AI-assisted workflow development. The TypeScript transformer and bundled schemas are genuinely useful. The main risks are supply-chain (unpinned actions, no lockfile) and the complexity of the core sync logic. Teams already invested in n8n and AI agents will get the most value; casual users may find the setup overhead not worth it.
What the analyser found
Deployment readiness
6/7
✓Container image
✓CI pipeline
✗Lockfile committed
✓Test suite
✓README
✓License
✓No committed secrets
Composition
357 files
TypeScript162
Markdown71
JSON42
JavaScript9
YAML7
CSS3
ReactDocker
Module dependencies
The 10 most depended-upon modules of 202, from static import analysis. Red outline marks a module in an import cycle.
Ranked by severity × confidence × production reach. Reach is the honest discriminator across a collection that is mostly other people's code: the same finding matters more in something that ships.
high2
medium7
low2
Pin third-party GitHub Actions to a commit SHAhigh8 occurrences
A tag can be moved, so the action running with your token and secrets is whatever its owner last pushed; this is how tj-actions/changed-files leaked secrets from thousands of repos.
Fix: Replace each @vN with the 40-character commit SHA, keep # vN as a comment, and let Dependabot bump the SHAs.
Commit a lockfile beside the manifesthigh
docs/package.json
manifest with no lockfile in scope
An unlocked range means the artifact you tested and the artifact you ship can contain different transitive code, so a malicious patch release reaches production with no diff.
Fix: Run the package manager once and commit the generated lockfile.
Declare least-privilege permissions for GITHUB_TOKENmedium
.github/workflows/ci.yml
1 workflow(s) declare no permissions, 1 of them reference secrets
With no declaration the token inherits the repository default, so any injected step can push commits or mint releases from inside your own CI.
Fix: Add permissions: contents: read at the top of the workflow and widen per job only where needed.
Enable Dependabot or Renovatemedium
8 manifest(s), no update bot configured
Without a bot a published advisory sits unpatched until someone audits by hand, which across 1,322 repositories means never.
Fix: Commit .github/dependabot.yml covering the repo ecosystems plus github-actions.
Pin the container base image by digestmedium
packages/mcp/docker/Dockerfile
node:lts-alpine
An untagged or mutable base means today's build and last month's contain different libc and a different CVE set, with no record of which shipped.
Fix: Use image:tag@sha256:<digest> and enable Dependabot's docker ecosystem.
Gate pull requests on a dependency vulnerability scanmedium
.github/workflows
no dependency scan in CI
This is the one gate that would catch a known-vulnerable package before it reaches a build, and no repository in the sample had it.
Fix: Add dependency-review-action on pull_request, or osv-scanner on push and a schedule.
Move large binaries to Git LFS or out of the repositorymedium7 occurrences
7 blobs over 5MB: n8n-nodes-technical.json 29.3MB, n8n-nodes-index.json 26.8MB, n8n-knowledge-index.json 15.4MB
One repository here carries twenty blobs over 5MB including a 10.9MB spreadsheet, so every clone and every CI checkout pays for data nobody diffs.
Fix: Track those extensions with LFS, or move datasets to object storage and fetch them in a setup step.
Set persist-credentials: false on checkoutmedium
.github/workflows/ci.yml
checkout keeps the token, then dependencies are installed
The token stays in .git/config for every later step, so a malicious postinstall script reads a pushable credential without one ever being passed to it.
Fix: Add with: persist-credentials: false, and pass an explicit token only to the step that pushes.
Pin the resolution so a vulnerable version cannot installmedium31 occurrences
no lockfile, and the declared ranges reach vitest@1.1.0 CVE-2026-47429 (critical) +1 more; axios@1.13.2 CVE-2026-44494 (high) +28 more
The range includes versions with published advisories and nothing in the repository records which one was resolved, so two installs a month apart can differ in whether they are vulnerable and neither is reproducible from what is committed.
Fix: Commit the lockfile, which fixes the resolution and makes this answerable.
Set timeout-minutes on the workflow jobslow4 occurrences
.github/workflows/ci.yml
4 workflow(s) declare no job timeout
A wedged step runs to the six-hour platform default, which on a two-hourly schedule means three runs overlap behind it.
Fix: Add timeout-minutes with a realistic bound to each job.
Add the repository convention files this project lackslow3 occurrences
missing .editorconfig, .gitattributes, a formatter config
Without them one contributor's editor writes tabs into a Python file, a shell script commits with CRLF and fails in the container, and a notebook diff is unreviewable.
Fix: Add .editorconfig, .gitattributes with text=auto eol=lf, and a formatter config.
Checked deterministically against the repository tree and a bounded set of its files: committed credentials, unpinned actions and base images, missing lockfiles and update bots, workflows that discard failures, published advisories against the declared dependencies, runtime configuration, licensing and notebook reproducibility. No language model is involved in this section.