Files
claude-mem/docs/reports/2026-01-05--issue-557-settings-module-loader-error.md
T
Alex Newman f38b5b85bc fix: resolve issues #543, #544, #545, #557 (#558)
* docs: add investigation reports for 5 open GitHub issues

Comprehensive analysis of issues #543, #544, #545, #555, and #557:

- #557: settings.json not generated, module loader error (node/bun mismatch)
- #555: Windows hooks not executing, hasIpc always false
- #545: formatTool crashes on non-JSON tool_input strings
- #544: mem-search skill hint shown incorrectly to Claude Code users
- #543: /claude-mem slash command unavailable despite installation

Each report includes root cause analysis, affected files, and proposed fixes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(logger): handle non-JSON tool_input in formatTool (#545)

Wrap JSON.parse in try-catch to handle raw string inputs (e.g., Bash
commands) that aren't valid JSON. Falls back to using the string as-is.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(context): update mem-search hint to reference MCP tools (#544)

Update hint messages to reference MCP tools (search, get_observations)
instead of the deprecated "mem-search skill" terminology.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(settings): auto-create settings.json on first load (#557, #543)

When settings.json doesn't exist, create it with defaults instead of
returning in-memory defaults. Creates parent directory if needed.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(hooks): use bun runtime for hooks except smart-install (#557)

Change hook commands from node to bun since hooks use bun:sqlite.
Keep smart-install.js on node since it bootstraps bun installation.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: rebuild plugin scripts

* docs: clarify that build artifacts must be committed

* fix(docs): update build artifacts directory reference in CLAUDE.md

* test: add test coverage for PR #558 fixes

- Fix 2 failing tests: update "mem-search skill" → "MCP tools" expectations
- Add 56 tests for formatTool() JSON.parse crash fix (Issue #545)
- Add 27 tests for settings.json auto-creation (Issue #543)

Test coverage includes:
- formatTool: JSON parsing, raw strings, objects, null/undefined, all tool types
- Settings: file creation, directory creation, schema migration, edge cases

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix(tests): clean up flaky tests and fix circular dependency

Phase 1 of test quality improvements:

- Delete 6 harmful/worthless test files that used problematic mock.module()
  patterns or tested implementation details rather than behavior:
  - context-builder.test.ts (tested internal implementation)
  - export-types.test.ts (fragile mock patterns)
  - smart-install.test.ts (shell script testing antipattern)
  - session_id_refactor.test.ts (outdated, tested refactoring itself)
  - validate_sql_update.test.ts (one-time migration validation)
  - observation-broadcaster.test.ts (excessive mocking)

- Fix circular dependency between logger.ts and SettingsDefaultsManager.ts
  by using late binding pattern - logger now lazily loads settings

- Refactor mock.module() to spyOn() in several test files for more
  maintainable and less brittle tests:
  - observation-compiler.test.ts
  - gemini_agent.test.ts
  - error-handler.test.ts
  - server.test.ts
  - response-processor.test.ts

All 649 tests pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor(tests): phase 2 - reduce mock-heavy tests and improve focus

- Remove mock-heavy query tests from observation-compiler.test.ts, keep real buildTimeline tests
- Convert session_id_usage_validation.test.ts from 477 to 178 lines of focused smoke tests
- Remove tests for language built-ins from worker-spawn.test.ts (JSON.parse, array indexing)
- Rename logger-coverage.test.ts to logger-usage-standards.test.ts for clarity

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* docs(tests): phase 3 - add JSDoc mock justification to test files

Document mock usage rationale in 5 test files to improve maintainability:
- error-handler.test.ts: Express req/res mocks, logger spies (~11%)
- fallback-error-handler.test.ts: Zero mocks, pure function tests
- session-cleanup-helper.test.ts: Session fixtures, worker mocks (~19%)
- hook-constants.test.ts: process.platform mock for Windows tests (~12%)
- session_store.test.ts: Zero mocks, real SQLite :memory: database

Part of ongoing effort to document mock justifications per TESTING.md guidelines.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test(integration): phase 5 - add 72 tests for critical coverage gaps

Add comprehensive test coverage for previously untested areas:

- tests/integration/hook-execution-e2e.test.ts (10 tests)
  Tests lifecycle hooks execution flow and context propagation

- tests/integration/worker-api-endpoints.test.ts (19 tests)
  Tests all worker service HTTP endpoints without heavy mocking

- tests/integration/chroma-vector-sync.test.ts (16 tests)
  Tests vector embedding synchronization with ChromaDB

- tests/utils/tag-stripping.test.ts (27 tests)
  Tests privacy tag stripping utilities for both <private> and
  <meta-observation> tags

All tests use real implementations where feasible, following the
project's testing philosophy of preferring integration-style tests
over unit tests with extensive mocking.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* context update

* docs: add comment linking DEFAULT_DATA_DIR locations

Added NOTE comment in logger.ts pointing to the canonical DEFAULT_DATA_DIR
in SettingsDefaultsManager.ts. This addresses PR reviewer feedback about
the fragility of having the default defined in two places to avoid
circular dependencies.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 19:45:09 -05:00

344 lines
11 KiB
Markdown

# Investigation Report: Issue #557 - Plugin Fails to Start
**Date:** January 5, 2026
**Issue:** [#557](https://github.com/thedotmack/claude-mem/issues/557) - Plugin fails to start: settings.json not generated, worker throws module loader error
**Author:** Sheikh Abdur Raheem Ali (@sheikheddy)
**Investigator:** Claude (Opus 4.5)
---
## Executive Summary
The plugin fails to start during the SessionStart hook with a Node.js module loader error. This investigation identifies two separate but related issues:
1. **Primary Issue:** Runtime mismatch - hooks are built for Bun but invoked with Node.js
2. **Secondary Issue:** settings.json auto-creation only happens via HTTP API, not during initialization
The root cause appears to be that Claude Code 2.0.76 is invoking hooks with Node.js despite hooks having `#!/usr/bin/env bun` shebangs, and Node.js v25.2.1 cannot execute code with `bun:sqlite` imports (an external module reference that doesn't exist in Node.js).
---
## Environment Details
| Component | Version |
|-----------|---------|
| claude-mem | 8.1.0 |
| Claude Code | 2.0.76 |
| Node.js | v25.2.1 |
| Bun | 1.3.5 |
| OS | macOS 26.2 (arm64) |
| Database Size | 17.9 MB (existing data) |
---
## Issue Analysis
### Error Location
The error occurs at:
```
node:internal/modules/cjs/loader:1423
throw err;
^
```
This error signature indicates Node.js (not Bun) is attempting to load a CommonJS module that has unresolvable dependencies.
### Hook Configuration Analysis
From `/Users/alexnewman/Scripts/claude-mem/plugin/hooks/hooks.json`:
```json
{
"SessionStart": [
{
"matcher": "startup|clear|compact",
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/smart-install.js\"",
"timeout": 300
},
{
"type": "command",
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" start",
"timeout": 60
},
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js\"",
"timeout": 60
},
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/user-message-hook.js\"",
"timeout": 60
}
]
}
]
}
```
**Key Observation:** Hooks are explicitly invoked with `node` but are built as ESM bundles with Bun-specific features.
### Build Configuration Analysis
From `/Users/alexnewman/Scripts/claude-mem/scripts/build-hooks.js`:
1. **Hooks** are built with:
- `format: 'esm'` (ES modules)
- `external: ['bun:sqlite']` (Bun-specific SQLite binding)
- Shebang: `#!/usr/bin/env bun`
2. **Worker Service** is built with:
- `format: 'cjs'` (CommonJS)
- `external: ['bun:sqlite']`
- Shebang: `#!/usr/bin/env bun`
The `bun:sqlite` external dependency is the critical issue. When Node.js tries to load these files, it cannot resolve `bun:sqlite` as it's a Bun-specific built-in module.
### Settings.json Auto-Creation Analysis
From `/Users/alexnewman/Scripts/claude-mem/src/services/worker/http/routes/SettingsRoutes.ts`:
```typescript
private ensureSettingsFile(settingsPath: string): void {
if (!existsSync(settingsPath)) {
const defaults = SettingsDefaultsManager.getAllDefaults();
const dir = path.dirname(settingsPath);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
writeFileSync(settingsPath, JSON.stringify(defaults, null, 2), 'utf-8');
logger.info('SETTINGS', 'Created settings file with defaults', { settingsPath });
}
}
```
This method is only called when:
1. `GET /api/settings` is requested
2. `POST /api/settings` is requested
**Problem:** If the worker service fails to start (due to the module loader error), the HTTP API never becomes available, so `ensureSettingsFile` is never called.
### SettingsDefaultsManager Behavior
From `/Users/alexnewman/Scripts/claude-mem/src/shared/SettingsDefaultsManager.ts`:
```typescript
static loadFromFile(settingsPath: string): SettingsDefaults {
try {
if (!existsSync(settingsPath)) {
return this.getAllDefaults(); // Returns defaults, doesn't create file
}
// ... rest of loading logic
} catch (error) {
return this.getAllDefaults(); // Fallback to defaults on any error
}
}
```
**Behavior:** When settings.json doesn't exist, `loadFromFile` returns in-memory defaults but does NOT create the file. This is defensive programming (fail-safe) but means the file is never auto-created during worker startup.
---
## Root Cause Analysis
### Primary Root Cause: Runtime Mismatch
The hooks are designed to run under Bun (as indicated by their shebangs and `bun:sqlite` dependency), but hooks.json explicitly invokes them with `node`:
```json
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js\""
```
When Node.js v25.2.1 attempts to load these ESM bundles:
1. It parses the JavaScript successfully (ESM is valid)
2. It encounters `import ... from 'bun:sqlite'`
3. Node.js cannot resolve `bun:sqlite` (not a valid Node.js specifier)
4. CJS loader throws the error at line 1423
### Why This Worked Before (Potential Regression Paths)
1. **Bun Availability:** The smart-install.js script auto-installs Bun, but the PATH may not be updated within the same shell session
2. **Claude Code Change:** Claude Code 2.0.76 may have changed how it invokes hooks (not honoring shebangs, using explicit `node` command)
3. **Node.js v25 Change:** Node.js v25 may handle ESM/CJS boundaries differently than earlier versions
### Secondary Root Cause: Settings Not Auto-Created at Startup
The worker service's background initialization (`initializeBackground()`) loads settings but doesn't create the file:
```typescript
const settings = SettingsDefaultsManager.loadFromFile(USER_SETTINGS_PATH);
const modeId = settings.CLAUDE_MEM_MODE;
ModeManager.getInstance().loadMode(modeId);
```
`loadFromFile` returns defaults when the file is missing but doesn't write them to disk.
---
## Affected Files
| File | Role | Issue |
|------|------|-------|
| `/plugin/hooks/hooks.json` | Hook configuration | Explicitly uses `node` instead of `bun` |
| `/plugin/scripts/context-hook.js` | SessionStart hook | ESM with `bun:sqlite` dependency |
| `/plugin/scripts/user-message-hook.js` | SessionStart hook | ESM with `bun:sqlite` dependency |
| `/plugin/scripts/worker-service.cjs` | Worker service | CJS with `bun:sqlite` dependency |
| `/src/shared/SettingsDefaultsManager.ts` | Settings manager | Doesn't auto-create file |
| `/src/services/worker/http/routes/SettingsRoutes.ts` | HTTP routes | Only creates file on API access |
| `/scripts/build-hooks.js` | Build script | Marks `bun:sqlite` as external |
---
## Proposed Fixes
### Fix 1: Update hooks.json to Use Bun (Recommended)
Change all hook commands from `node` to `bun`:
```json
{
"type": "command",
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/context-hook.js\"",
"timeout": 60
}
```
**Rationale:** Hooks depend on `bun:sqlite`, so they must run under Bun.
### Fix 2: Create Settings File During Startup
Add file creation to `SettingsDefaultsManager.loadFromFile`:
```typescript
static loadFromFile(settingsPath: string): SettingsDefaults {
try {
if (!existsSync(settingsPath)) {
const defaults = this.getAllDefaults();
// Create directory if needed
const dir = path.dirname(settingsPath);
if (!existsSync(dir)) {
mkdirSync(dir, { recursive: true });
}
// Write defaults to file
writeFileSync(settingsPath, JSON.stringify(defaults, null, 2), 'utf-8');
logger.info('SETTINGS', 'Created settings file with defaults', { settingsPath });
return defaults;
}
// ... existing logic
} catch (error) {
logger.warn('SETTINGS', 'Failed to load/create settings, using defaults', { settingsPath }, error);
return this.getAllDefaults();
}
}
```
**Rationale:** This ensures settings.json always exists after first access, regardless of how the plugin starts.
### Fix 3: Build Hooks Without bun:sqlite Dependency (Alternative)
Modify the build to inline SQLite operations or use a Node.js-compatible SQLite library:
```javascript
// In build-hooks.js
external: [], // Remove bun:sqlite from externals
```
This would require using `better-sqlite3` or similar, which has been deliberately avoided due to native module compilation issues.
### Fix 4: Add Fallback Logic in Hooks (Defensive)
Add runtime detection to hooks to provide better error messages:
```typescript
if (typeof Bun === 'undefined') {
console.error('This hook requires Bun runtime. Please ensure Bun is installed.');
process.exit(1);
}
```
---
## Verification Steps
1. **Confirm Bun is installed and in PATH:**
```bash
which bun
bun --version
```
2. **Manually test context-hook with Bun:**
```bash
bun ~/.claude/plugins/marketplaces/thedotmack/plugin/scripts/context-hook.js
```
3. **Manually test context-hook with Node (should fail):**
```bash
node ~/.claude/plugins/marketplaces/thedotmack/plugin/scripts/context-hook.js
```
4. **Check if settings.json exists:**
```bash
cat ~/.claude-mem/settings.json
```
5. **Verify worker can start:**
```bash
bun ~/.claude/plugins/marketplaces/thedotmack/plugin/scripts/worker-service.cjs start
```
---
## Related Issues
- **Issue #290:** `refactor: simplify hook execution - use Node directly instead of Bun` - This commit changed hooks to use Node, potentially introducing this regression
- **Issue #265:** `fix: add npm fallback when bun install fails with alias packages` - Related to Bun/npm installation issues
- **Issue #527:** `uv-homebrew-analysis` - Related to dependency installation issues
---
## Workaround for Users
Until a fix is released, users can manually:
1. **Ensure Bun is installed:**
```bash
curl -fsSL https://bun.sh/install | bash
source ~/.bashrc # or ~/.zshrc
```
2. **Create settings.json manually:**
```bash
mkdir -p ~/.claude-mem
cat > ~/.claude-mem/settings.json << 'EOF'
{
"CLAUDE_MEM_MODEL": "claude-sonnet-4-5",
"CLAUDE_MEM_CONTEXT_OBSERVATIONS": "50",
"CLAUDE_MEM_WORKER_PORT": "37777",
"CLAUDE_MEM_WORKER_HOST": "127.0.0.1",
"CLAUDE_MEM_PROVIDER": "claude",
"CLAUDE_MEM_DATA_DIR": "$HOME/.claude-mem",
"CLAUDE_MEM_LOG_LEVEL": "INFO",
"CLAUDE_MEM_MODE": "code"
}
EOF
```
3. **Start worker manually:**
```bash
bun ~/.claude/plugins/marketplaces/thedotmack/plugin/scripts/worker-service.cjs start
```
---
## Conclusion
This issue is a **runtime mismatch regression** where hooks built for Bun are being invoked with Node.js. The fix requires updating `hooks.json` to use Bun for all hook commands that depend on `bun:sqlite`. The settings.json creation is a secondary issue that should be addressed by ensuring the file is created during first access in `SettingsDefaultsManager.loadFromFile`.
**Priority:** High (blocks plugin startup)
**Severity:** Critical (plugin completely non-functional)
**Effort:** Low (configuration change + minor code addition)