thedotmack/claude-mem · critical · Error
mcp-server.cjs contains a Bun-only ${bunRequireMatch[0]} cal
Error message
mcp-server.cjs contains a Bun-only ${bunRequireMatch[0]} call. This means a transitive import in src/servers/mcp-server.ts pulled in code from worker-service.ts (or another module that touches DatabaseManager/ChromaSync). The MCP server runs under Node and cannot load bun:* modules. Audit recent imports in src/servers/mcp-server.ts and src/services/worker-spawner.ts — the spawner module is intentionally lightweight and MUST NOT import anything that touches SQLite or other Bun-only modules. See PR #1645 for context. What it means
A bundle-hygiene guard over the built MCP server (hooks/<name>.mcp-server.cjs). After esbuild bundles src/servers/mcp-server.ts, the build scans the output for require('bun:*') calls. The MCP server runs under Node (e.g. Claude Desktop), which has no bun:* modules, so any such require proves a transitive import dragged in worker-service.ts or another Bun-only module (DatabaseManager/ChromaSync). The fix is to sever the offending import chain, not to stub bun at runtime.
Source
Thrown at scripts/build-hooks.js:525
define: {
'__DEFAULT_PACKAGE_VERSION__': `"${version}"`
},
banner: {
js: '#!/usr/bin/env node'
}
});
stripHardcodedDirname(`${hooksDir}/${MCP_SERVER.name}.cjs`);
fs.chmodSync(`${hooksDir}/${MCP_SERVER.name}.cjs`, 0o755);
const mcpServerStats = fs.statSync(`${hooksDir}/${MCP_SERVER.name}.cjs`);
console.log(`✓ mcp-server built (${(mcpServerStats.size / 1024).toFixed(2)} KB)`);
const mcpBundleContent = fs.readFileSync(`${hooksDir}/${MCP_SERVER.name}.cjs`, 'utf-8');
const bunRequireRegex = /require\(\s*["']bun:[a-z][a-z0-9_-]*["']\s*\)/;
const bunRequireMatch = mcpBundleContent.match(bunRequireRegex);
if (bunRequireMatch) {
throw new Error(
`mcp-server.cjs contains a Bun-only ${bunRequireMatch[0]} call. This means a transitive import in src/servers/mcp-server.ts pulled in code from worker-service.ts (or another module that touches DatabaseManager/ChromaSync). The MCP server runs under Node and cannot load bun:* modules. Audit recent imports in src/servers/mcp-server.ts and src/services/worker-spawner.ts — the spawner module is intentionally lightweight and MUST NOT import anything that touches SQLite or other Bun-only modules. See PR #1645 for context.`
);
}
const zodRequireRegex = /require\(\s*["']zod(?:\/[^"']*)?["']\s*\)/;
const zodRequireMatch = mcpBundleContent.match(zodRequireRegex);
if (zodRequireMatch) {
throw new Error(
`mcp-server.cjs contains external ${zodRequireMatch[0]}. Claude Desktop can launch this bundle without plugin node_modules available, so Zod must be bundled into the MCP server.`
);
}
const MCP_SERVER_MAX_BYTES = 600 * 1024;
if (mcpServerStats.size > MCP_SERVER_MAX_BYTES) {
console.warn(
`⚠️ mcp-server.cjs is ${(mcpServerStats.size / 1024).toFixed(2)} KB (advisory budget ${(MCP_SERVER_MAX_BYTES / 1024).toFixed(0)} KB). If this jumped unexpectedly, a transitive import may have pulled worker-service.ts or another heavy module into the MCP bundle (see #1645).`
);
}
View on GitHub (pinned to d768ba3643)
Solutions
- Audit imports in src/servers/mcp-server.ts and src/services/worker-spawner.ts for anything that transitively touches SQLite/Chroma/DatabaseManager.
- Move the shared logic into a node-safe module with no Bun imports, and have both worker-service.ts and mcp-server.ts depend on that, OR pass data across the worker/MCP boundary via IPC/HTTP instead of importing.
- Keep worker-spawner.ts lightweight — it must not import anything that touches Bun-only modules (the error message states this contract explicitly).
- Rebuild; the guard passes when no require('bun:*') remains in the bundle.
Example fix
// before (src/services/worker-spawner.ts) — pulls a Bun-only helper
import { queryObservations } from './worker-service.js';
// after — keep the spawner node-safe; MCP server gets data via the worker HTTP API instead
import { spawn } from 'child_process';
// no DB imports here; mcp-server.ts calls worker /api/* endpoints Defensive patterns
Strategy: validation
Validate before calling
// After building, scan the bundle yourself to catch bun:* leakage early:
const out = fs.readFileSync('hooks/mcp-server.cjs','utf8');
if (/require\(\s*["']bun:[a-z][a-z0-9_-]*["']\s*\)/.test(out)) {
throw new Error('Bun-only require leaked into mcp-server bundle');
} Type guard
// Node-safe module marker: a module that must not import bun:* // Add a lint rule forbidding bun:* imports in src/servers/** and src/services/worker-spawner.ts.
Try / catch
// Build-time only. Fix the import graph — do not patch the bundle. // Move shared logic into a node-safe module or call the worker over HTTP from the MCP server.
Prevention
- Keep src/services/worker-spawner.ts free of any import that transitively touches SQLite/Chroma/DatabaseManager.
- Add an eslint boundary rule (no-restricted-imports) banning bun:* in the MCP server's import graph.
- When adding an import to mcp-server.ts, mentally trace it to ensure it doesn't reach worker-service.ts.
When it happens
Trigger: Adding an import in src/servers/mcp-server.ts (or src/services/worker-spawner.ts) that transitively reaches a module importing 'bun:sqlite'/'bun:test'/etc. The esbuild bundle then carries the require('bun:...') and this guard fires. Cross-ref PR #1645 which introduced the isolation.
Common situations: Refactor that makes the MCP server import a shared service that itself imports DatabaseManager. Adding a utility to worker-spawner.ts that pulls in a Bun-only helper. A new feature that needs DB access from the MCP server path.
Related errors
- mcp-server.cjs contains external ${zodRequireMatch[0]}. Clau
- plugin/scripts/bun-runner.js uses optional chaining (?.) or
- Hand-edited shell string detected in ${filePath} (mcp-search
- Hand-edited shell string detected in ${filePath} (${dottedPa
- Hand-edited Windows shell string detected in ${filePath} (${
AI-assisted analysis of thedotmack/claude-mem@d768ba3643 (2026-08-12).
Data as JSON: /api/errors/aee61afdab69ca6d.
Report an issue: GitHub.