debug-workflow — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited debug-workflow (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Fix n8n workflows the way a senior engineer does — systematically, not by guessing.
Ask the user (or infer from error text) which class:
| Class | Symptom | Common root causes |
|---|---|---|
| Expression error | Cannot read property X of undefined, red node badge | Wrong expression syntax, missing = prefix, accessing undefined fields |
| Type mismatch | Expected string, got array, downstream weirdness | Item Lists vs single item confusion, Split In Batches output shape |
| Silent empty | Workflow runs green but no output/side effect | Filter condition wrong, pinned stale data, credential scope |
| Auth failure | 401, 403, Invalid API key | Wrong credential selected, expired token, OAuth refresh not configured |
| Rate limit | Intermittent 429, works manually, fails in batch | No backoff, batch too large, shared credential across workflows |
| Sub-workflow | Execute Workflow returns nothing or errors | Parameters not passed, return value not set, wrong workflow ID |
The single biggest footgun: ={{ $json.field }} vs {{ $json.field }}. The leading = makes the field an expression. Without it, the literal string {{ $json.field }} is sent.
Check:
=: "value": "={{ $json.id }}"Accessing undefined paths: $json.customer.email throws if customer is null. Use optional chaining: $json.customer?.email ?? 'unknown'.
Referencing earlier nodes: Use $('Node Name').item.json.field, NOT $node['Node Name'].json.field (deprecated). Quote exact node name including spaces.
Common expression patterns:
// Array access
{{ $json.items[0].name }}
{{ $json.items?.[0]?.name ?? 'empty' }}
// Previous node output
{{ $('Webhook').item.json.body.email }}
// All items from previous node (for loops)
{{ $('Split In Batches').all().map(i => i.json.id) }}
// Conditional
{{ $json.amount > 100 ? 'high' : 'low' }}
// Date formatting (n8n uses Luxon)
{{ $now.toISO() }}
{{ $now.minus({ days: 7 }).toFormat('yyyy-MM-dd') }}
// Environment vars (self-hosted)
{{ $env.MY_SECRET }}n8n passes data as an array of items, each with { json: {...}, binary: {...} }. Many bugs come from treating a single item as an array or vice versa.
Inspection routine:
{ json: { list: [...] } }), or MULTIPLE items ([ { json: {} }, { json: {} } ])?Item Lists → "Split Out Items" to fan outSplit In Batches outputs different shapes on main vs "done" outputs — the main output is a batch, the done output is empty. Wire accordingly.
Workflow shows green checkmarks but nothing happened downstream. Diagnostic order:
IF evaluating false silently skips the branch. Check the condition value at runtime.Self-hosted n8n specific: if a credential suddenly stops working after a container restart, check that the encryption key (N8N_ENCRYPTION_KEY env var) is persisted. Losing it = all credentials corrupt.
Symptom: works on 10 items, fails on 100. Classic.
Fix pattern:
Split In Batches with batchSize matching the vendor's per-second limitWait node between batches: waitBetweenBatches: ceil(60000 / requests_per_minute)retry.maxTries: 3 with retry.waitBetweenTries: 5000 on the HTTP Request nodeX-RateLimit-Remaining, Retry-After) and dynamically back off via a Code nodeFor cron-triggered workflows hitting the same vendor from multiple cron jobs, centralize the API call in ONE sub-workflow with a semaphore pattern (MySQL row as lock).
Execute Workflow returns null or undefined most often because:
{{ $json }} or explicit fields.MySQL insert returning nothing, parent gets nothing. Add a final Set node that constructs the return payload.Execution log shows per-node input and output. Debug pattern:
Most bugs are upstream shape issues masquerading as downstream errors. The error message lies about where the bug is.
=mysql-checkpointing skill)Information Extractor with schema (see chain-llm-pattern)Respond to Webhook (respond first, process async)Ask the user for:
Don't guess without these. n8n errors are specific — vague guesses waste time.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.