← All breaking-change reports

Slack shipped 8 major versions in one day: only 3 of them touch your code

slack · guide · 2026-08-04 · upstream source

What changed

On 2026-07-14 the Slack Node SDK cut eight major releases at once: @slack/[email protected], @slack/[email protected], @slack/[email protected], @slack/[email protected], @slack/[email protected], @slack/[email protected], @slack/[email protected], and @slack/[email protected]. All eight are classified breaking in the mendapi database, because a major bump on a package your app imports is breaking until someone proves otherwise.

Someone has to do that proving, and a version number will not do it for you. When we adjudicated the eight one at a time, the spread came out lopsided:

  • code-fixable: 3 — @slack/web-api, @slack/webhook, @slack/socket-mode
  • not-code-fixable: 5 — @slack/oauth, @slack/types, @slack/logger, @slack/cli-test, @slack/cli-hooks

The five on the right are not a smaller version of the problem. Four of them (types, logger, cli-test, cli-hooks) carry a single breaking item: they dropped Node 18. That is a runtime decision, settled in your Dockerfile and CI matrix, and no edit to a .js file can help. @slack/[email protected] is subtler: its error classes gained a shared base class, which widens what your existing catch blocks match rather than narrowing it, so working code keeps working.

The three on the left share one root cause. The v8 line replaced string-code error branching with real Error subclasses. Code written against v7 asks error.code === ErrorCode.PlatformError; the class the SDK now throws still has a code property in some paths, but the sanctioned check is instanceof. This is the failure mode that hurts most in production: nothing throws at import time, nothing fails a type check on a loosely typed catch parameter, and the branch simply stops matching. Your error handler falls through to the rethrow and a rate-limit response becomes an unhandled crash.

Who is affected

You are affected if you branch on error codes anywhere you call Slack. In practice this is retry logic, rate-limit backoff, and the "did Slack reject this or did the network fail" fork that every notification service grows eventually.

Across the whole Slack corpus the pattern holds beyond this one release day: of 38 tracked Slack records, 16 are breaking and 1 is a deprecation, and of those 17, only 4 are adjudicated code-fixable. The rest are CLI tooling changes, documentation moves from api.slack.com to docs.slack.dev, and environment-level requirements. That ratio is worth internalising before you budget a week for "the Slack upgrade" — most of the noise is not aimed at your source tree, and the part that is aimed at it is narrow and mechanical.

The narrowness is what makes it automatable. A rename from a string-enum comparison to an instanceof check against a documented class name is a deterministic transform, provided you know the mapping and you only apply it in files that actually import the package.

How to fix it

Start by finding out whether you use the affected surface at all. The scanner reads your repository locally and reports file and line; nothing is uploaded:

npx mendapi scan --repo .

If it finds call sites, the slack-sdk-v8-errors pack drafts the migration. It carries one rule per package, each gated on that package's import so a repository that only uses @slack/webhook never gets @slack/web-api symbols injected into it:

npx mendapi fix --repo . --migration slack-sdk-v8-errors --dry-run

Here is the actual patch it produces on the demo fixture, unedited:

--- a/index.js
+++ b/index.js
@@ -1,7 +1,7 @@
-const { WebClient, ErrorCode } = require('@slack/web-api');
+const { WebClient, WebAPIHTTPError, WebAPIPlatformError, WebAPIRateLimitedError, WebAPIRequestError } = require('@slack/web-api');
@@ -9,15 +9,15 @@
   } catch (error) {
-    if (error.code === ErrorCode.PlatformError) {
+    if (error instanceof WebAPIPlatformError) {
       console.error('Slack rejected the call:', error.data);
       return null;
     }
-    if (error.code === ErrorCode.RateLimitedError) {
+    if (error instanceof WebAPIRateLimitedError) {
       console.warn('Rate limited, retry after', error.retryAfter);
       return null;
     }
@@ -29,7 +29,7 @@
   } catch (error) {
-    if (error.code !== ErrorCode.HTTPError) throw error;
+    if (!(error instanceof WebAPIHTTPError)) throw error;

Two details in that diff are the reason to run a tool instead of a find-and-replace. The import line is rewritten to bring in exactly the classes the file ends up referencing, not the whole set. And the negated check on the last hunk becomes !(error instanceof WebAPIHTTPError) rather than a naive error instanceof swap that would invert the branch and silently swallow every non-HTTP error.

Dry run is the default. You get a patch to read, not a mutated working tree, and you decide whether the rewrite matches your intent before anything lands. For the five packages the pack deliberately does not touch, the honest answer stays honest: raise your Node floor and move on.

Every Slack verdict with its reasoning is on the Slack breaking-change guide, and the pack's rules and scope are documented in the migration pack reference.

Related

Change data is recorded from upstream provider releases, changelogs and OpenAPI spec diffs; every article names its source.