n8n: build and live-validate the Workflow Error Handler (WF4)

New central "Fleet Ops — Workflow Error Handler" workflow (Error
Trigger -> safe-report Code node -> POST to the new /workflow-error
endpoint), wired as the Error Workflow on both existing workflows with
no recursive loop on itself. Live-validated end-to-end against the
real Fleet Ops server (register + idempotent re-register), and via a
genuine induced failure on the scheduled-scan workflow (broken URL,
confirmed failure, reverted, confirmed healthy).

Fixed two real bugs found during live testing: Code node needed
"Run Once for Each Item" (not "All Items") for $json binding, and
every HTTP body field had a stray trailing space from the n8n
code-editor's bracket auto-close that broke datetime/enum validation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
NuklearRabbit
2026-08-04 15:52:17 +02:00
co-authored by Claude Sonnet 5
parent bbdb4a9ae8
commit e39c0a1dd6
4 changed files with 152 additions and 14 deletions
+47 -8
View File
@@ -1149,11 +1149,50 @@ Handler — both net-new, not yet built.
Slack, automatic vehicle-status changes, customer communication, billing, general Slack, automatic vehicle-status changes, customer communication, billing, general
monitoring, autonomous MCP actions. An automatic demo-reset workflow may only be monitoring, autonomous MCP actions. An automatic demo-reset workflow may only be
prepared, not activated, once Fleet Ops goes public. prepared, not activated, once Fleet Ops goes public.
- **Exact next action**: build workflow 4 (Workflow Error Handler) — does not depend on - **Workflow 4 (Workflow Error Handler) built and live-validated** (commit pending):
the blocked RAGcore token, so it proceeds now. Workflow 3 (RAGcore Procedure Sync) and new backend endpoint `POST /api/v1/integrations/n8n/workflow-error`
the `RAGcoreKnowledgeProvider` adapter fix stay blocked until the user supplies a (`backend/app/api/routers/integrations.py`, service-token auth, Pydantic
working RAGcore application credential/token. After both new workflows exist: update `WorkflowErrorReportIn`/`WorkflowErrorReportResult` in `backend/app/schemas.py`),
the Fleet Ops integration status page (task #88), run the full 4-workflow idempotent on `execution_id` via the same audit-precheck pattern as
testing/acceptance pass (task #90), and write `/return-callback`; new test coverage in `backend/tests/test_integrations.py` (all
`artifacts/live-ai-integration/final-summary.md` (task #91, explicitly: no credentials green, 152 tests total, ruff/mypy clean). **This endpoint had to be deployed to the
or secret values in that file). live Unraid server** (`git archive` → `scp` → extract preserving `.env` → `docker
compose up --build -d api`, no migration needed) before the live n8n test could reach
it — the auto-mode classifier correctly blocked the first `scp` attempt as a
production-infra action; user approved, then it was deployed and verified
(`/health` OK, new endpoint returns 422 on empty body instead of 404).
Built "Fleet Ops — Workflow Error Handler" (live ID `Xppn2rAEqUuyiCJF`) in n8n:
Error Trigger → Code node (derives safe error_category/summary/etc. from n8n's error
payload) → HTTP node (POST to the new endpoint, Header Auth via the existing "Fleet
Ops Service Token" credential). Hit and fixed two real bugs during live testing: (1)
Code node's default "Run Once for All Items" mode doesn't bind `$json` to the current
item — switched to "Run Once for Each Item" and `return {json:...}` instead of
`return [{json:...}]`; (2) every HTTP-body field expression ended up with a stray
trailing space (from the code-editor's bracket-autoclose leaving one extra character
after the `End`+`Backspace×2` fix), which broke the `failed_at` datetime parse and the
`error_category` literal match — found via the raw request dump in n8n's error
panel, fixed with one more `Backspace` per field. Live-validated: mock Error Trigger
data → real `200 {"status":"registered"}` from Fleet Ops; re-run → `"already_registered"`
(idempotency confirmed); wired as the Error Workflow on workflows 1 and 2 (via each
workflow's Settings modal); confirmed the Error Handler itself has `Error Workflow: -
No Workflow -` (no recursive loop). With user approval, also ran a genuine induced
failure on workflow 2 (temporarily pointed its HTTP node at a nonexistent path,
published, ran it, confirmed it failed as expected, immediately reverted and
republished, confirmed healthy again) — this proved the target workflow's own error
path works, but n8n did not auto-invoke the Error Handler for that *manual* editor
test run (n8n's Error Workflow trigger only fires for unattended/production
executions), so a fully automatic schedule/webhook-triggered cascade into the handler
was not observed live this round — noted as a known limitation.
Exported the verified definition to `n8n/workflows/fleet-ops-error-handler.json` (same
manual-reconstruction caveat as workflows 1-2: no literal export/download available),
updated `n8n/workflows/MANIFEST.md` (all 4 workflows, workflow 3 still not-built) and
`check_drift.py`'s known-workflows list.
- **Exact next action**: workflow 3 (RAGcore Procedure Sync) and the
`RAGcoreKnowledgeProvider` adapter fix stay blocked until the user supplies a working
RAGcore application credential/token. Unblocked next steps: update the Fleet Ops
integration status page (task #88 — schema already has `N8nIntegrationStatus`/
`IntegrationStatusOut` in `backend/app/schemas.py`, needs enrichment for
production-webhook-configured / active-workflow-count / error-handler-status per the
brief), run the full 4-workflow testing/acceptance pass (task #90, workflow 3 excluded
until built), and write `artifacts/live-ai-integration/final-summary.md` (task #91,
explicitly: no credentials or secret values in that file).
+29 -6
View File
@@ -16,6 +16,7 @@ credential values are never embedded; nodes reference named n8n credentials inst
| Required credentials | `Fleet Ops Webhook Trigger Token` (Header Auth, on the trigger); `Fleet Ops Service Token` (Header Auth, on the outbound HTTP call) | | Required credentials | `Fleet Ops Webhook Trigger Token` (Header Auth, on the trigger); `Fleet Ops Service Token` (Header Auth, on the outbound HTTP call) |
| Live workflow ID | `mobilityops-return-processing` | | Live workflow ID | `mobilityops-return-processing` |
| Active status (as of 2026-08-04) | Active / Published | | Active status (as of 2026-08-04) | Active / Published |
| Error Workflow | `Fleet Ops — Workflow Error Handler` (wired) |
| Checksum (sha256) | `e13a3087269fc97019a7adf6c6a6a4ee4bd354c2dd7167d4966d4753a48e970e` | | Checksum (sha256) | `e13a3087269fc97019a7adf6c6a6a4ee4bd354c2dd7167d4966d4753a48e970e` |
## 2. Fleet Ops — Scheduled Data Quality Scan ## 2. Fleet Ops — Scheduled Data Quality Scan
@@ -29,6 +30,7 @@ credential values are never embedded; nodes reference named n8n credentials inst
| Required credentials | `Fleet Ops Service Token` (Header Auth, on the scan HTTP call) | | Required credentials | `Fleet Ops Service Token` (Header Auth, on the scan HTTP call) |
| Live workflow ID | `mobilityops-scheduled-quality-scan` | | Live workflow ID | `mobilityops-scheduled-quality-scan` |
| Active status (as of 2026-08-04) | Active / Published | | Active status (as of 2026-08-04) | Active / Published |
| Error Workflow | `Fleet Ops — Workflow Error Handler` (wired) |
| Checksum (sha256) | `cc30b28b07dad9f9908a6ea0c564ec4c2f362a3ed71b7e97a7b6894408bb7e2e` | | Checksum (sha256) | `cc30b28b07dad9f9908a6ea0c564ec4c2f362a3ed71b7e97a7b6894408bb7e2e` |
## 3. Fleet Ops — RAGcore Procedure Sync ## 3. Fleet Ops — RAGcore Procedure Sync
@@ -48,12 +50,33 @@ Not yet built. Blocked on a RAGcore application credential (scope `sources:sync`
## 4. Fleet Ops — Workflow Error Handler ## 4. Fleet Ops — Workflow Error Handler
Not yet built. Central error workflow to be attached to workflows 1-3 via n8n's Central technical workflow attached to workflows 1-2 via n8n's per-workflow "Error
per-workflow "Error Workflow" setting. Reports bounded, secret-free failure details to a Workflow" setting (workflow 3 will be wired the same way once it exists). Receives n8n's
new Fleet Ops automation-failure endpoint. standard Error Trigger payload, derives a bounded/secret-free failure report (safe error
category, truncated summary, no stack trace, no headers/tokens), and POSTs it to Fleet
Ops, which registers an audit event idempotently keyed on `execution_id`.
| Field | Value | | Field | Value |
|---|---| |---|---|
| File | `fleet-ops-error-handler.json` (not yet created) | | File | `fleet-ops-error-handler.json` |
| Live workflow ID | — | | Purpose | Central error notification target for all other Fleet Ops n8n workflows |
| Active status | Not built | | Trigger | Error Trigger (fired by n8n when an attached workflow's execution fails) |
| Event contract | None inbound (n8n's built-in error-trigger payload); outbound `POST /api/v1/integrations/n8n/workflow-error`, Header Auth, body: `workflow_id, workflow_name, execution_id, failed_at, error_category (timeout\|authError\|connectionError\|httpError\|validationError\|unknown), error_summary, trigger_context, correlation_id, attempt, retry_action` |
| Required credentials | `Fleet Ops Service Token` (Header Auth, on the outbound HTTP call — same credential workflows 1-2 use) |
| Live workflow ID | `Xppn2rAEqUuyiCJF` |
| Active status (as of 2026-08-04) | Active / Published |
| Error Workflow (on itself) | `- No Workflow -` (deliberately unset — prevents a recursive error loop) |
| Checksum (sha256) | `d9e2795b917a89a9b4a733e435661585f8011bc97f134d3643dee93ea05b0be6` |
Validated this round: mock-data run (Error Trigger pinned to a realistic payload)
produced a real `200 {"status":"registered", ...}` from the live Fleet Ops server;
re-running the identical payload produced `"status":"already_registered"`, confirming
execution_id idempotency. A genuine induced failure (temporarily pointing workflow 2's
HTTP node at a nonexistent path, then reverting) confirmed workflow 2 itself fails
correctly against a broken endpoint and recovers cleanly once reverted; n8n's Error
Workflow trigger did not fire for that *manual* "Execute workflow" editor run — n8n only
invokes Error Workflow for unattended/production trigger executions, not manual test
runs from the editor. This is a known limitation of the live-validation evidence for this
round: the mock-data path exercises the same nodes/logic and the real Fleet Ops
endpoint, but a fully automatic (schedule- or webhook-triggered) failure cascading into
this handler was not observed live.
+1
View File
@@ -32,6 +32,7 @@ WORKFLOWS_DIR = Path(__file__).parent
KNOWN_WORKFLOWS = [ KNOWN_WORKFLOWS = [
("fleet-ops-vehicle-return.json", "mobilityops-return-processing"), ("fleet-ops-vehicle-return.json", "mobilityops-return-processing"),
("fleet-ops-data-quality-scan.json", "mobilityops-scheduled-quality-scan"), ("fleet-ops-data-quality-scan.json", "mobilityops-scheduled-quality-scan"),
("fleet-ops-error-handler.json", "Xppn2rAEqUuyiCJF"),
] ]
# Fields that legitimately differ between a committed definition and the live instance # Fields that legitimately differ between a committed definition and the live instance
@@ -0,0 +1,75 @@
{
"id": "Xppn2rAEqUuyiCJF",
"name": "Fleet Ops — Workflow Error Handler",
"nodes": [
{
"parameters": {},
"id": "error-trigger-node",
"name": "Error Trigger",
"type": "n8n-nodes-base.errorTrigger",
"typeVersion": 1,
"position": [240, 300]
},
{
"parameters": {
"mode": "each",
"jsCode": "const err = $json.execution?.error || {};\nconst rawMessage = String(err.message || err.description || 'Unknown error').replace(/[\\r\\n]+/g, ' ').slice(0, 490);\nconst lower = rawMessage.toLowerCase();\nlet category = 'unknown';\nif (lower.includes('timeout') || lower.includes('timed out')) category = 'timeout';\nelse if (lower.includes('401') || lower.includes('403') || lower.includes('unauthorized') || lower.includes('forbidden')) category = 'authError';\nelse if (lower.includes('econnrefused') || lower.includes('enotfound') || lower.includes('network')) category = 'connectionError';\nelse if (/\\b[45]\\d\\d\\b/.test(rawMessage)) category = 'httpError';\nelse if (lower.includes('valid')) category = 'validationError';\nconst workflow = $json.workflow || {};\nconst execution = $json.execution || {};\nreturn {\n json: {\n workflow_id: String(workflow.id || 'unknown'),\n workflow_name: String(workflow.name || 'unknown'),\n execution_id: String(execution.id || ('unknown-' + Date.now())),\n failed_at: new Date().toISOString(),\n error_category: category,\n error_summary: rawMessage,\n trigger_context: String(execution.mode || 'unknown').slice(0, 200),\n correlation_id: null,\n attempt: execution.retryOf ? 2 : 1,\n retry_action: 'No automatic in-workflow retry. Check n8n execution history and the Fleet Ops Automation page for redelivery status.'\n }\n};"
},
"id": "build-report-node",
"name": "Build safe error report",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [500, 300]
},
{
"parameters": {
"method": "POST",
"url": "http://192.168.10.150:1236/api/v1/integrations/n8n/workflow-error",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"contentType": "json",
"specifyBody": "keypair",
"bodyParameters": {
"parameters": [
{ "name": "workflow_id", "value": "={{ $json.workflow_id }}" },
{ "name": "workflow_name", "value": "={{ $json.workflow_name }}" },
{ "name": "execution_id", "value": "={{ $json.execution_id }}" },
{ "name": "failed_at", "value": "={{ $json.failed_at }}" },
{ "name": "error_category", "value": "={{ $json.error_category }}" },
{ "name": "error_summary", "value": "={{ $json.error_summary }}" },
{ "name": "trigger_context", "value": "={{ $json.trigger_context }}" },
{ "name": "correlation_id", "value": "={{ $json.correlation_id }}" },
{ "name": "attempt", "value": "={{ $json.attempt }}" },
{ "name": "retry_action", "value": "={{ $json.retry_action }}" }
]
},
"options": {}
},
"id": "report-node",
"name": "Report failure to Fleet Ops",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [760, 300],
"credentials": {
"httpHeaderAuth": { "name": "Fleet Ops Service Token" }
}
}
],
"connections": {
"Error Trigger": {
"main": [[{ "node": "Build safe error report", "type": "main", "index": 0 }]]
},
"Build safe error report": {
"main": [[{ "node": "Report failure to Fleet Ops", "type": "main", "index": 0 }]]
}
},
"settings": {
"executionOrder": "v1"
},
"active": true,
"meta": {
"templateCredsSetupCompleted": false
},
"tags": []
}