feat(core): Add Instance reporting module #105162
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: 'CI: Pull Requests (Build, Test, Lint)' | |
| on: | |
| pull_request: | |
| merge_group: | |
| concurrency: | |
| group: ci-${{ github.event.pull_request.number || github.event.merge_group.head_sha || github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| install-and-build: | |
| name: Install & Build | |
| runs-on: ${{ vars.RUNNER_PROVIDER == 'github' && 'ubuntu-latest' || 'blacksmith-4vcpu-ubuntu-2204' }} | |
| env: | |
| NODE_OPTIONS: '--max-old-space-size=7168' | |
| CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} | |
| QA_METRICS_WEBHOOK_URL: ${{ secrets.QA_METRICS_WEBHOOK_URL }} | |
| QA_METRICS_WEBHOOK_USER: ${{ secrets.QA_METRICS_WEBHOOK_USER }} | |
| QA_METRICS_WEBHOOK_PASSWORD: ${{ secrets.QA_METRICS_WEBHOOK_PASSWORD }} | |
| outputs: | |
| ci: ${{ fromJSON(steps.ci-filter.outputs.results).ci == true }} | |
| runtime: ${{ fromJSON(steps.ci-filter.outputs.results).runtime == true }} | |
| unit: ${{ fromJSON(steps.ci-filter.outputs.results).unit == true }} | |
| e2e: ${{ fromJSON(steps.ci-filter.outputs.results).e2e == true }} | |
| dev_server_smoke: ${{ fromJSON(steps.ci-filter.outputs.results)['dev-server-smoke'] == true }} | |
| workflows: ${{ fromJSON(steps.ci-filter.outputs.results).workflows == true }} | |
| workflow_scripts: ${{ fromJSON(steps.ci-filter.outputs.results)['workflow-scripts'] == true }} | |
| db: ${{ fromJSON(steps.ci-filter.outputs.results).db == true }} | |
| e2e_performance: ${{ fromJSON(steps.ci-filter.outputs.results)['e2e-performance'] == true }} | |
| instance_ai_workflow_eval: ${{ fromJSON(steps.ci-filter.outputs.results)['instance-ai-workflow-eval'] == true }} | |
| commit_sha: ${{ steps.commit-sha.outputs.sha }} | |
| merge_base: ${{ steps.ci-filter.outputs.merge-base }} | |
| matrix: ${{ steps.generate-matrix.outputs.matrix }} | |
| db_test_matrix: ${{ steps.generate-db-test-matrix.outputs.db_test_matrix }} | |
| skip_tests: ${{ steps.generate-matrix.outputs.skip-tests }} | |
| affected_packages: ${{ steps.affected-packages.outputs.list }} | |
| changed_files: ${{ steps.ci-filter.outputs.changed-files }} | |
| steps: | |
| - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1 | |
| with: | |
| # Use merge_group SHA when in merge queue, otherwise PR merge ref | |
| ref: ${{ github.event_name == 'merge_group' && github.event.merge_group.head_sha || format('refs/pull/{0}/merge', github.event.pull_request.number) }} | |
| - name: Capture commit SHA for cache consistency | |
| id: commit-sha | |
| run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" | |
| - name: Check for relevant changes | |
| uses: ./.github/actions/ci-filter | |
| id: ci-filter | |
| with: | |
| mode: filter | |
| filters: | | |
| ci: | |
| ** | |
| !packages/@n8n/task-runner-python/** | |
| !.github/** | |
| !**/*.md | |
| !**/LICENSE | |
| !**/CHANGELOG.md | |
| runtime: | |
| ** | |
| !packages/@n8n/task-runner-python/** | |
| !.github/** | |
| !**/*.md | |
| !**/LICENSE | |
| !**/CHANGELOG.md | |
| !**/*.test.ts | |
| !**/*.spec.ts | |
| !packages/testing/playwright/** | |
| !packages/frontend/@n8n/storybook/** | |
| !.devcontainer/** | |
| !scripts/agent-setup.mjs | |
| !scripts/backend-module/** | |
| !scripts/cloud-session.mjs | |
| !scripts/preview.mjs | |
| !scripts/preview-serve.mjs | |
| !scripts/licenses/** | |
| !scripts/mutation-health/** | |
| !scripts/sync-agent-skill-links.mjs | |
| unit: | |
| ** | |
| !packages/@n8n/task-runner-python/** | |
| !packages/testing/playwright/** | |
| !.github/** | |
| !**/*.md | |
| !**/LICENSE | |
| !**/CHANGELOG.md | |
| !.devcontainer/** | |
| !scripts/agent-setup.mjs | |
| !scripts/backend-module/** | |
| !scripts/cloud-session.mjs | |
| !scripts/preview.mjs | |
| !scripts/preview-serve.mjs | |
| !scripts/licenses/** | |
| !scripts/mutation-health/** | |
| !scripts/sync-agent-skill-links.mjs | |
| e2e: | |
| .github/workflows/test-e2e-*.yml | |
| .github/workflows/prepare-docker-reusable.yml | |
| .github/actions/build-n8n-docker/** | |
| .github/actions/load-n8n-docker/** | |
| packages/testing/playwright/** | |
| packages/testing/containers/** | |
| dev-server-smoke: | |
| packages/frontend/editor-ui/vite.config.mts | |
| # If this job becomes too slow, use a smaller glob. Do not delete the assertion. | |
| packages/frontend/editor-ui/src/** | |
| pnpm-workspace.yaml | |
| packages/@n8n/*/package.json | |
| packages/frontend/@n8n/frontend-vite-config/** | |
| packages/testing/playwright/tests/dev-server-smoke/** | |
| packages/testing/playwright/playwright.config.ts | |
| packages/testing/playwright/playwright-projects.ts | |
| packages/testing/playwright/package.json | |
| .github/workflows/test-dev-server-smoke-reusable.yml | |
| workflows: .github/** | |
| workflow-scripts: | |
| .github/scripts/** | |
| .devcontainer/codespaces/agent-worker*.mjs | |
| OWNERS | |
| scripts/*.test.mjs | |
| scripts/codespace-env.mjs | |
| scripts/preview.mjs | |
| scripts/preview* | |
| scripts/serve-ready.mjs | |
| scripts/licenses/** | |
| scripts/mutation-health/** | |
| cubic.yaml | |
| .agents/review-rules/** | |
| e2e-performance: | |
| packages/testing/playwright/tests/performance/** | |
| packages/testing/playwright/utils/performance-helper.ts | |
| packages/testing/containers/** | |
| .github/workflows/test-e2e-performance-reusable.yml | |
| instance-ai-workflow-eval: | |
| packages/@n8n/instance-ai/src/** | |
| packages/@n8n/instance-ai/evaluations/** | |
| packages/cli/src/modules/instance-ai/** | |
| packages/core/src/execution-engine/eval-mock-helpers.ts | |
| .github/workflows/test-evals-discovery.yml | |
| db: | |
| packages/cli/src/databases/** | |
| packages/cli/src/modules/*/database/** | |
| packages/cli/src/modules/**/*.entity.ts | |
| packages/cli/src/modules/**/*.repository.ts | |
| packages/cli/test/integration/** | |
| packages/cli/test/migration/** | |
| packages/cli/test/shared/db/** | |
| packages/cli/test/setup-testcontainers.js | |
| packages/cli/test/teardown-testcontainers.js | |
| packages/@n8n/db/** | |
| packages/@n8n/backend-test-utils/** | |
| packages/@n8n/scheduler/src/** | |
| packages/cli/**/__tests__/** | |
| packages/testing/containers/services/postgres.ts | |
| packages/testing/containers/postgres-versions.json | |
| packages/testing/containers/test-containers.ts | |
| .github/workflows/test-db-reusable.yml | |
| .github/scripts/db-test-matrix.mjs | |
| docs/generated/** | |
| .tbls.sqlite.yml | |
| .tbls.postgres.yml | |
| - name: Setup and Build | |
| if: fromJSON(steps.ci-filter.outputs.results).ci || fromJSON(steps.ci-filter.outputs.results).e2e | |
| uses: ./.github/actions/setup-nodejs | |
| with: | |
| build-command: ${{ fromJSON(steps.ci-filter.outputs.results).ci && 'pnpm build' || 'pnpm turbo run build --filter=@n8n/playwright-janitor' }} | |
| - name: Report build critical path | |
| if: fromJSON(steps.ci-filter.outputs.results).ci | |
| run: node scripts/turbo-critical-path.mjs >> "$GITHUB_STEP_SUMMARY" | |
| - name: Run format check | |
| if: fromJSON(steps.ci-filter.outputs.results).ci | |
| run: pnpm format:check | |
| - name: Generate shard matrix | |
| id: generate-matrix | |
| if: fromJSON(steps.ci-filter.outputs.results).ci || fromJSON(steps.ci-filter.outputs.results).e2e | |
| env: | |
| CHANGED_FILES: ${{ steps.ci-filter.outputs.changed-files }} | |
| MERGE_BASE: ${{ steps.ci-filter.outputs.merge-base }} | |
| run: | | |
| FILES_CSV=$(echo "$CHANGED_FILES" | tr '\n' ',' | sed 's/,$//') | |
| MATRIX=$(node packages/testing/playwright/scripts/distribute-tests.mjs --matrix 16 --orchestrate --impact "--files=$FILES_CSV" "--base=$MERGE_BASE") | |
| echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT" | |
| echo "skip-tests=$(node -e "process.stdout.write(JSON.parse(process.argv[1])[0]?.skip === true ? 'true' : 'false')" "$MATRIX")" >> "$GITHUB_OUTPUT" | |
| # Generated here because a reusable workflow cannot resolve its own matrix. | |
| - name: Generate DB test matrix | |
| id: generate-db-test-matrix | |
| if: fromJSON(steps.ci-filter.outputs.results).db | |
| run: echo "db_test_matrix=$(node .github/scripts/db-test-matrix.mjs)" >> "$GITHUB_OUTPUT" | |
| - name: Compute affected packages | |
| id: affected-packages | |
| if: fromJSON(steps.ci-filter.outputs.results).unit | |
| env: | |
| CHANGED_FILES: ${{ steps.ci-filter.outputs.changed-files }} | |
| run: | | |
| PACKAGES=$(node packages/testing/janitor/dist/cli.js affected-packages | tr '\n' ' ' | sed 's/ *$//') | |
| echo "Affected packages: $PACKAGES" | |
| echo "list=$PACKAGES" >> "$GITHUB_OUTPUT" | |
| unit-test: | |
| name: Unit tests | |
| if: needs.install-and-build.outputs.unit == 'true' | |
| uses: ./.github/workflows/test-unit-reusable.yml | |
| needs: install-and-build | |
| with: | |
| ref: ${{ needs.install-and-build.outputs.commit_sha }} | |
| collectCoverage: ${{ github.event_name != 'merge_group' }} | |
| affectedPackages: ${{ needs.install-and-build.outputs.affected_packages }} | |
| changedFiles: ${{ needs.install-and-build.outputs.changed_files }} | |
| secrets: inherit | |
| typecheck: | |
| name: Typecheck | |
| if: needs.install-and-build.outputs.ci == 'true' | |
| runs-on: ${{ vars.RUNNER_PROVIDER == 'github' && 'ubuntu-latest' || 'blacksmith-4vcpu-ubuntu-2204' }} | |
| needs: install-and-build | |
| env: | |
| # Inherited by every worker turbo spawns, so the real budget is | |
| # concurrency x cap: 2 x 6 GB fits the runner's 15.2 GB. | |
| NODE_OPTIONS: --max-old-space-size=6144 | |
| steps: | |
| - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1 | |
| with: | |
| ref: ${{ needs.install-and-build.outputs.commit_sha }} | |
| - name: Setup Node.js | |
| uses: ./.github/actions/setup-nodejs | |
| with: | |
| # turbo directly, not the root `typecheck` script, to keep the | |
| # concurrency bound CI-only. | |
| build-command: pnpm turbo typecheck --concurrency=2 | |
| lint: | |
| name: Lint | |
| if: needs.install-and-build.outputs.ci == 'true' | |
| uses: ./.github/workflows/test-linting-reusable.yml | |
| needs: install-and-build | |
| with: | |
| ref: ${{ needs.install-and-build.outputs.commit_sha }} | |
| check-packaging: | |
| name: Check packaging | |
| if: needs.install-and-build.outputs.ci == 'true' | |
| runs-on: ${{ vars.RUNNER_PROVIDER == 'github' && 'ubuntu-latest' || 'blacksmith-4vcpu-ubuntu-2204' }} | |
| needs: install-and-build | |
| steps: | |
| - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1 | |
| with: | |
| ref: ${{ needs.install-and-build.outputs.commit_sha }} | |
| - name: Setup Node.js | |
| uses: ./.github/actions/setup-nodejs | |
| - name: Check packaging | |
| shell: bash | |
| run: | | |
| pnpm -r pack --dry-run | |
| # Seeds the SHA-keyed Docker image cache once so that downstream e2e jobs | |
| # (each of which invokes prepare-docker internally) short-circuit to a | |
| # cache hit instead of racing to rebuild. | |
| prepare-docker: | |
| name: Prepare Docker | |
| needs: install-and-build | |
| if: >- | |
| github.repository == 'n8n-io/n8n' && | |
| github.event_name != 'merge_group' && | |
| (needs.install-and-build.outputs.runtime == 'true' | |
| || needs.install-and-build.outputs.e2e == 'true' | |
| || needs.install-and-build.outputs.e2e_performance == 'true' | |
| || contains(github.event.pull_request.labels.*.name, 'force-e2e') | |
| || contains(github.event.pull_request.labels.*.name, 'run-perf-tests')) | |
| uses: ./.github/workflows/prepare-docker-reusable.yml | |
| with: | |
| branch: ${{ needs.install-and-build.outputs.commit_sha }} | |
| secrets: inherit | |
| # Full e2e run. Internal PRs run multi-main (postgres + redis + caddy + 2 mains + 1 worker). | |
| # Fork PRs run sqlite-only and skip @licensed tests (no enterprise license secrets on forks). | |
| e2e: | |
| name: E2E | |
| needs: [install-and-build, prepare-docker] | |
| if: >- | |
| needs.prepare-docker.result == 'success' && | |
| (needs.install-and-build.outputs.runtime == 'true' | |
| || needs.install-and-build.outputs.e2e == 'true' | |
| || contains(github.event.pull_request.labels.*.name, 'force-e2e')) && | |
| needs.install-and-build.outputs.skip_tests != 'true' && | |
| github.event_name != 'merge_group' | |
| uses: ./.github/workflows/test-e2e-reusable.yml | |
| with: | |
| branch: ${{ needs.install-and-build.outputs.commit_sha }} | |
| test-mode: docker-artifact | |
| test-command: ${{ github.event.pull_request.head.repo.fork == true && 'pnpm --filter=n8n-playwright test:container:sqlite:e2e --grep-invert=@licensed' || 'pnpm --filter=n8n-playwright test:container:multi-main:e2e' }} | |
| workers: '1' | |
| pre-generated-matrix: ${{ needs.install-and-build.outputs.matrix }} | |
| artifact-prefix: e2e | |
| secrets: inherit | |
| # Boots the editor-ui against the Vite dev server and fails on any console | |
| # or page error during load. Catches regressions in dev-mode module | |
| # resolution (missing Vite alias, broken workspace package interop) that | |
| # the production-bundle e2e job bundles around. | |
| dev-server-smoke: | |
| name: Dev-server boot smoke | |
| needs: install-and-build | |
| if: needs.install-and-build.outputs.dev_server_smoke == 'true' && github.event_name != 'merge_group' | |
| uses: ./.github/workflows/test-dev-server-smoke-reusable.yml | |
| with: | |
| ref: ${{ needs.install-and-build.outputs.commit_sha }} | |
| secrets: inherit | |
| db-tests: | |
| name: DB Tests | |
| needs: install-and-build | |
| if: needs.install-and-build.outputs.db == 'true' | |
| uses: ./.github/workflows/test-db-reusable.yml | |
| with: | |
| ref: ${{ needs.install-and-build.outputs.commit_sha }} | |
| matrix: ${{ needs.install-and-build.outputs.db_test_matrix }} | |
| e2e-performance: | |
| name: E2E Performance | |
| needs: [install-and-build, prepare-docker] | |
| # Performance is internal-only (license secrets required, not available on forks). | |
| # Runs when path filter trips OR when the PR carries the `run-perf-tests` label | |
| # (opt-in coverage for shared-utility / Vue Flow changes that the path filter | |
| # would otherwise miss). | |
| if: >- | |
| needs.prepare-docker.result == 'success' && | |
| (needs.install-and-build.outputs.e2e_performance == 'true' || | |
| contains(github.event.pull_request.labels.*.name, 'run-perf-tests')) && | |
| github.event.pull_request.head.repo.fork != true | |
| uses: ./.github/workflows/test-e2e-performance-reusable.yml | |
| secrets: inherit | |
| security-checks: | |
| name: Security Checks | |
| needs: install-and-build | |
| if: needs.install-and-build.outputs.workflows == 'true' | |
| uses: ./.github/workflows/sec-ci-reusable.yml | |
| with: | |
| ref: ${{ needs.install-and-build.outputs.commit_sha }} | |
| secrets: inherit | |
| workflow-scripts: | |
| name: Workflow scripts | |
| needs: install-and-build | |
| if: needs.install-and-build.outputs.workflow_scripts == 'true' | |
| uses: ./.github/workflows/test-workflow-scripts-reusable.yml | |
| with: | |
| ref: ${{ needs.install-and-build.outputs.commit_sha }} | |
| secrets: inherit | |
| # In-process discovery eval — asserts the orchestrator reaches for browser/computer-use | |
| # tools at OAuth/screenshot moments. Lightweight (no Docker), runs in parallel with the | |
| # heavy workflow eval. Non-blocking initially; promote to required after stability. | |
| instance-ai-discovery-evals: | |
| name: Instance AI Discovery Evals | |
| needs: install-and-build | |
| if: >- | |
| !cancelled() && | |
| needs.install-and-build.result == 'success' && | |
| needs.install-and-build.outputs.instance_ai_workflow_eval == 'true' && | |
| github.repository == 'n8n-io/n8n' && | |
| github.event_name != 'merge_group' && | |
| (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork) | |
| uses: ./.github/workflows/test-evals-discovery.yml | |
| with: | |
| branch: ${{ needs.install-and-build.outputs.commit_sha }} | |
| secrets: inherit | |
| # This job is required by GitHub branch protection rules. | |
| # PRs cannot be merged unless this job passes. | |
| required-checks: | |
| name: Required Checks | |
| needs: | |
| [ | |
| install-and-build, | |
| unit-test, | |
| typecheck, | |
| lint, | |
| check-packaging, | |
| e2e, | |
| dev-server-smoke, | |
| db-tests, | |
| security-checks, | |
| workflow-scripts, | |
| ] | |
| if: always() | |
| runs-on: ubuntu-slim | |
| steps: | |
| - uses: useblacksmith/checkout@41cdeedae8edb2e684ba22896a5fd2a3cb85db6b # v1 | |
| with: | |
| sparse-checkout: .github/actions/ci-filter | |
| sparse-checkout-cone-mode: false | |
| - name: Validate required checks | |
| uses: ./.github/actions/ci-filter | |
| with: | |
| mode: validate | |
| job-results: ${{ toJSON(needs) }} | |
| # A red `chore: Bundle/*` PR blocks publishing every fix batched into it, so the | |
| # failure is announced instead of waiting to be noticed. Only fires in n8n-private, | |
| # where bundle branches exist. | |
| notify-bundle-pr-failure: | |
| name: Notify Slack on bundle PR failure | |
| needs: required-checks | |
| if: >- | |
| always() && | |
| needs.required-checks.result == 'failure' && | |
| (github.head_ref == 'bundle/2.x' || github.head_ref == 'bundle/1.x') | |
| runs-on: ubuntu-slim | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| sparse-checkout: .github/scripts/slack | |
| sparse-checkout-cone-mode: false | |
| - name: Notify Slack | |
| env: | |
| SLACK_TOKEN: ${{ secrets.QBOT_SLACK_TOKEN }} | |
| HEAD_REF: ${{ github.head_ref }} | |
| PR_URL: ${{ github.event.pull_request.html_url }} | |
| run: | | |
| node .github/scripts/slack/notify.mjs \ | |
| --channel '#alerts-build' \ | |
| --text "<${PR_URL}|Required Checks failed on the ${HEAD_REF} PR> - the batched fixes stay unpublished until it is green." | |
| # The tarball is scoped to one run and nothing else deleted it, so it evicted | |
| # the entries that are reused across runs (DEVP-912). Gated on the whole run | |
| # being green, not just the image consumers: a re-run of a red workflow should | |
| # restore the image rather than rebuild it. | |
| # | |
| # Blacksmith-only rather than following the usual `runs-on` provider switch: off | |
| # Blacksmith the tarball lands in GitHub's own cache, which this action does not | |
| # address, so there is nothing here to delete. | |
| cleanup-docker-image: | |
| name: Cleanup Docker image | |
| needs: [prepare-docker, e2e-performance, required-checks] | |
| if: | | |
| always() && | |
| vars.RUNNER_PROVIDER != 'github' && | |
| needs.prepare-docker.result == 'success' && | |
| needs.required-checks.result == 'success' && | |
| (needs.e2e-performance.result == 'success' || needs.e2e-performance.result == 'skipped') | |
| runs-on: blacksmith-2vcpu-ubuntu-2204 | |
| steps: | |
| # Not `gh api --method DELETE`: written on Blacksmith runners, where | |
| # actions/cache is transparently intercepted, so GitHub's cache has no | |
| # such entry and the REST call would no-op. | |
| # Key must track build-n8n-docker/load-n8n-docker; a mismatch makes the | |
| # delete a silent no-op rather than a failure. | |
| - name: Delete the image tarball from the cache | |
| uses: useblacksmith/cache-delete@941f534fcf46c8d0a89feeceeb0f5377d82dd5e7 # v2 | |
| with: | |
| key: n8n-docker-image-v2-standard-${{ github.sha }} | |
| # Posts a QA metrics comparison comment on the PR. | |
| # Runs after all checks so any job can emit metrics before this reports. | |
| post-qa-metrics-comment: | |
| name: QA Metrics | |
| needs: [required-checks, e2e-performance] | |
| # Only post when the perf suite actually measured this PR, otherwise the | |
| # comment fires on trivial PRs and flags master drift/noise that isn't theirs. | |
| if: always() && needs.e2e-performance.result == 'success' | |
| uses: ./.github/workflows/util-qa-metrics-comment-reusable.yml | |
| with: | |
| metrics: memory-heap-used-baseline,memory-rss-baseline,instance-ai-heap-used-baseline,instance-ai-rss-baseline,agents-heap-used-baseline,agents-rss-baseline,docker-image-size-n8n,docker-image-size-runners,canvas-cold-load-S-ms,canvas-cold-load-M-ms,canvas-cold-load-L-ms,canvas-server-heap-M-mb,canvas-server-heap-L-mb,canvas-browser-heap-M-mb,canvas-browser-heap-L-mb,canvas-pan-frame-p95-M-ms,canvas-pan-frame-p95-L-ms,canvas-tidy-up-M-ms,canvas-tidy-up-L-ms,canvas-exec-heavy-M-ms,canvas-exec-heavy-L-ms | |
| secrets: inherit |