fix: strip settings.engineType so workflow updates survive n8n >= 2.36.0 (v2.76.1) - #1044
Conversation
…6.0 (v2.76.1) n8n 2.36.0 added engineType to the workflow entity's persisted settings without adding it to the Public API write schema, which is additionalProperties: false. Our updates read-modify-write, so GET handed us the property and the echoed PUT failed with "settings must NOT have additional properties" (#1043). engineType is now marked derived and stripped from every create and update, like binaryMode before it; n8n keeps stored settings for omitted keys, so the value is preserved. check-settings-drift gains the axis that was blind to this class: it diffs IWorkflowSettings from the installed n8n-workflow package against the OpenAPI schema, fails on an unhandled entity-only property, and fails again when n8n later publishes a stripped property to the schema. The entity parser fails loudly on extends/declaration merging, and the classification gate is a pure function with direct tests plus an offline CI assertion that every entity property is known to the table. Verified end-to-end against n8n 2.36.7: a workflow with settings.engineType = "v2" injected updates successfully through the fixed server, and the setting survives the write unchanged. Fixes #1043 Conceived by Romuald Członkowski - www.aiadvisors.pl/en Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXDW1LGvRXaQydK21X89sj
There was a problem hiding this comment.
🟡 Changes recommended
diffSettingsProperties() currently treats derived keys as “entity-only expected” when the entity axis is skipped (explicit-version mode) without respecting the since version, which can misclassify future settings for older targets.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Fixes workflow update failures against n8n ≥ 2.36.0 by ensuring settings.engineType (persisted by n8n but rejected by the Public API write schema) is stripped from all workflow create/update payloads, and strengthens the settings drift guard to catch future entity-vs-schema mismatches.
Changes:
- Mark
engineTypeas a derived/entity-only workflow setting so it’s always stripped on writes. - Extend
check-settings-driftto also diff the workflow entity settings type (IWorkflowSettingsfromn8n-workflow) against the Public API schema, with direct unit tests. - Bump package version to
2.76.1and document the fix inCHANGELOG.md.
File summaries
| File | Description |
|---|---|
| tests/unit/services/n8n-version.test.ts | Adds coverage that engineType is dropped even in pass-through and null-version paths. |
| tests/unit/services/n8n-validation.test.ts | Ensures create/update workflow cleaning strips engineType alongside other derived settings. |
| tests/unit/scripts/check-settings-drift.test.ts | Adds tests for parsing entity settings and classifying drift buckets (including entity-only). |
| src/constants/workflow-settings.ts | Adds engineType as derived + entityOnly, and updates docs for derived/entity-only semantics. |
| scripts/check-settings-drift.ts | Adds entity-axis parsing and drift classification to catch entity-vs-schema mismatches like engineType. |
| package.json | Version bump to 2.76.1. |
| package-lock.json | Lockfile version bump to 2.76.1. |
| CHANGELOG.md | Documents the fix and drift-check enhancement in the 2.76.1 release notes. |
Review details
- Files reviewed: 7/8 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| const meta = WORKFLOW_SETTINGS_PROPERTIES[name]; | ||
| if (meta.derived && (entityProperties === null || entityProperties.has(name))) { | ||
| entityOnly.push(name); | ||
| continue; | ||
| } | ||
| (compareVersions(meta.since, target) <= 0 ? removed : ahead).push(name); | ||
| } |
There was a problem hiding this comment.
Adopted in 4a6c49e: without an entity set, a derived property whose since is later than the target is now classified ahead ("known from a later n8n than the pin") instead of entityOnly. Running the check explicitly against 2.23.0 now lists engineType/binaryMode/credentialResolverId under ahead, and the entity-only line disappears for old targets. Covered by the new test "classifies a derived property from a later n8n as ahead, not entity-only, without an entity set".
Test Results Summary📊 ArtifactsGenerated at Mon, 31 Aug 2026 15:24:44 GMT |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Codex and Copilot findings on the new drift axis, all adopted: - Verify the fetched schema's n8n release actually ships the installed n8n-workflow (exact pin in its package.json) and warn on a mismatch - the nodes-base pin names a sibling release, not the meta-package. - An entity-only property is handled only when marked BOTH derived and entityOnly, and the remediation message says so - derived alone would leave the published-upstream detector disarmed. - The entity parser strips block comments (a declaration-shaped line or stray brace inside one no longer derails it) and throws when content shares the opening-brace line instead of skipping it silently. - Without an entity set, a derived property from a later n8n than the target is classified ahead-of-the-pin, not "entity-only, expected". - An unhandled entity-only property is no longer double-reported as expected by the info line. Conceived by Romuald Członkowski - www.aiadvisors.pl/en Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXDW1LGvRXaQydK21X89sj
There was a problem hiding this comment.
🔵 Needs a closer look
Release metadata is inconsistent (the changelog needs the 2.76.0 section restored under its own header, and package.runtime.json still reports 2.76.0 while package.json is 2.76.1).
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
CHANGELOG.md:20
- The 2.76.1 section currently includes the major “Added/Changed/Fixed/Security” release notes that appear to belong to 2.76.0 (the previous release), because the 2.76.0 header is missing. This makes the changelog history ambiguous/incorrect for both versions.
### Added
- Files reviewed: 7/8 changed files
- Comments generated: 0 new
- Review effort level: Lite
…AST (review round 3) - The schema is now fetched from the n8n release whose published pins match the installed packages, found by scanning the newest releases' package.json pins (nodes-base@2.36.4 ships in n8n@2.36.7, while n8n@2.36.4 pins nodes-base@2.36.3 - the same-number mapping read a neighbouring release's schema). Falls back to the old mapping with a pin warning when the registry is unreachable or nothing matches. - The entity parser is the real TypeScript parser instead of a hand-rolled lexer: line comments, string literal types, inline braces and split declarations no longer silently truncate the property set, and any member it cannot enumerate (index signature, method, computed name) fails closed. - Restore the 2.76.0 changelog header that the 2.76.1 entry accidentally swallowed, and bump package.runtime.json to 2.76.1 (both Copilot). Conceived by Romuald Członkowski - www.aiadvisors.pl/en Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXDW1LGvRXaQydK21X89sj
|
Round 3 pushed (c113cde), addressing Copilot's suppressed changelog/version findings and the remaining Codex review findings:
|
There was a problem hiding this comment.
🟢 Approval recommended
The fix is narrowly scoped, well-covered by unit tests (including the production null-version path), and the new drift guard reduces recurrence risk.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
tests/unit/scripts/check-settings-drift.test.ts:258
- The comment says “everything except derived-only keys”, but the filter is actually excluding
meta.entityOnlyentries. This is minor, but it can mislead future maintenance of the test setup.
// The published schema of n8n 2.36 as the table models it: everything except derived-only keys
- Files reviewed: 8/9 changed files
- Comments generated: 0 new
- Review effort level: Lite
… round 4) - Reject any syntactic diagnostic before walking the AST: createSourceFile recovers from errors, so a truncated declarations file would otherwise yield a partial property set that reads as "no entity-only properties". The internal parseDiagnostics field disappearing also throws. - Collect only top-level IWorkflowSettings declarations - a same-named interface inside a namespace does not merge with the export. - The residual-skew warning now covers both pins (the fallback release can match n8n-workflow while shipping a different n8n-nodes-base) and the pins-unfetchable case. Conceived by Romuald Członkowski - www.aiadvisors.pl/en Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXDW1LGvRXaQydK21X89sj
There was a problem hiding this comment.
🟢 Approval recommended
The fix cleanly routes engineType through the existing derived-settings stripping path and adds targeted drift-guard + tests to prevent recurrence.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
tests/unit/scripts/check-settings-drift.test.ts:278
- The comment says the derived keys are excluded, but the code actually excludes only
entityOnlykeys (.filter(([, meta]) => !meta.entityOnly)). This is misleading when reading the test setup.
const v236 = { major: 2, minor: 36, patch: 4 };
// The published schema of n8n 2.36 as the table models it: everything except derived-only keys
const schemaOf236 = new Set(
- Files reviewed: 8/9 changed files
- Comments generated: 0 new
- Review effort level: Lite
…es (review round 5) The skew warning and release resolution read the installed n8n-nodes-base version from node_modules instead of the package.json pin, so a stale install cannot pair this run's entity types with a schema neither belongs to; an unreadable install is itself reported. Conceived by Romuald Członkowski - www.aiadvisors.pl/en Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXDW1LGvRXaQydK21X89sj
There was a problem hiding this comment.
🔵 Needs a closer look
installedEntityPackageVersion() likely resolves the wrong package.json path (returning null in common layouts), weakening the schema-release matching logic in check-settings-drift.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
scripts/check-settings-drift.ts:280
- installedEntityPackageVersion() builds a path by going two levels up from require.resolve('n8n-workflow'), which will point at the wrong package.json when the module entry is
dist/index.js(common layout). That causes this function to frequently return null and weakens schema-release resolution. Prefer reading the version directly fromn8n-workflow/package.json, consistent with installedNodesBaseVersion().
function installedEntityPackageVersion(): string | null {
try {
const pkgPath = join(dirname(require.resolve('n8n-workflow')), '..', '..', 'package.json');
return (require(pkgPath) as { version?: string }).version ?? null;
} catch {
return null;
}
- Files reviewed: 8/9 changed files
- Comments generated: 0 new
- Review effort level: Lite
…path (Copilot round 3) Consistent with installedNodesBaseVersion; the resolved-entry path walk stays as a fallback for a future exports map that blocks the subpath. Conceived by Romuald Członkowski - www.aiadvisors.pl/en Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JXDW1LGvRXaQydK21X89sj
|
Copilot's round-3 suppressed comment adopted in the latest push: |
There was a problem hiding this comment.
🟢 Approval recommended
The change is narrowly scoped, well-covered by targeted unit tests (including the production null-version path), and the new drift guard reduces the chance of regressions from future n8n schema/entity divergence.
Review details
- Files reviewed: 8/9 changed files
- Comments generated: 0 new
- Review effort level: Lite
Fixes #1043.
Problem
n8n_update_partial_workflowandn8n_update_full_workflowfail against n8n ≥ 2.36.0 withsettings must NOT have additional properties. n8n 2.36.0 (n8n-io/n8n#36428) addedengineTypeto the workflow entity's persisted settings without adding it to the Public API write schema (additionalProperties: false). Our writes are read-modify-write, soGEThands us the property and we echo it into aPUTthat rejects it. The version-aware filter never fires because modern n8n hides its version from API clients, and the drift guard was blind to the entity-vs-schema axis —engineTypewas absent from both sides of the schema-only diff. 16 occurrences in 24h on the hosted server, growing as instances update.Fix
src/constants/workflow-settings.ts:engineTypeis markedderived: true, routing it throughstripDerivedSettings— removed from every create and update payload regardless of the (dead) version probe, the same treatmentbinaryModeandcredentialResolverIdget. Stripping is lossless: n8n'sWorkflowService.updatespreads stored settings under the request body, so an omitted key is preserved, not cleared (verified on then8n@2.36.0tag and live, below).scripts/check-settings-drift.tsnow checks the axis that was blind to this class: it parsesIWorkflowSettingsfrom the installedn8n-workflowpackage and fails when n8n persists a settings property the write schema rejects that our table does not strip — and fails again in the reverse direction, when n8n later publishes a property we strip (so an upstream schema fix cannot leave us stripping it forever). Verified red on the pre-fix tree, green post-fix. The entity parser fails loudly onextends/declaration merging instead of returning a silently incomplete set, and the classification gate is extracted into a purediffSettingsPropertieswith direct tests.engineTypestrip coverage on the create, update, and version-filter paths — including the null-version path production actually takes — plus an offline CI assertion that every entity settings property is known to the table (the full drift check only runs insidenpm run update:n8n; this makes the same class fail in CI on anyn8n-workflowbump).Verification
npm run check:settings-driftgreen (both default and explicit-version modes).settings.engineType = "v2"server-side (the Public API cannot set it — that is the bug), confirmedGETechoes it, rann8n_update_partial_workflowthrough the fixed server — success, where 2.76.0 fails with the 400 — and confirmedengineType: "v2"still persisted after the write.Follow-up (out of scope)
engineTypeto the Public API settings schema — as it stands the property can be neither read-modify-written nor deliberately set through the API. The newpublishedEntityOnlydrift failure will surface when that lands.settings must NOT have additional properties400 insendWorkflowWrite(issue Bug: workflow updates fail with "settings must NOT have additional properties" on n8n >= 2.36.0 (settings.engineType) #1043 follow-up 3), which would turn the next property of this class into a warning instead of a hard failure.Conceived by Romuald Członkowski - www.aiadvisors.pl/en
🤖 Generated with Claude Code