Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e91868a7f6 | |||
| 644cccd3e1 | |||
| e6df88bf42 | |||
| ef823a89c1 | |||
| b169e18de0 |
@@ -10,7 +10,7 @@
|
||||
"plugins": [
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "9.0.2",
|
||||
"version": "9.0.3",
|
||||
"source": "./plugin",
|
||||
"description": "Persistent memory system for Claude Code - context compression across sessions"
|
||||
}
|
||||
|
||||
+15
-15
@@ -2,6 +2,21 @@
|
||||
|
||||
All notable changes to claude-mem.
|
||||
|
||||
## [v9.0.2] - 2026-01-10
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **Windows Terminal Tab Accumulation (#625, #628)**: Fixed terminal tab accumulation on Windows by implementing graceful exit strategy. All expected failure scenarios (port conflicts, version mismatches, health check timeouts) now exit with code 0 instead of code 1.
|
||||
- **Windows 11 Compatibility (#625)**: Replaced deprecated WMIC commands with PowerShell `Get-Process` and `Get-CimInstance` for process enumeration. WMIC is being removed from Windows 11.
|
||||
|
||||
## Maintenance
|
||||
|
||||
- **Removed Obsolete CLAUDE.md Files**: Cleaned up auto-generated CLAUDE.md files from `~/.claude/plans/` and `~/.claude/plugins/marketplaces/` directories.
|
||||
|
||||
---
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v9.0.1...v9.0.2
|
||||
|
||||
## [v9.0.1] - 2026-01-08
|
||||
|
||||
## Bug Fixes
|
||||
@@ -1274,18 +1289,3 @@ Fixed unbounded database growth in the `pending_messages` table by implementing
|
||||
|
||||
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.2.3...v7.2.4
|
||||
|
||||
## [v7.2.3] - 2025-12-15
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
- **Fix MCP server failures on plugin updates**: Add 2-second pre-restart delay in `ensureWorkerVersionMatches()` to give files time to sync before killing the old worker. This prevents the race condition where the worker restart happened too quickly after plugin file updates, causing "Worker service connection failed" errors.
|
||||
|
||||
## Changes
|
||||
|
||||
- Add `PRE_RESTART_SETTLE_DELAY` constant (2000ms) to `hook-constants.ts`
|
||||
- Add delay before `ProcessManager.restart()` call in `worker-utils.ts`
|
||||
- Fix pre-existing bug where `port` variable was undefined in error logging
|
||||
|
||||
---
|
||||
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||
|
||||
|
||||
@@ -1,688 +0,0 @@
|
||||
🔍 Scanning for error handling anti-patterns...
|
||||
|
||||
Found 80 TypeScript files
|
||||
|
||||
|
||||
═══════════════════════════════════════════════════════════════
|
||||
ERROR HANDLING ANTI-PATTERNS DETECTED
|
||||
═══════════════════════════════════════════════════════════════
|
||||
|
||||
Found 153 anti-patterns:
|
||||
🔴 CRITICAL: 26
|
||||
🟠 HIGH: 47
|
||||
🟡 MEDIUM: 80
|
||||
|
||||
🔴 CRITICAL ISSUES (Fix immediately - these cause silent failures):
|
||||
─────────────────────────────────────────────────────────────
|
||||
|
||||
📁 src/utils/transcript-parser.ts:44
|
||||
❌ NO_LOGGING_IN_CATCH
|
||||
Catch block has no logging - errors occur invisibly.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// [POSSIBLY RELEVANT]: Parse errors accumulated in parseErrors array for batch access, logging each line would be excessive
|
||||
this.parseErrors.push({
|
||||
lineNumber: index + 1,
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
... (2 more lines)
|
||||
|
||||
📁 src/shared/timeline-formatting.ts:18
|
||||
❌ NO_LOGGING_IN_CATCH
|
||||
Catch block has no logging - errors occur invisibly.
|
||||
|
||||
Code:
|
||||
} catch (err) {
|
||||
// [POSSIBLY RELEVANT]: Expected JSON parse failures for malformed data fields, too frequent to log
|
||||
return [];
|
||||
}
|
||||
|
||||
📁 src/shared/paths.ts:105
|
||||
❌ NO_LOGGING_IN_CATCH
|
||||
Catch block has no logging - errors occur invisibly.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// [POSSIBLY RELEVANT]: Expected when not in git repo or git unavailable, common fallback path
|
||||
return basename(process.cwd());
|
||||
}
|
||||
|
||||
📁 src/sdk/prompts.ts:98
|
||||
❌ NO_LOGGING_IN_CATCH
|
||||
Catch block has no logging - errors occur invisibly.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// [POSSIBLY RELEVANT]: Expected JSON parse failures for plain string tool inputs, normal fallback
|
||||
toolInput = obs.tool_input;
|
||||
}
|
||||
|
||||
📁 src/sdk/prompts.ts:105
|
||||
❌ NO_LOGGING_IN_CATCH
|
||||
Catch block has no logging - errors occur invisibly.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// [POSSIBLY RELEVANT]: Expected JSON parse failures for plain string tool outputs, normal fallback
|
||||
toolOutput = obs.tool_output;
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:68
|
||||
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||
Critical path continues after error - may cause silent data corruption.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// [POSSIBLY RELEVANT]: PID file cleanup is non-critical, log full error and continue shutdown
|
||||
logger.warn('SYSTEM', 'Failed to remove PID file', { path: PID_FILE }, error as Error);
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:131
|
||||
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||
Critical path continues after error - may cause silent data corruption.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// [POSSIBLY RELEVANT]: Context update is non-critical, log full error and continue
|
||||
logger.warn('CURSOR', 'Failed to update context file', { projectName }, error as Error);
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:152
|
||||
❌ NO_LOGGING_IN_CATCH
|
||||
Catch block has no logging - errors occur invisibly.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// [POSSIBLY RELEVANT]: Expected failure when port is free, called frequently for health checks
|
||||
return false;
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:165
|
||||
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||
Critical path continues after error - may cause silent data corruption.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// [POSSIBLY RELEVANT]: Expected failures during startup health check, will retry
|
||||
logger.debug('SYSTEM', 'Service not ready yet, will retry', { port }, error as Error);
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:368
|
||||
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||
Critical path continues after error - may cause silent data corruption.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// [POSSIBLY RELEVANT]: Shutdown must complete, log error and exit with failure code
|
||||
logger.error('SYSTEM', 'Error during shutdown', {}, error as Error);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:623
|
||||
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||
Critical path continues after error - may cause silent data corruption.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// [POSSIBLY RELEVANT]: Process may have already exited during cleanup, expected failure
|
||||
logger.debug('SYSTEM', 'Failed to kill process, may have already exited', { pid }, error as Error);
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:632
|
||||
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||
Critical path continues after error - may cause silent data corruption.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// [POSSIBLY RELEVANT]: Process may have already exited during cleanup, expected failure
|
||||
logger.debug('SYSTEM', 'Process already exited', { pid }, error as Error);
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:838
|
||||
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||
Critical path continues after error - may cause silent data corruption.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// Recovery is best-effort - skip failed sessions and continue with others
|
||||
logger.warn('SYSTEM', `Failed to process session ${sessionDbId}`, {}, error as Error);
|
||||
result.sessionsSkipped++;
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:987
|
||||
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||
Critical path continues after error - may cause silent data corruption.
|
||||
|
||||
Code:
|
||||
} catch {
|
||||
// Process may have already exited - continue shutdown
|
||||
logger.debug('SYSTEM', 'Process already exited during force kill', { pid });
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:1004
|
||||
❌ NO_LOGGING_IN_CATCH
|
||||
Catch block has no logging - errors occur invisibly.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// Expected: process has exited
|
||||
// Not logging - this is called in a tight loop during cleanup
|
||||
return false;
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:1095
|
||||
❌ EMPTY_CATCH
|
||||
Empty catch block - errors are silently swallowed. User will waste hours debugging.
|
||||
|
||||
Code:
|
||||
} catch {
|
||||
// Start fresh if corrupt
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:1301
|
||||
❌ EMPTY_CATCH
|
||||
Empty catch block - errors are silently swallowed. User will waste hours debugging.
|
||||
|
||||
Code:
|
||||
} catch {
|
||||
// CLI not found
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:1413
|
||||
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||
Critical path continues after error - may cause silent data corruption.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// Start fresh if corrupt
|
||||
logger.warn('SYSTEM', 'Corrupt mcp.json, creating new config', { path: mcpJsonPath, error: error instanceof Error ? error.message : String(error) });
|
||||
config = { mcpServers: {} };
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:1670
|
||||
❌ EMPTY_CATCH
|
||||
Empty catch block - errors are silently swallowed. User will waste hours debugging.
|
||||
|
||||
Code:
|
||||
} catch {
|
||||
// Worker not running - that's ok, context will be generated after first session
|
||||
}
|
||||
|
||||
📁 src/services/worker-service.ts:2050
|
||||
❌ PROMISE_CATCH_NO_LOGGING
|
||||
Promise .catch() without logging - errors are silently swallowed.
|
||||
|
||||
Code:
|
||||
.catch((error) => {
|
||||
logger.failure('SYSTEM', 'Worker failed to start', {}, error as Error);
|
||||
removePidFile();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
📁 src/services/worker/SDKAgent.ts:545
|
||||
❌ CATCH_AND_CONTINUE_CRITICAL_PATH
|
||||
Critical path continues after error - may cause silent data corruption.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// [POSSIBLY RELEVANT]: Expected failure when claude not in PATH, falls through to clear error message below
|
||||
logger.debug('SDK', 'Claude executable auto-detection failed', {}, error as Error);
|
||||
}
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1403
|
||||
❌ NO_LOGGING_IN_CATCH
|
||||
Catch block has no logging - errors occur invisibly.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// [POSSIBLY RELEVANT]: Expected JSON parse failures for plain string file lists, normal fallback
|
||||
if (summary.files_read.trim()) {
|
||||
lines.push(`**Files Read:** ${summary.files_read}`);
|
||||
}
|
||||
... (1 more lines)
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1418
|
||||
❌ NO_LOGGING_IN_CATCH
|
||||
Catch block has no logging - errors occur invisibly.
|
||||
|
||||
Code:
|
||||
} catch (error) {
|
||||
// [POSSIBLY RELEVANT]: Expected JSON parse failures for plain string file lists, normal fallback
|
||||
if (summary.files_edited.trim()) {
|
||||
lines.push(`**Files Edited:** ${summary.files_edited}`);
|
||||
}
|
||||
... (1 more lines)
|
||||
|
||||
📁 src/services/worker/PaginationHelper.ts:54
|
||||
❌ NO_LOGGING_IN_CATCH
|
||||
Catch block has no logging - errors occur invisibly.
|
||||
|
||||
Code:
|
||||
} catch (err) {
|
||||
// [POSSIBLY RELEVANT]: Expected JSON parse failures for plain string file paths, normal fallback
|
||||
return filePathsStr;
|
||||
}
|
||||
|
||||
📁 src/services/context-generator.ts:202
|
||||
❌ NO_LOGGING_IN_CATCH
|
||||
Catch block has no logging - errors occur invisibly.
|
||||
|
||||
Code:
|
||||
} catch (parseError) {
|
||||
// [POSSIBLY RELEVANT]: Expected malformed JSON lines in transcript, logging each would be excessive
|
||||
continue;
|
||||
}
|
||||
|
||||
📁 src/services/context-generator.ts:226
|
||||
❌ NO_LOGGING_IN_CATCH
|
||||
Catch block has no logging - errors occur invisibly.
|
||||
|
||||
Code:
|
||||
} catch (error: any) {
|
||||
if (error.code === 'ERR_DLOPEN_FAILED') {
|
||||
unlinkSync(VERSION_MARKER_PATH);
|
||||
} catch (unlinkError) {
|
||||
// [POSSIBLY RELEVANT]: Marker file may not exist during first run, expected cleanup failure
|
||||
... (1 more lines)
|
||||
|
||||
🟠 HIGH PRIORITY:
|
||||
─────────────────────────────────────────────────────────────
|
||||
|
||||
📁 src/ui/viewer/hooks/useSSE.ts:50 - LARGE_TRY_BLOCK
|
||||
Try block has 33 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/ui/viewer/hooks/usePagination.ts:54 - LARGE_TRY_BLOCK
|
||||
Try block has 19 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/ui/viewer/hooks/useSettings.ts:64 - LARGE_TRY_BLOCK
|
||||
Try block has 14 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/ui/viewer/hooks/useContextPreview.ts:47 - LARGE_TRY_BLOCK
|
||||
Try block has 11 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/bin/import-xml-observations.ts:62 - LARGE_TRY_BLOCK
|
||||
Try block has 12 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/bin/import-xml-observations.ts:134 - LARGE_TRY_BLOCK
|
||||
Try block has 15 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/bin/import-xml-observations.ts:167 - LARGE_TRY_BLOCK
|
||||
Try block has 13 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/servers/mcp-server.ts:52 - LARGE_TRY_BLOCK
|
||||
Try block has 14 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/servers/mcp-server.ts:97 - LARGE_TRY_BLOCK
|
||||
Try block has 21 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:55 - LARGE_TRY_BLOCK
|
||||
Try block has 67 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:227 - LARGE_TRY_BLOCK
|
||||
Try block has 38 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:346 - LARGE_TRY_BLOCK
|
||||
Try block has 40 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:427 - LARGE_TRY_BLOCK
|
||||
Try block has 43 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:495 - LARGE_TRY_BLOCK
|
||||
Try block has 15 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:532 - LARGE_TRY_BLOCK
|
||||
Try block has 35 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:599 - LARGE_TRY_BLOCK
|
||||
Try block has 13 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:1550 - LARGE_TRY_BLOCK
|
||||
Try block has 27 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker-service.ts:438 - LARGE_TRY_BLOCK
|
||||
Try block has 16 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker-service.ts:526 - LARGE_TRY_BLOCK
|
||||
Try block has 11 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker-service.ts:666 - LARGE_TRY_BLOCK
|
||||
Try block has 56 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker-service.ts:814 - LARGE_TRY_BLOCK
|
||||
Try block has 15 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker-service.ts:1638 - LARGE_TRY_BLOCK
|
||||
Try block has 24 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker-service.ts:1753 - LARGE_TRY_BLOCK
|
||||
Try block has 28 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/sync/ChromaSync.ts:99 - LARGE_TRY_BLOCK
|
||||
Try block has 28 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/sync/ChromaSync.ts:344 - LARGE_TRY_BLOCK
|
||||
Try block has 14 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/sync/ChromaSync.ts:534 - LARGE_TRY_BLOCK
|
||||
Try block has 32 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/sync/ChromaSync.ts:609 - LARGE_TRY_BLOCK
|
||||
Try block has 106 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/GeminiAgent.ts:144 - LARGE_TRY_BLOCK
|
||||
Try block has 76 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/BranchManager.ts:120 - LARGE_TRY_BLOCK
|
||||
Try block has 13 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/BranchManager.ts:268 - LARGE_TRY_BLOCK
|
||||
Try block has 21 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:120 - LARGE_TRY_BLOCK
|
||||
Try block has 43 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:382 - LARGE_TRY_BLOCK
|
||||
Try block has 13 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:642 - LARGE_TRY_BLOCK
|
||||
Try block has 22 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:726 - LARGE_TRY_BLOCK
|
||||
Try block has 18 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:818 - LARGE_TRY_BLOCK
|
||||
Try block has 14 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:888 - LARGE_TRY_BLOCK
|
||||
Try block has 16 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:958 - LARGE_TRY_BLOCK
|
||||
Try block has 16 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1028 - LARGE_TRY_BLOCK
|
||||
Try block has 16 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1098 - LARGE_TRY_BLOCK
|
||||
Try block has 16 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1181 - LARGE_TRY_BLOCK
|
||||
Try block has 17 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1282 - LARGE_TRY_BLOCK
|
||||
Try block has 16 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1493 - LARGE_TRY_BLOCK
|
||||
Try block has 147 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1725 - LARGE_TRY_BLOCK
|
||||
Try block has 15 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/OpenRouterAgent.ts:104 - LARGE_TRY_BLOCK
|
||||
Try block has 77 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/http/routes/SessionRoutes.ts:151 - LARGE_TRY_BLOCK
|
||||
Try block has 13 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/worker/http/routes/SessionRoutes.ts:185 - LARGE_TRY_BLOCK
|
||||
Try block has 20 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
📁 src/services/context-generator.ts:182 - LARGE_TRY_BLOCK
|
||||
Try block has 15 lines - too broad. Multiple errors lumped together.
|
||||
|
||||
🟡 MEDIUM PRIORITY:
|
||||
─────────────────────────────────────────────────────────────
|
||||
|
||||
📁 src/ui/viewer/hooks/useStats.ts:13 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/ui/viewer/hooks/useSSE.ts:93 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/ui/viewer/hooks/useTheme.ts:19 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/ui/viewer/hooks/useTheme.ts:64 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/ui/viewer/hooks/usePagination.ts:84 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/ui/viewer/hooks/useContextPreview.ts:31 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/ui/viewer/hooks/useContextPreview.ts:60 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/bin/import-xml-observations.ts:152 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/bin/import-xml-observations.ts:183 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/bin/import-xml-observations.ts:329 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/bin/import-xml-observations.ts:361 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/utils/logger.ts:55 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/utils/logger.ts:74 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/utils/logger.ts:269 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/shared/timeline-formatting.ts:18 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/shared/SettingsDefaultsManager.ts:152 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/shared/paths.ts:105 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/sdk/prompts.ts:98 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/sdk/prompts.ts:105 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/servers/mcp-server.ts:76 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/servers/mcp-server.ts:123 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/servers/mcp-server.ts:269 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:138 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:278 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:399 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:482 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:520 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:575 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:619 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:1489 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:1521 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/sqlite/SessionStore.ts:1577 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker-service.ts:59 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker-service.ts:68 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker-service.ts:131 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker-service.ts:152 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker-service.ts:165 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker-service.ts:185 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker-service.ts:368 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker-service.ts:623 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker-service.ts:632 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker-service.ts:743 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker-service.ts:838 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker-service.ts:963 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker-service.ts:1004 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker-service.ts:1797 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/queue/SessionQueueProcessor.ts:31 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/sync/ChromaSync.ts:578 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/sync/ChromaSync.ts:808 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SettingsManager.ts:45 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/BranchManager.ts:138 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/BranchManager.ts:243 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/BranchManager.ts:300 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SDKAgent.ts:545 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:185 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:398 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:676 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:754 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:838 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:912 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:982 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1052 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1127 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1214 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1311 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1403 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1418 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1700 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SearchManager.ts:1745 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/PaginationHelper.ts:54 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/http/BaseRouteHandler.ts:28 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/http/routes/SettingsRoutes.ts:76 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/http/routes/SessionRoutes.ts:165 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SessionManager.ts:208 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/worker/SessionManager.ts:256 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/domain/ModeManager.ts:146 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/domain/ModeManager.ts:163 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/domain/ModeManager.ts:173 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/context-generator.ts:202 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
📁 src/services/context-generator.ts:226 - GENERIC_CATCH
|
||||
Catch block handles all errors identically - no error type discrimination.
|
||||
|
||||
═══════════════════════════════════════════════════════════════
|
||||
REMINDER: Every try-catch must answer these questions:
|
||||
1. What SPECIFIC error am I catching? (Name it)
|
||||
2. Show me documentation proving this error can occur
|
||||
3. Why can't this error be prevented?
|
||||
4. What will the catch block DO? (Log + rethrow? Fallback?)
|
||||
5. Why shouldn't this error propagate to the caller?
|
||||
|
||||
To approve an anti-pattern, add: // [APPROVED OVERRIDE]: reason
|
||||
═══════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
❌ FAILED: 26 critical error handling anti-patterns must be fixed.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "9.0.2",
|
||||
"version": "9.0.3",
|
||||
"description": "Memory compression system for Claude Code - persist context across sessions",
|
||||
"keywords": [
|
||||
"claude",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem",
|
||||
"version": "9.0.2",
|
||||
"version": "9.0.3",
|
||||
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
|
||||
"author": {
|
||||
"name": "Alex Newman"
|
||||
|
||||
@@ -28,4 +28,10 @@
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #32309 | 3:09 PM | 🔵 | Claude-mem hooks system configuration structure | ~435 |
|
||||
|
||||
### Jan 9, 2026
|
||||
|
||||
| ID | Time | T | Title | Read |
|
||||
|----|------|---|-------|------|
|
||||
| #38802 | 5:11 PM | 🔵 | Claude-Mem Hook Configuration Architecture | ~450 |
|
||||
</claude-mem-context>
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "claude-mem-plugin",
|
||||
"version": "9.0.1",
|
||||
"version": "9.0.2",
|
||||
"private": true,
|
||||
"description": "Runtime dependencies for claude-mem bundled hooks",
|
||||
"type": "module",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -77,6 +77,30 @@ import { LogsRoutes } from './worker/http/routes/LogsRoutes.js';
|
||||
// Re-export updateCursorContextForProject for SDK agents
|
||||
export { updateCursorContextForProject };
|
||||
|
||||
/**
|
||||
* Build JSON status output for hook framework communication.
|
||||
* This is a pure function extracted for testability.
|
||||
*
|
||||
* @param status - 'ready' for successful startup, 'error' for failures
|
||||
* @param message - Optional error message (only included when provided)
|
||||
* @returns JSON object with continue, suppressOutput, status, and optionally message
|
||||
*/
|
||||
export interface StatusOutput {
|
||||
continue: true;
|
||||
suppressOutput: true;
|
||||
status: 'ready' | 'error';
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export function buildStatusOutput(status: 'ready' | 'error', message?: string): StatusOutput {
|
||||
return {
|
||||
continue: true,
|
||||
suppressOutput: true,
|
||||
status,
|
||||
...(message && { message })
|
||||
};
|
||||
}
|
||||
|
||||
export class WorkerService {
|
||||
private server: Server;
|
||||
private startTime: number = Date.now();
|
||||
@@ -622,6 +646,14 @@ async function main() {
|
||||
const command = process.argv[2];
|
||||
const port = getWorkerPort();
|
||||
|
||||
// Helper for JSON status output in 'start' command
|
||||
// Exit code 0 ensures Windows Terminal doesn't keep tabs open
|
||||
function exitWithStatus(status: 'ready' | 'error', message?: string): never {
|
||||
const output = buildStatusOutput(status, message);
|
||||
console.log(JSON.stringify(output));
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
case 'start': {
|
||||
if (await waitForHealth(port, 1000)) {
|
||||
@@ -636,14 +668,12 @@ async function main() {
|
||||
const freed = await waitForPortFree(port, getPlatformTimeout(15000));
|
||||
if (!freed) {
|
||||
logger.error('SYSTEM', 'Port did not free up after shutdown for version mismatch restart', { port });
|
||||
// Exit gracefully: Windows Terminal won't keep tab open on exit 0
|
||||
// The wrapper/plugin will handle restart logic if needed
|
||||
process.exit(0);
|
||||
exitWithStatus('error', 'Port did not free after version mismatch restart');
|
||||
}
|
||||
removePidFile();
|
||||
} else {
|
||||
logger.info('SYSTEM', 'Worker already running and healthy');
|
||||
process.exit(0);
|
||||
exitWithStatus('ready');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,21 +683,17 @@ async function main() {
|
||||
const healthy = await waitForHealth(port, getPlatformTimeout(15000));
|
||||
if (healthy) {
|
||||
logger.info('SYSTEM', 'Worker is now healthy');
|
||||
process.exit(0);
|
||||
exitWithStatus('ready');
|
||||
}
|
||||
logger.error('SYSTEM', 'Port in use but worker not responding to health checks');
|
||||
// Exit gracefully: Windows Terminal won't keep tab open on exit 0
|
||||
// The wrapper/plugin will handle restart logic if needed
|
||||
process.exit(0);
|
||||
exitWithStatus('error', 'Port in use but worker not responding');
|
||||
}
|
||||
|
||||
logger.info('SYSTEM', 'Starting worker daemon');
|
||||
const pid = spawnDaemon(__filename, port);
|
||||
if (pid === undefined) {
|
||||
logger.error('SYSTEM', 'Failed to spawn worker daemon');
|
||||
// Exit gracefully: Windows Terminal won't keep tab open on exit 0
|
||||
// The wrapper/plugin will handle restart logic if needed
|
||||
process.exit(0);
|
||||
exitWithStatus('error', 'Failed to spawn worker daemon');
|
||||
}
|
||||
|
||||
writePidFile({ pid, port, startedAt: new Date().toISOString() });
|
||||
@@ -676,13 +702,11 @@ async function main() {
|
||||
if (!healthy) {
|
||||
removePidFile();
|
||||
logger.error('SYSTEM', 'Worker failed to start (health check timeout)');
|
||||
// Exit gracefully: Windows Terminal won't keep tab open on exit 0
|
||||
// The wrapper/plugin will handle restart logic if needed
|
||||
process.exit(0);
|
||||
exitWithStatus('error', 'Worker failed to start (health check timeout)');
|
||||
}
|
||||
|
||||
logger.info('SYSTEM', 'Worker started successfully');
|
||||
process.exit(0);
|
||||
exitWithStatus('ready');
|
||||
}
|
||||
|
||||
case 'stop': {
|
||||
|
||||
@@ -0,0 +1,446 @@
|
||||
/**
|
||||
* Tests for worker JSON status output structure
|
||||
*
|
||||
* Tests the buildStatusOutput pure function extracted from worker-service.ts
|
||||
* to ensure JSON output matches the hook framework contract.
|
||||
*
|
||||
* Also tests CLI output capture for the 'start' command to verify
|
||||
* actual JSON output matches expected structure.
|
||||
*
|
||||
* No mocks needed - tests a pure function directly and captures real CLI output.
|
||||
*/
|
||||
import { describe, it, expect } from 'bun:test';
|
||||
import { spawnSync } from 'child_process';
|
||||
import { existsSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { buildStatusOutput, StatusOutput } from '../../src/services/worker-service.js';
|
||||
|
||||
const WORKER_SCRIPT = path.join(__dirname, '../../plugin/scripts/worker-service.cjs');
|
||||
|
||||
/**
|
||||
* Run worker CLI command and return stdout + exit code
|
||||
* Uses spawnSync for synchronous output capture
|
||||
*/
|
||||
function runWorkerStart(): { stdout: string; exitCode: number } {
|
||||
const result = spawnSync('bun', [WORKER_SCRIPT, 'start'], {
|
||||
encoding: 'utf-8',
|
||||
timeout: 60000
|
||||
});
|
||||
return { stdout: result.stdout?.trim() || '', exitCode: result.status || 0 };
|
||||
}
|
||||
|
||||
describe('worker-json-status', () => {
|
||||
describe('buildStatusOutput', () => {
|
||||
describe('ready status', () => {
|
||||
it('should return valid JSON with required fields for ready status', () => {
|
||||
const result = buildStatusOutput('ready');
|
||||
|
||||
expect(result.status).toBe('ready');
|
||||
expect(result.continue).toBe(true);
|
||||
expect(result.suppressOutput).toBe(true);
|
||||
});
|
||||
|
||||
it('should not include message field when not provided', () => {
|
||||
const result = buildStatusOutput('ready');
|
||||
|
||||
expect(result.message).toBeUndefined();
|
||||
expect('message' in result).toBe(false);
|
||||
});
|
||||
|
||||
it('should include message field when explicitly provided for ready status', () => {
|
||||
const result = buildStatusOutput('ready', 'Worker started successfully');
|
||||
|
||||
expect(result.status).toBe('ready');
|
||||
expect(result.message).toBe('Worker started successfully');
|
||||
});
|
||||
});
|
||||
|
||||
describe('error status', () => {
|
||||
it('should return valid JSON with required fields for error status', () => {
|
||||
const result = buildStatusOutput('error');
|
||||
|
||||
expect(result.status).toBe('error');
|
||||
expect(result.continue).toBe(true);
|
||||
expect(result.suppressOutput).toBe(true);
|
||||
});
|
||||
|
||||
it('should include message field when provided for error status', () => {
|
||||
const result = buildStatusOutput('error', 'Port in use but worker not responding');
|
||||
|
||||
expect(result.status).toBe('error');
|
||||
expect(result.message).toBe('Port in use but worker not responding');
|
||||
});
|
||||
|
||||
it('should handle various error messages correctly', () => {
|
||||
const errorMessages = [
|
||||
'Port did not free after version mismatch restart',
|
||||
'Failed to spawn worker daemon',
|
||||
'Worker failed to start (health check timeout)'
|
||||
];
|
||||
|
||||
for (const msg of errorMessages) {
|
||||
const result = buildStatusOutput('error', msg);
|
||||
expect(result.message).toBe(msg);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('required fields always present', () => {
|
||||
it('should always include continue: true', () => {
|
||||
expect(buildStatusOutput('ready').continue).toBe(true);
|
||||
expect(buildStatusOutput('error').continue).toBe(true);
|
||||
expect(buildStatusOutput('ready', 'msg').continue).toBe(true);
|
||||
expect(buildStatusOutput('error', 'msg').continue).toBe(true);
|
||||
});
|
||||
|
||||
it('should always include suppressOutput: true', () => {
|
||||
expect(buildStatusOutput('ready').suppressOutput).toBe(true);
|
||||
expect(buildStatusOutput('error').suppressOutput).toBe(true);
|
||||
expect(buildStatusOutput('ready', 'msg').suppressOutput).toBe(true);
|
||||
expect(buildStatusOutput('error', 'msg').suppressOutput).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('JSON serialization', () => {
|
||||
it('should produce valid JSON when stringified', () => {
|
||||
const readyResult = buildStatusOutput('ready');
|
||||
const errorResult = buildStatusOutput('error', 'Test error message');
|
||||
|
||||
expect(() => JSON.stringify(readyResult)).not.toThrow();
|
||||
expect(() => JSON.stringify(errorResult)).not.toThrow();
|
||||
|
||||
const parsedReady = JSON.parse(JSON.stringify(readyResult));
|
||||
expect(parsedReady.status).toBe('ready');
|
||||
expect(parsedReady.continue).toBe(true);
|
||||
|
||||
const parsedError = JSON.parse(JSON.stringify(errorResult));
|
||||
expect(parsedError.status).toBe('error');
|
||||
expect(parsedError.message).toBe('Test error message');
|
||||
});
|
||||
|
||||
it('should match expected JSON structure for hook framework', () => {
|
||||
const readyOutput = JSON.stringify(buildStatusOutput('ready'));
|
||||
const errorOutput = JSON.stringify(buildStatusOutput('error', 'error msg'));
|
||||
|
||||
// Verify exact structure (order may vary, but content must match)
|
||||
const parsedReady = JSON.parse(readyOutput);
|
||||
expect(parsedReady).toEqual({
|
||||
continue: true,
|
||||
suppressOutput: true,
|
||||
status: 'ready'
|
||||
});
|
||||
|
||||
const parsedError = JSON.parse(errorOutput);
|
||||
expect(parsedError).toEqual({
|
||||
continue: true,
|
||||
suppressOutput: true,
|
||||
status: 'error',
|
||||
message: 'error msg'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('type safety', () => {
|
||||
it('should only accept valid status values', () => {
|
||||
// TypeScript ensures these are the only valid values at compile time
|
||||
// This runtime test validates the behavior
|
||||
const readyResult: StatusOutput = buildStatusOutput('ready');
|
||||
const errorResult: StatusOutput = buildStatusOutput('error');
|
||||
|
||||
expect(['ready', 'error']).toContain(readyResult.status);
|
||||
expect(['ready', 'error']).toContain(errorResult.status);
|
||||
});
|
||||
|
||||
it('should have correct type structure', () => {
|
||||
const result = buildStatusOutput('ready');
|
||||
|
||||
// Verify literal types
|
||||
expect(result.continue).toBe(true as const);
|
||||
expect(result.suppressOutput).toBe(true as const);
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should handle empty string message', () => {
|
||||
// Empty string is falsy, so message should NOT be included
|
||||
const result = buildStatusOutput('error', '');
|
||||
expect('message' in result).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle message with special characters', () => {
|
||||
const specialMessage = 'Error: "quoted" & special <chars>';
|
||||
const result = buildStatusOutput('error', specialMessage);
|
||||
expect(result.message).toBe(specialMessage);
|
||||
|
||||
// Verify it serializes correctly
|
||||
const parsed = JSON.parse(JSON.stringify(result));
|
||||
expect(parsed.message).toBe(specialMessage);
|
||||
});
|
||||
|
||||
it('should handle very long message', () => {
|
||||
const longMessage = 'A'.repeat(10000);
|
||||
const result = buildStatusOutput('error', longMessage);
|
||||
expect(result.message).toBe(longMessage);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('start command JSON output', () => {
|
||||
describe('when worker already healthy', () => {
|
||||
it('should output valid JSON with status: ready', () => {
|
||||
// Skip if worker script doesn't exist (not built)
|
||||
if (!existsSync(WORKER_SCRIPT)) {
|
||||
console.log('Skipping CLI test - worker script not built');
|
||||
return;
|
||||
}
|
||||
|
||||
const { stdout, exitCode } = runWorkerStart();
|
||||
|
||||
// The start command always exits with 0 (Windows Terminal compatibility)
|
||||
expect(exitCode).toBe(0);
|
||||
|
||||
// Should output valid JSON
|
||||
expect(() => JSON.parse(stdout)).not.toThrow();
|
||||
|
||||
const parsed = JSON.parse(stdout);
|
||||
|
||||
// Verify required fields per hook framework contract
|
||||
expect(parsed.continue).toBe(true);
|
||||
expect(parsed.suppressOutput).toBe(true);
|
||||
expect(['ready', 'error']).toContain(parsed.status);
|
||||
});
|
||||
|
||||
it('should match expected JSON structure when worker is healthy', () => {
|
||||
if (!existsSync(WORKER_SCRIPT)) {
|
||||
console.log('Skipping CLI test - worker script not built');
|
||||
return;
|
||||
}
|
||||
|
||||
const { stdout } = runWorkerStart();
|
||||
const parsed = JSON.parse(stdout);
|
||||
|
||||
// When worker is already healthy, status should be 'ready'
|
||||
// (or 'error' if something unexpected happens)
|
||||
if (parsed.status === 'ready') {
|
||||
// Ready status should not include message unless explicitly set
|
||||
expect(parsed.continue).toBe(true);
|
||||
expect(parsed.suppressOutput).toBe(true);
|
||||
} else if (parsed.status === 'error') {
|
||||
// Error status may include a message explaining the failure
|
||||
expect(typeof parsed.message).toBe('string');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('error scenarios', () => {
|
||||
// These tests require complex setup (mocking ports, killing processes)
|
||||
// Skipped for now - the pure function tests above cover the JSON structure
|
||||
it.skip('should output JSON with status: error when port in use but not responding', () => {
|
||||
// Would require: start a non-worker server on the port, then call start
|
||||
});
|
||||
|
||||
it.skip('should output JSON with status: error on spawn failure', () => {
|
||||
// Would require: mock spawnDaemon to fail
|
||||
});
|
||||
|
||||
it.skip('should output JSON with status: error on health check timeout', () => {
|
||||
// Would require: start worker that never becomes healthy
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Claude Code hook framework compatibility tests
|
||||
*
|
||||
* These tests verify that the worker 'start' command output conforms to
|
||||
* Claude Code's hook output contract. Key requirements:
|
||||
*
|
||||
* 1. Exit code 0 - Required for Windows Terminal compatibility (prevents
|
||||
* tab accumulation from spawned processes)
|
||||
*
|
||||
* 2. JSON on stdout - Claude Code parses stdout as JSON. Logs must go to
|
||||
* stderr to avoid breaking JSON parsing.
|
||||
*
|
||||
* 3. `continue: true` - CRITICAL: This field tells Claude Code to continue
|
||||
* processing. If missing or false, Claude Code stops after the hook.
|
||||
* Per docs: "If continue is false, Claude stops processing after the
|
||||
* hooks run."
|
||||
*
|
||||
* 4. `suppressOutput: true` - Hides output from transcript mode (Ctrl-R).
|
||||
* Optional but recommended for non-user-facing status.
|
||||
*
|
||||
* Reference: private/context/claude-code/hooks.md
|
||||
*/
|
||||
describe('Claude Code hook framework compatibility', () => {
|
||||
/**
|
||||
* Windows Terminal compatibility requirement
|
||||
*
|
||||
* When hooks run in Windows Terminal, each spawned process can open a
|
||||
* new tab. Exit code 0 tells the terminal the process completed
|
||||
* successfully and prevents tab accumulation.
|
||||
*
|
||||
* Even for error states (worker failed to start), we exit 0 and
|
||||
* communicate the error via JSON { status: 'error', message: '...' }
|
||||
*/
|
||||
it('should always exit with code 0', () => {
|
||||
if (!existsSync(WORKER_SCRIPT)) {
|
||||
console.log('Skipping CLI test - worker script not built');
|
||||
return;
|
||||
}
|
||||
|
||||
const { exitCode } = runWorkerStart();
|
||||
|
||||
// Per Windows Terminal compatibility requirement, exit code is always 0
|
||||
// Error states are communicated via JSON status field, not exit codes
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
/**
|
||||
* JSON must go to stdout, not stderr
|
||||
*
|
||||
* Claude Code parses stdout as JSON for hook output. Any non-JSON on
|
||||
* stdout breaks parsing. Logs, warnings, and debug info must go to
|
||||
* stderr.
|
||||
*
|
||||
* Structure: { status, continue, suppressOutput, message? }
|
||||
*/
|
||||
it('should output JSON on stdout (not stderr)', () => {
|
||||
if (!existsSync(WORKER_SCRIPT)) {
|
||||
console.log('Skipping CLI test - worker script not built');
|
||||
return;
|
||||
}
|
||||
|
||||
const result = spawnSync('bun', [WORKER_SCRIPT, 'start'], {
|
||||
encoding: 'utf-8',
|
||||
timeout: 60000
|
||||
});
|
||||
|
||||
const stdout = result.stdout?.trim() || '';
|
||||
const stderr = result.stderr?.trim() || '';
|
||||
|
||||
// stdout should contain valid JSON
|
||||
expect(() => JSON.parse(stdout)).not.toThrow();
|
||||
|
||||
// stderr should NOT contain the JSON output (it may have logs)
|
||||
// The JSON structure should only appear in stdout
|
||||
const parsed = JSON.parse(stdout);
|
||||
expect(parsed).toHaveProperty('status');
|
||||
expect(parsed).toHaveProperty('continue');
|
||||
|
||||
// Verify stderr doesn't accidentally contain the JSON output
|
||||
if (stderr) {
|
||||
try {
|
||||
const stderrParsed = JSON.parse(stderr);
|
||||
// If stderr parses as JSON with our structure, that's wrong
|
||||
expect(stderrParsed).not.toHaveProperty('suppressOutput');
|
||||
} catch {
|
||||
// stderr is not JSON, which is expected (logs, etc.)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* JSON must be parseable as valid JSON
|
||||
*
|
||||
* This seems obvious but is critical - any extraneous output (console.log
|
||||
* statements, warnings, etc.) will break JSON parsing and cause Claude
|
||||
* Code to fail processing the hook output.
|
||||
*/
|
||||
it('should be parseable as valid JSON', () => {
|
||||
if (!existsSync(WORKER_SCRIPT)) {
|
||||
console.log('Skipping CLI test - worker script not built');
|
||||
return;
|
||||
}
|
||||
|
||||
const { stdout } = runWorkerStart();
|
||||
|
||||
// Should not throw on parse
|
||||
let parsed: unknown;
|
||||
expect(() => {
|
||||
parsed = JSON.parse(stdout);
|
||||
}).not.toThrow();
|
||||
|
||||
// Should be an object, not a string, array, etc.
|
||||
expect(typeof parsed).toBe('object');
|
||||
expect(parsed).not.toBeNull();
|
||||
expect(Array.isArray(parsed)).toBe(false);
|
||||
});
|
||||
|
||||
/**
|
||||
* `continue: true` is CRITICAL
|
||||
*
|
||||
* From Claude Code docs: "If continue is false, Claude stops processing
|
||||
* after the hooks run."
|
||||
*
|
||||
* For SessionStart hooks (which start the worker), we MUST return
|
||||
* continue: true so Claude Code continues to process the user's prompt.
|
||||
* If we returned continue: false, Claude would stop immediately after
|
||||
* starting the worker and never respond to the user.
|
||||
*
|
||||
* This is why continue: true is a required literal in our StatusOutput
|
||||
* type - it can never be false.
|
||||
*/
|
||||
it('should always include continue: true (required for Claude Code to proceed)', () => {
|
||||
if (!existsSync(WORKER_SCRIPT)) {
|
||||
console.log('Skipping CLI test - worker script not built');
|
||||
return;
|
||||
}
|
||||
|
||||
const { stdout } = runWorkerStart();
|
||||
const parsed = JSON.parse(stdout);
|
||||
|
||||
// continue: true is CRITICAL - without it, Claude Code stops processing
|
||||
// This is not optional; it must always be true for our hooks
|
||||
expect(parsed.continue).toBe(true);
|
||||
|
||||
// Also verify it's the literal `true`, not a truthy value
|
||||
expect(parsed.continue).toStrictEqual(true);
|
||||
});
|
||||
|
||||
/**
|
||||
* suppressOutput hides from transcript mode
|
||||
*
|
||||
* When suppressOutput: true, the hook output doesn't appear in transcript
|
||||
* mode (Ctrl-R). This is useful for status messages that aren't relevant
|
||||
* to the user's conversation history.
|
||||
*
|
||||
* For the worker start command, we suppress output since "worker started"
|
||||
* is infrastructure noise, not conversation content.
|
||||
*/
|
||||
it('should include suppressOutput: true to hide from transcript mode', () => {
|
||||
if (!existsSync(WORKER_SCRIPT)) {
|
||||
console.log('Skipping CLI test - worker script not built');
|
||||
return;
|
||||
}
|
||||
|
||||
const { stdout } = runWorkerStart();
|
||||
const parsed = JSON.parse(stdout);
|
||||
|
||||
// suppressOutput prevents infrastructure noise from polluting transcript
|
||||
expect(parsed.suppressOutput).toBe(true);
|
||||
});
|
||||
|
||||
/**
|
||||
* status field communicates outcome
|
||||
*
|
||||
* The status field tells Claude Code (and debugging tools) whether the
|
||||
* hook succeeded. Valid values: 'ready' | 'error'
|
||||
*
|
||||
* Unlike exit codes (which are always 0), status can indicate failure.
|
||||
* This allows Claude Code to potentially take remedial action or log
|
||||
* the issue.
|
||||
*/
|
||||
it('should include a valid status field', () => {
|
||||
if (!existsSync(WORKER_SCRIPT)) {
|
||||
console.log('Skipping CLI test - worker script not built');
|
||||
return;
|
||||
}
|
||||
|
||||
const { stdout } = runWorkerStart();
|
||||
const parsed = JSON.parse(stdout);
|
||||
|
||||
expect(parsed).toHaveProperty('status');
|
||||
expect(['ready', 'error']).toContain(parsed.status);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user