Skip to content

fix: bundle Slack runtime deps to make community-node install deterministic - #15

Open
kilpa wants to merge 1 commit into
mbakgun:masterfrom
kilpa:fix/bundle-runtime-deps
Open

fix: bundle Slack runtime deps to make community-node install deterministic#15
kilpa wants to merge 1 commit into
mbakgun:masterfrom
kilpa:fix/bundle-runtime-deps

Conversation

@kilpa

@kilpa kilpa commented Apr 30, 2026

Copy link
Copy Markdown

Summary

Fixes #14.

n8n installs community packages with --install-strategy=shallow, which disables npm's normal dependency hoisting and forces transitive deps into deeply nested per-package node_modules trees. Under that install strategy, in some npm/cache states a nested copy of @slack/socket-mode ends up under @slack/bolt/node_modules/ without its dist/ extracted, and n8n's package loader fails with:

ENOENT: no such file or directory, open
'.../n8n-nodes-slack-socket-mode/node_modules/@slack/bolt/node_modules/@slack/socket-mode/dist/src/index.js'

n8n's lifecycle service auto-uninstalls the package after the load failure, so the node is permanently unusable for affected users until the install path itself is made deterministic.

Change

Add bundleDependencies listing the four runtime deps (@slack/bolt, @slack/socket-mode, http-proxy-agent, https-proxy-agent) so they are shipped pre-extracted inside the published tarball. npm then does not need to resolve them transitively at install time, and the install becomes deterministic regardless of install strategy or npm cache state.

   "dependencies": {
     "@slack/bolt": "^4.6.0",
     "@slack/socket-mode": "^2.0.5",
     "http-proxy-agent": "^7.0.2",
     "https-proxy-agent": "^7.0.6"
-  }
+  },
+  "bundleDependencies": [
+    "@slack/bolt",
+    "@slack/socket-mode",
+    "http-proxy-agent",
+    "https-proxy-agent"
+  ]
 }

Tradeoffs

  • Tarball size grows from ~30 KB to ~2.5 MB. Slack's runtime SDK is the bulk of that; users were paying that disk cost already, just resolved at install time instead of bake time.
  • Top-level deps are still listed in dependencies (npm requires this when using bundleDependencies), so semver and Dependabot continue to work as before. Bumping a Slack dep is a one-line change followed by npm install + npm pack.
  • A more aggressive alternative is bundling everything into dist/ via esbuild/tsup; that produces a smaller tarball but is a larger refactor. bundleDependencies is the minimum-diff fix that solves the underlying install determinism problem.

Verification

Packed the branch locally and installed the resulting tarball with the exact flags n8n uses inside an n8n container (node 24.14.1, npm 11.12.1):

npm install ./mbakgun-n8n-nodes-slack-socket-mode-1.7.0.tgz \
  --bin-links=false --install-strategy=shallow \
  --ignore-scripts=true --package-lock=false

Result: @slack/socket-mode/dist/src/index.js is present at the expected path, and both @slack/bolt and @slack/socket-mode require() cleanly under the package root.

Notes

  • Version field is unchanged — happy to bump to 1.7.1 (or whatever you'd like) before publish.
  • I skipped editing the README; the install instructions don't change for end users.

Disclosure: this PR was prepared with the help of Claude (issue investigation, repro, and writeup). The diff is small and reviewable; happy to iterate.

…nistic

n8n installs community packages with `--install-strategy=shallow`, which
disables npm's normal dependency hoisting and forces transitive deps into
deeply nested per-package node_modules trees. In some npm/cache states
this produces a nested copy of `@slack/socket-mode` under `@slack/bolt`
whose `dist/` is not extracted, causing n8n's package loader to fail
with:

    ENOENT: no such file or directory, open
    '.../n8n-nodes-slack-socket-mode/node_modules/@slack/bolt/node_modules/@slack/socket-mode/dist/src/index.js'

After the failure, n8n's lifecycle service auto-uninstalls the package,
making the node permanently unusable for affected users.

Listing the runtime Slack and proxy dependencies in `bundleDependencies`
ships them pre-extracted inside the published tarball, so npm does not
need to resolve them transitively at install time. The install becomes
deterministic regardless of npm's install strategy or cache state.

Verified by packing locally and installing the resulting tarball with
the exact flags n8n uses (`--bin-links=false --install-strategy=shallow
--ignore-scripts=true --package-lock=false`) inside an n8n container:
`@slack/socket-mode/dist/src/index.js` is present and both `bolt` and
`socket-mode` can be required by the loader.

Fixes mbakgun#14

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mbakgun

mbakgun commented Apr 30, 2026

Copy link
Copy Markdown
Owner

Hi Hendrik, have you attempted building this package locally and then launching n8n using Docker Compose? Once built, if you mount the package into the container at install time (for instance via a volume), does everything function as expected?

Thanks again for your support and for putting together this PR.

@kilpa

kilpa commented Apr 30, 2026

Copy link
Copy Markdown
Author

Yes — verified with your docker-compose.yml + start-n8n.sh flow on the fix branch.

Local-dev flow (volume-mounted package, your standard reproduction):

docker compose up -d

Boot logs:

n8n-1 | debug   No codex available for: slackSocketTrigger { "file": "directory-loader.js", "function": "addCodex" }
n8n-1 | info    n8n ready on ::, port 5678
n8n-1 | debug   Initialized module "community-packages"

Then inside the running container:

$ docker compose exec n8n node -e "
const Node = new (require('/home/node/.n8n/custom/dist/nodes/SlackSocketTrigger/SlackSocketTrigger.node.js')).SlackSocketTrigger();
console.log(Node.description.name, '/', Node.description.displayName, 'v' + Node.description.version);
require('/home/node/.n8n/node_modules/@slack/socket-mode');
require('/home/node/.n8n/node_modules/@slack/bolt');
console.log('slack runtime resolves: ok');
"
slackSocketTrigger / Slack Socket Mode Trigger v2
slack runtime resolves: ok

So with the volume-mounted dev setup, the node loads, instantiates, and the bundled @slack/* runtime is reachable. Healthcheck (/healthz) returns {"status":"ok"}.

Note on what this does and doesn't exercise

The dev flow above mounts ./node_modules directly, so it doesn't go through npm pack / community-package install — meaning bundleDependencies isn't actually being exercised here, but it's also not breaking the dev flow, which I think was your concern.

The bundleDependencies change only affects what gets shipped in npm pack and how downstream npm install <tgz> resolves transitive Slack deps. I covered that path separately on real n8n containers (node 24.14.1, npm 11.12.1):

# inside the n8n container
cd /tmp/repro && echo '{"name":"t","private":true}' > package.json
npm install ./mbakgun-n8n-nodes-slack-socket-mode-1.7.0.tgz \
  --bin-links=false --install-strategy=shallow \
  --ignore-scripts=true --package-lock=false

ls node_modules/@mbakgun/n8n-nodes-slack-socket-mode/node_modules/@slack/socket-mode/dist/src/index.js
# -> file present (this is what's missing today, causing the ENOENT)

I also installed the resulting tarball on a real n8n queue-mode deployment (n8n 2.16.1, GKE, persistent PVC for the main pod). The package loaded cleanly on boot, no auto-uninstall, and the trigger is selectable in the editor.

Happy to also bump the version (e.g. 1.7.1) before publish if that's how you'd prefer to ship it, or leave it for you. Thanks again for taking a look!

@mbakgun

mbakgun commented May 19, 2026

Copy link
Copy Markdown
Owner

Hi @kilpa , I’ve published version 1.7.1, but I wasn’t able to upgrade from the previous 1.7.0 in my local environment.

A fresh install is also failing. In other words, uninstalling and reinstalling(with the latest version) doesn’t work either, since 1.7.1 fails to install as well. Could you test this on your side? Based on your results, we can decide how to proceed with the pull request.

@SkybuIIy

SkybuIIy commented Jun 9, 2026

Copy link
Copy Markdown

hi @mbakgun,
I'm gonna jump in for @kilpa :))

can confirm we are having the same problem as you described with v1.7.1
it seems that @slack/logger is being hoisted, so it doesn't get bundled with @slack/bolt.
it might work adding @slack/logger to bundleDependencies, tho this might just reveal more missing deps. instead I would recommend using a tool like tsup or esbuild to bundle everything.

let me know what you think!

@kilpa

kilpa commented Jun 9, 2026

Copy link
Copy Markdown
Author

@mbakgun with my changes everything seems to be installing fine with the latest n8n currently available.

@mbakgun

mbakgun commented Jun 9, 2026

Copy link
Copy Markdown
Owner

@mbakgun with my changes everything seems to be installing fine with the latest n8n currently available.

Very interesting, we had this issue on 2 different instances while upgrading from 1.7.0 to 1.7.1, and it looks like a similar situation happened on @SkybuIIy as well.

By the way, in practice, the following is already the safe path and always works: remove the plugin from n8n, restart n8n, then install version 1.7.0 or earlier. There is no issue with this approach.

@mbakgun

mbakgun commented Jun 9, 2026

Copy link
Copy Markdown
Owner

hi @mbakgun, I'm gonna jump in for @kilpa :))

can confirm we are having the same problem as you described with v1.7.1 it seems that @slack/logger is being hoisted, so it doesn't get bundled with @slack/bolt. it might work adding @slack/logger to bundleDependencies, tho this might just reveal more missing deps. instead I would recommend using a tool like tsup or esbuild to bundle everything.

let me know what you think!

I think it’s worth trying. Could you please open a separate pull request for it so that we can release version 1.7.2 and test it out?
Thx in advance ! 🙏

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Installing the community node fails on n8n v2.x

5 participants