Compare commits

...

5 Commits

Author SHA1 Message Date
Alex Newman e91868a7f6 chore: bump version to 9.0.3 2026-01-10 03:13:18 -05:00
Alex Newman 644cccd3e1 fix(worker): add JSON status output for hook framework (#655)
* fix(worker): add JSON status output for hook framework (#638)

Adds JSON output before all process.exit() calls in the start command
so Claude Code's hook framework can track worker startup progress.

- Add exitWithStatus() helper function
- Output {"continue":true,"suppressOutput":true,"status":"ready"|"error"}
- Maintains exit code 0 for Windows Terminal compatibility

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

* test(worker): add unit tests for buildStatusOutput function

Extract buildStatusOutput() as pure function for testability and add
comprehensive unit tests validating JSON structure for hook framework.

Tests cover:
- Ready/error status variants
- Required fields (continue, suppressOutput, status)
- Optional message field inclusion logic
- Edge cases (empty strings, special characters, long messages)
- JSON serialization roundtrip

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

* test(worker): add CLI output capture tests for start command

Add integration tests that spawn actual worker-service start command
and verify JSON output matches hook framework contract.

Tests:
- Valid JSON with required fields (continue, suppressOutput, status)
- JSON structure matches expected format when worker healthy
- Skipped placeholders for error scenarios requiring complex setup

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

* test(worker): add Claude Code hook framework compatibility tests

Add contract tests documenting the hook framework requirements:
- Exit code 0 (Windows Terminal compatibility)
- JSON output on stdout (not stderr)
- Valid JSON structure with required fields
- Critical 'continue: true' for Claude Code to proceed
- 'suppressOutput: true' for transcript mode

These tests serve as living documentation of the hook output contract,
explaining both WHAT and WHY for each requirement.

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

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 03:11:42 -05:00
Alex Newman e6df88bf42 chore: remove error handling baseline file 2026-01-09 23:20:15 -05:00
Alex Newman ef823a89c1 build assets 2026-01-09 23:19:25 -05:00
Alex Newman b169e18de0 docs: update CHANGELOG.md for v9.0.2 2026-01-09 23:18:08 -05:00
10 changed files with 585 additions and 797 deletions
+1 -1
View File
@@ -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
View File
@@ -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)
-688
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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"
+6
View File
@@ -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
View File
@@ -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
+39 -15
View File
@@ -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);
});
});
});