n8n 2.38.6 Bounds Source Control Push Memory


n8n published [email protected] on 10 September 2026. The build is not a prerelease. The compare range from [email protected] contains one core change: peak memory during a source control push is now bounded. The notes point at commit b3c32da and pull request 37975, a backport onto release-candidate/2.38.x.

The full release notes and downloads are on the GitHub release page. That page is one performance bullet. Figures below are from the linked pull request.

Source control push, and the status pass behind the push and pull modal, could exhaust the Node heap on a large instance. Status file reads were already bounded. Three export allocations were not.

exportTagsToWorkFolder called workflowRepository.find() with the scoped filter and no select. It loaded nodes and connections for every visible workflow, only to compare ids, even when no workflow was selected. The patch selects id only and tests membership with a Set.

exportWorkflowsToWorkFolder loaded every selected workflow in one find(), then wrote files in chunks of SOURCE_CONTROL_WRITE_FILE_BATCH_SIZE (20). The write was bounded. The load was not. The patch loads and writes 20 workflows at a time, so at most one chunk of full workflow entities stays in memory. The write helper no longer chunks again.

exportCredentialsToWorkFolder loaded every sharing in one query, then ran one Promise.all that decrypted, serialized, and opened a file for every credential at once. Credentials now use that same batch of 20.

More database round trips are the cost. Peak export memory tracks batch size times item size. Push time on the measured runs stayed close, 9.5 to 10.7 seconds against 9.9 to 12.6 seconds before. Pull is unchanged.

Workflow, credential, and data table export, plus the data table status loader, resolved an owner by loading project.projectRelations with role and user. Role.scopes is eager, 47 to 175 scopes per role, so raw rows grew with items, members, and scopes. The pull request cites about 189 thousand rows for 3000 credentials. On 31 members per team project, getLocalDataTablesFromDb allocated about 2.37 GB for 114 data tables and killed the owner status call on a 1536 MB heap.

An earlier revision kept the member joins and set loadEagerRelations: false. Scopes stopped hydrating, but Role.scopes stayed typed as an array while the value was undefined, and rows still grew with membership. The merged patch deletes the member joins.

ProjectRelationRepository.findPersonalOwnerEmails returns a Map of project id to email from one raw query on project_relation and user, filtered by PROJECT_OWNER_ROLE_SLUG, chunked with chunkIds. Duplicate ids are dropped. An empty list skips the query. Item queries load project only.

findByWorkflowIds is removed. VariableExporter.resolveWorkflowProjects calls findOwnerProjectsByWorkflowIds. findByCredentialIds no longer joins project.projectRelations. findOwnerProjectsByCredentialIds loads role credential:owner and relation project only. The cost is one extra query per export type. No half loaded Role leaves the repository. Edits sit in project-relation.repository.ts, shared-credentials.repository.ts, shared-workflow.repository.ts, variable.exporter.ts, and source-control.ee.

One message changes. A workflow with no owner now names the workflow id. The old text formatted a workflow relation the query never loaded, so the path threw a TypeError and the caller wrapped it as a generic failure.

Data table runs are not matched (114 tables, then 14), so 2.37 GB versus about 1 MB is not the gain. Membership is the same. Owner status goes from a crash to about 143 MB extra. Project admin is listed at 137 MB before and about 101 MB extra after.

The phase-profile corpus was 1500 workflows of about 250 KB each, 30 team projects, 3050 credentials, the instance owner, and force: true, with every item treated as modified. Source control still needs an Enterprise license and the sourceControl feature. This release does not change that gate.

  • 1500 workflows, 768 MB heap: peak 763 MB before, about 398 to 431 MB allocated, garbage collection bound. After, peak 355 MB, about 104 MB allocated.
  • 1500 workflows, 512 MB heap: FATAL ERROR: Ineffective mark-compacts near heap limit before. After, the push completes at a 354 MB peak.
  • 1500 workflows plus 3050 credentials, 768 MB heap: the process dies before, and completes at a 376 MB peak after. At 512 MB it dies before and completes at a 369 MB peak after. At 1536 MB the peak falls from 1014 MB to 521 MB.
  • exportWorkflowsToWorkFolder: 358 to 379 MB before, 68 to 108 MB after.
  • exportCredentialsToWorkFolder, 3050 credentials: 621 MB before, 4 to 18 MB after, with the before figure attributed to the owner lookup.
  • exportTagsToWorkFolder, 1500 workflows: 341 MB before, 8 MB after.

The 768 MB push peak is about 53 percent lower, and allocated memory falls from about 415 MB to 104 MB. A push that died at 768 MB now finishes inside 512 MB. Export memory follows the batch. Owner resolution follows the project count. The numbers are from this one corpus.

To check it, start n8n with NODE_OPTIONS=--max-old-space-size=512 and push everything from the modal. With tags and no workflow selected, tags.json should still hold the mappings. A pull on a second instance should keep workflow folders, owners, and credential stub owners.