Summary
After restarting the openclaw gateway (any reason: config change, system reboot, manual restart), all HTTP requests return 500 Internal Server Error. WebSocket connections continue to work normally. The root cause is a module resolution failure for @slack/web-api that is silently swallowed by a bare catch {} block.
Environment
- openclaw version: 2026.3.31 (213a704)
- Node.js version: v25.8.1
- OS: macOS (Darwin 25.4.0)
- Install method:
npm install -g openclaw
- Install path:
/opt/homebrew/lib/node_modules/openclaw
- Service manager: macOS LaunchAgent (
~/Library/LaunchAgents/ai.openclaw.gateway.plist)
Steps to Reproduce
- Install openclaw v2026.3.31 globally:
npm install -g openclaw
- Start the gateway (via LaunchAgent or
openclaw gateway start)
- Access any HTTP endpoint — all return 500 immediately after a fresh process start
The issue is most noticeable after changing any config that triggers a gateway restart (e.g., modifying gateway.port or gateway.controlUi.allowedOrigins in the Web UI), because it exposes that HTTP was silently broken since the process started.
Expected Behavior
- HTTP requests to
/, /__openclaw/control-ui-config.json, /health, etc. should return their appropriate responses (200, 302, etc.)
- The Web UI (control-ui) should load normally after a gateway restart
Actual Behavior
- Every HTTP request returns
500 Internal Server Error
- The gateway log shows no errors (the exception is silently swallowed)
- The Web UI never loads; the WebSocket client (
webchat) never reconnects
- WebSocket connections still work normally (they bypass the broken code path)
Root Cause Analysis
The Error
By temporarily patching the catch {} block in gateway-cli-ChUE8Mp7.js to log the error, the actual exception is:
Error: Cannot find module '@slack/web-api'
Require stack:
- /opt/homebrew/lib/node_modules/openclaw/dist/runtime-api-DARHIvKf.js
at Module._resolveFilename (node:internal/modules/cjs/loader:1475:15)
at jitiResolve (.../jiti.cjs:1:148703)
at jitiRequire (.../jiti.cjs:1:150290)
...
Call Chain
Every HTTP request goes through this chain:
handleRequest(req, res) [gateway-cli-ChUE8Mp7.js ~L24107]
└─ requestStages["slack"].run() [always included, regardless of Slack config]
└─ handleSlackHttpRequest(req, res) [slack-surface-BW-gJBzz.js]
└─ loadFacadeModule()
└─ loadBundledPluginPublicSurfaceModuleSync(
{ dirName: 'slack', artifactBasename: 'api.js' }
) [facade-runtime-D_UMLPAC.js]
└─ getJiti(modulePath)(modulePath)
└─ extensions/slack/api.js
└─ import "../../runtime-api-DARHIvKf.js"
└─ import { WebClient } from "@slack/web-api"
└─ MODULE NOT FOUND ← throws here
Why Module Resolution Fails
@slack/web-api is installed at:
/opt/homebrew/lib/node_modules/openclaw/dist/extensions/slack/node_modules/@slack/web-api
But runtime-api-DARHIvKf.js is located at:
/opt/homebrew/lib/node_modules/openclaw/dist/runtime-api-DARHIvKf.js
The getJiti() function in facade-runtime-D_UMLPAC.js creates the jiti instance using import.meta.url (i.e., the facade-runtime file's own URL, inside /dist/) as the resolution base:
// facade-runtime-D_UMLPAC.js ~L71
const loader = createJiti(import.meta.url, { // ← base is /dist/, not /dist/extensions/slack/
...buildPluginLoaderJitiOptions(aliasMap),
tryNative
});
Node.js module resolution starting from /dist/ looks for @slack/web-api in:
/dist/node_modules/@slack/web-api ← does not exist
/node_modules/openclaw/node_modules/@slack/web-api ← does not exist
- (etc., never descends into
extensions/slack/node_modules/)
It never finds the package installed at extensions/slack/node_modules/@slack/web-api.
Why the Bare catch {} Makes This Catastrophic
The handleSlackHttpRequest stage is registered unconditionally for every HTTP request, even when Slack is not configured. When it throws, the outer catch {} absorbs the error silently and returns 500:
// gateway-cli-ChUE8Mp7.js ~L24270
} catch { // ← no error variable bound, no logging
res.statusCode = 500;
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.end("Internal Server Error");
}
This means:
- The error produces zero log output — impossible to diagnose without patching the binary
- Every HTTP request fails, not just Slack-related ones
- The Web UI is completely inaccessible
Why It Appeared to Work Before Restart
loadedFacadeModules is a process-level Map cache in facade-runtime-D_UMLPAC.js. On a fresh process start, the cache is empty. The first HTTP request triggers the load, which fails. On failure, the cache entry is deleted, so every subsequent request retries and fails again.
The previous process (v2026.3.28 or an earlier v2026.3.31 instance) either: (a) used a different code path that didn't have this issue, or (b) successfully loaded the module using a different resolution strategy that was changed in v2026.3.31.
Workaround
Create symlinks in /dist/node_modules/ so that Node.js resolution can find the @slack/* packages:
DIST=/opt/homebrew/lib/node_modules/openclaw/dist
mkdir -p "$DIST/node_modules/@slack"
for pkg in web-api bolt logger oauth socket-mode types; do
ln -sf "$DIST/extensions/slack/node_modules/@slack/$pkg" \
"$DIST/node_modules/@slack/$pkg"
done
After creating the links, restart the gateway — HTTP requests return 200 immediately.
Note: This workaround is overwritten if openclaw is updated via npm install -g openclaw. Re-run the commands after each update until the bug is fixed.
Suggested Fixes
Option A: Fix jiti resolution base (preferred)
When loading a plugin's facade module, create the jiti instance with the plugin's directory as the resolution base, not the facade-runtime file's location:
// facade-runtime-D_UMLPAC.js
// Change:
const loader = createJiti(import.meta.url, { ... });
// To (use plugin module's directory as resolution base):
const loader = createJiti(
pathToFileURL(path.dirname(location.modulePath)).href,
{ ... }
);
Option B: Add @slack/* to jiti alias map
In buildPluginLoaderAliasMap, add explicit aliases for @slack/* packages pointing to the extension's node_modules:
aliasMap['@slack/web-api'] = path.resolve(extensionDir, 'node_modules/@slack/web-api');
// (similar for other @slack/* packages)
Option C: Bundle @slack/web-api into the plugin artifact
Bundle @slack/web-api and its dependencies directly into runtime-api-DARHIvKf.js so no external resolution is needed.
Option D: Improve error handling to not break all HTTP requests (recommended regardless)
As a defensive measure, the handleSlackHttpRequest stage should degrade gracefully on module load failures, and the outer catch should log the error:
// Slack stage: degrade gracefully if module unavailable
{
name: "slack",
run: async () => {
try {
return await handleSlackHttpRequest(req, res);
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') return false;
throw err;
}
}
}
// Outer catch: log instead of silently swallowing
} catch (err) {
log.error(`[gateway] unhandled error in handleRequest: ${String(err)}`);
res.statusCode = 500;
res.end("Internal Server Error");
}
Option D is the most important fix regardless of which root-cause fix is chosen — a single plugin's module resolution failure should never silently take down the entire HTTP server with no log output.
Files Involved
| File |
Role |
dist/gateway-cli-ChUE8Mp7.js |
HTTP request handler; bare catch {} hides the error |
dist/facade-runtime-D_UMLPAC.js |
Plugin facade loader; getJiti() uses wrong resolution base |
dist/runtime-api-DARHIvKf.js |
Slack runtime API; import { WebClient } from "@slack/web-api" fails |
dist/slack-surface-BW-gJBzz.js |
Exports handleSlackHttpRequest via facade loader |
dist/extensions/slack/node_modules/@slack/ |
Correct install location of @slack/* packages |
Additional Context
- The gateway has no Slack accounts configured — yet the Slack module is loaded on every HTTP request
- The bug is 100% reproducible on a fresh
openclaw-gateway process start with v2026.3.31
- The symptom (HTTP 500 after config-triggered restart) is extremely difficult to diagnose because the bare
catch {} produces zero log output
Summary
After restarting the openclaw gateway (any reason: config change, system reboot, manual restart), all HTTP requests return
500 Internal Server Error. WebSocket connections continue to work normally. The root cause is a module resolution failure for@slack/web-apithat is silently swallowed by a barecatch {}block.Environment
npm install -g openclaw/opt/homebrew/lib/node_modules/openclaw~/Library/LaunchAgents/ai.openclaw.gateway.plist)Steps to Reproduce
npm install -g openclawopenclaw gateway start)The issue is most noticeable after changing any config that triggers a gateway restart (e.g., modifying
gateway.portorgateway.controlUi.allowedOriginsin the Web UI), because it exposes that HTTP was silently broken since the process started.Expected Behavior
/,/__openclaw/control-ui-config.json,/health, etc. should return their appropriate responses (200, 302, etc.)Actual Behavior
500 Internal Server Errorwebchat) never reconnectsRoot Cause Analysis
The Error
By temporarily patching the
catch {}block ingateway-cli-ChUE8Mp7.jsto log the error, the actual exception is:Call Chain
Every HTTP request goes through this chain:
Why Module Resolution Fails
@slack/web-apiis installed at:But
runtime-api-DARHIvKf.jsis located at:The
getJiti()function infacade-runtime-D_UMLPAC.jscreates the jiti instance usingimport.meta.url(i.e., thefacade-runtimefile's own URL, inside/dist/) as the resolution base:Node.js module resolution starting from
/dist/looks for@slack/web-apiin:/dist/node_modules/@slack/web-api← does not exist/node_modules/openclaw/node_modules/@slack/web-api← does not existextensions/slack/node_modules/)It never finds the package installed at
extensions/slack/node_modules/@slack/web-api.Why the Bare
catch {}Makes This CatastrophicThe
handleSlackHttpRequeststage is registered unconditionally for every HTTP request, even when Slack is not configured. When it throws, the outercatch {}absorbs the error silently and returns 500:This means:
Why It Appeared to Work Before Restart
loadedFacadeModulesis a process-levelMapcache infacade-runtime-D_UMLPAC.js. On a fresh process start, the cache is empty. The first HTTP request triggers the load, which fails. On failure, the cache entry is deleted, so every subsequent request retries and fails again.The previous process (v2026.3.28 or an earlier v2026.3.31 instance) either: (a) used a different code path that didn't have this issue, or (b) successfully loaded the module using a different resolution strategy that was changed in v2026.3.31.
Workaround
Create symlinks in
/dist/node_modules/so that Node.js resolution can find the@slack/*packages:After creating the links, restart the gateway — HTTP requests return 200 immediately.
Suggested Fixes
Option A: Fix jiti resolution base (preferred)
When loading a plugin's facade module, create the jiti instance with the plugin's directory as the resolution base, not the
facade-runtimefile's location:Option B: Add
@slack/*to jiti alias mapIn
buildPluginLoaderAliasMap, add explicit aliases for@slack/*packages pointing to the extension'snode_modules:Option C: Bundle
@slack/web-apiinto the plugin artifactBundle
@slack/web-apiand its dependencies directly intoruntime-api-DARHIvKf.jsso no external resolution is needed.Option D: Improve error handling to not break all HTTP requests (recommended regardless)
As a defensive measure, the
handleSlackHttpRequeststage should degrade gracefully on module load failures, and the outercatchshould log the error:Option D is the most important fix regardless of which root-cause fix is chosen — a single plugin's module resolution failure should never silently take down the entire HTTP server with no log output.
Files Involved
dist/gateway-cli-ChUE8Mp7.jscatch {}hides the errordist/facade-runtime-D_UMLPAC.jsgetJiti()uses wrong resolution basedist/runtime-api-DARHIvKf.jsimport { WebClient } from "@slack/web-api"failsdist/slack-surface-BW-gJBzz.jshandleSlackHttpRequestvia facade loaderdist/extensions/slack/node_modules/@slack/@slack/*packagesAdditional Context
openclaw-gatewayprocess start with v2026.3.31catch {}produces zero log output