f38b5b85bc
* 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>
344 lines
11 KiB
Markdown
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)
|