feat(core): Add Instance reporting module - #36796
Conversation
PR review overviewBased on ownership of the 24 changed files in this PR:
Required reviewsSome changed files have a
Request a review from the team — GitHub assigns reviewers according to the team's review settings. The |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
getInstanceOwner() filtered users by role but did not load the relation, so the returned User had no role and permission resolution on it threw "AuthPrincipal does not have a role defined". Load the relation, matching the sibling lookups in the same service. Role scopes come along, since Role.scopes is an eager relation.
78c29b1 to
ec4e4c0
Compare
…report-to-usage-monitoring # Conflicts: # packages/@n8n/backend-common/src/modules/modules.config.ts
Bundle ReportBundle size has no change ✅ |
|
TODO on this PR: undo any changes to scheduler code and no longer rely on it. EDIT: a vibe-coded attempt was committed here (not manual tested yet): 3c1f4a6 |
There was a problem hiding this comment.
0 issues found across 4 files (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Shadow auto-approve: would not auto-approve. Auto-approval blocked by 7 unresolved issues from previous reviews.
Re-trigger cubic
There was a problem hiding this comment.
0 issues found across 1 file (changes from recent commits).
Confidence score: 5/5
- Automated review surfaced no issues in the provided summaries.
- No files require special attention.
Shadow auto-approve: would not auto-approve. Auto-approval blocked by 7 unresolved issues from previous reviews.
Re-trigger cubic
|
Here is a report from letting claude run the local verification steps documented in f426844 Click to show manual test reportLocal verification: instance reporting →
|
Local review results by claudeCode Review — 2026-09-06 Principle-by-Principle Cumulative comes from license-metrics.repository.ts:64 — SUM(rootCount) over workflow_statistics rows production_success/production_error, i.e. root executions only. Sampling window mismatch, also unstated. Cumulative is sampled at report time T (say 07:42); daily covers the previous calendar UTC day 00:00–24:00. The cumulative delta is therefore a rolling 24h window offset ~7.7h from the calendar day the daily point names. The README's "fixed 24 hours apart" invariant is about consistency between consecutive cumulative samples — true, but it does not make the two points in one report comparable. The alternative (sample cumulative as of the reported day's boundary) is never mentioned. Multi-day downtime silently drops days. previousUtcDate(now) at instance-reporting.service.ts:152 only ever reports yesterday. A three-day outage reports one day; the receiver gets no gap signal. This is acknowledged only in a passing clause in an entity doc comment — "A future backfill reads the gap since the last delivered row", entity:18 — not in the README, not in the PR description, no follow-up ticket referenced. That's a data-completeness limitation buried where nobody reviewing the contract will see it. Near-midnight report times never recover from a failure. With a report time of, say, 23:58, the 5-minute retry lands after midnight, where scheduler:170 computes the new day's slot as still ahead and returns 'skipped'. The day is lost and the pending row lingers. Report times are random, so this hits a small slice of the fleet on any failing day. Undocumented. Broken link on merge. README.md:65 points at .agents/specs/central-instance-monitoring.md. That file is untracked locally (git status shows ?? .agents/specs/) and is not in the PR's 24 files. The README's "full design" pointer dead-ends. #2 Simplicity First — WARN Three names for one concept. Module instance-reporting, table and entity central_instance_monitoring_report, settings key features.centralInstanceMonitoring. Someone grepping the module name will not find its table. Redundant guard. scheduler:93 checks instanceType === 'main', already guaranteed by @BackendModule({ instanceTypes: ['main'] }) at module:18. isEnabled is also public with only internal callers. Two round trips where one would do. repository:59-67 — markDelivered and recordFailure each call increment then update, non-atomically, on a row they already own by id. Property stuttering. config.instanceReportingBaseUrl on InstanceReportingConfig (config.ts:18). The neighbouring convention is InsightsConfig.compactionIntervalMinutes — the class already carries the prefix. #3 Surgical Changes — WARN Worse, it's load-bearing and unmarked. getInsightsSummary → resolveAccessFilter → rolesGrantingScope reads user.role (insights.service.ts:84-99). Without the relation the owner looks role-less, the access filter narrows, and the daily data point silently under-counts — no exception, no log, just a wrong number shipped to a billing-adjacent receiver. There is no comment at instance-reporting.service.ts:130 or in ownership.service.ts recording the dependency. A future "why are we joining role here?" cleanup breaks this module invisibly. instance-reporting.service.ts:19-24 — orphaned class doc. The block "Measures and delivers one instance report…" sits immediately above const REQUEST_TIMEOUT_MS, so the class at :27 is documented by the timeout's one-liner and the real doc comment describes a constant. migration:9-11 — await separated from its expression by a three-line comment. Valid JS, reads as truncated code. Move the comment above the await. LOCAL_VERIFICATION.md — 223 lines of a personal verification log checked into packages/cli/src/. export N8N_DB=…, sqlite3 snippets, "Keep the receiver's request log visible". That's PR-description or .agents/ material, not module documentation; the README already covers the design. It will rot with the first refactor and nobody will notice. #4 Goal-Driven Execution — PASS Scheduler: catch-up on a passed slot, no re-fire after catch-up, leader takeover/stepdown/shutdown, retry bounded at exactly 3, settings-read failure re-arming, and a backward clock jump firing nothing (scheduler test:258-269). Verdict: PASS WITH WARNINGS Specific fixes |
Summary
Adds an opt-in
instance-reportingmodule that reports this instance's billable execution numbers to a central usage-monitoring receiver, once a day. Full design and behavior (scheduling, catch-up, retry, report-time selection) are documented in packages/cli/src/modules/instance-reporting/README.mdThis PR description covers only the externally-visible surface: the payload, the new env vars, and the schema change.
Payload
Once a day, a POST is sent to
<N8N_INSTANCE_REPORTING_BASE_URL>/api/v1/instance-reports:{ "instanceId": "…", "batchId": "…", "label": "", // optional "n8nVersion": "…", "dataPoints": [ { "kind": "cumulative", "name": "billableExecutions", "value": 42 }, { "kind": "daily", "name": "billableExecutions", "value": 5, "date": "2026-09-05" } ] }cumulativeis the instance's lifetime production root-execution count (same source as the licenseproductionRootExecutionsmetric).dailyis yesterday's (UTC) billable-execution count frominsights, since only a finished day has a final number.batchIdand values instead of re-measuring — see the README for why that matters for the cumulative series.New environment variables
N8N_INSTANCE_REPORTING_BASE_URL''N8N_INSTANCE_REPORTING_LABEL''label, when set.N8N_INSTANCE_REPORTING_AUTH_TOKEN''Authorization: Bearer …, when set.Opt-in via
N8N_ENABLED_MODULES=instance-reporting; requires theinsightsmodule.DB schema change
New table
central_instance_monitoring_report(migration1788445119184): one row per day,id(nanoid, doubles asbatchId),dataPoints(JSON, exactly as sent),deliveredAt(null while undelivered),attempts,lastError. No new columns on existing tables.Not yet on the durable scheduler
Scheduling currently uses a plain leader-gated in-process timer (same pattern as execution pruning/history compaction), not the durable scheduler — that framework has no first-class support yet for system-owned jobs. This is a known gap, and the plan is to move this job onto the durable scheduler as a system task in a follow-up.
How to test
See README.md for the module design. I additionally verified this locally end-to-end (real receiver, real workflow executions, forced report timing, retry/dedup, and auth failure handling) — see LOCAL_VERIFICATION.md.
Related Linear tickets, Github issues, and Community forum posts
https://linear.app/n8n/issue/API-171/
Review / Merge checklist
Backport to Beta,Backport to Stable, orBackport to v1(if the PR is an urgent fix that needs to be backported)