Compare commits

..

6 Commits

Author SHA1 Message Date
Alex Newman 65fb8d1ed2 Release v7.1.15
Fix worker service 404 error on /api/context/inject during startup

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 14:37:42 -05:00
Copilot e7380adb2f Fix 404 error on /api/context/inject during worker startup (#310)
* Initial plan

* Fix worker service connection failed error by adding early context/inject route

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

* Add integration test for context inject early access

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

* Fix import path and improve test code style

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

* Add clarifying comment about intentional code duplication

Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>

* build: compile fix for /api/context/inject 404 error

Compiled worker service and MCP server with the initialization race condition fix.
Validation results: All tests passing, route available immediately on restart.

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

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

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: thedotmack <683968+thedotmack@users.noreply.github.com>
Co-authored-by: Alex Newman <thedotmack@gmail.com>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-14 14:33:13 -05:00
Alex Newman 245c85a580 chore: update CHANGELOG.md for v7.1.14 2025-12-13 23:40:11 -05:00
Alex Newman 2e60f6fc81 Bump version to 7.1.14
Complete release including all built plugin files and timezone-aware logging.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-13 23:39:16 -05:00
Alex Newman dffde51f55 refactor: improve logging functionality and format in worker-cli.js 2025-12-13 23:37:34 -05:00
Alex Newman bee0e635a1 chore: update CHANGELOG.md for v7.1.13 2025-12-13 23:34:41 -05:00
18 changed files with 417 additions and 178 deletions
+1 -1
View File
@@ -10,7 +10,7 @@
"plugins": [
{
"name": "claude-mem",
"version": "7.1.13",
"version": "7.1.15",
"source": "./plugin",
"description": "Persistent memory system for Claude Code - context compression across sessions"
}
+112
View File
@@ -4,6 +4,118 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [7.1.14] - 2025-12-14
## Enhanced Error Handling & Logging
This patch release improves error message quality and logging across the claude-mem system.
### Error Message Improvements
**Standardized Hook Error Handling**
- Created shared error handlers (`handleFetchError`, `handleWorkerError`) for consistent error messages
- Platform-aware restart instructions (macOS, Linux, Windows) with correct commands
- Migrated all hooks (context, new, save, summary) to use standardized handlers
- Enhanced error logging with actionable context before throwing restart instructions
**ChromaSync Error Standardization**
- Consistent client initialization checks across all methods
- Enhanced error messages with troubleshooting steps and restart instructions
- Better context about which operation failed
**Worker Service Improvements**
- Enhanced version endpoint error logging with status codes and response text
- Improved worker restart error messages with PM2 commands
- Better context in all worker-related error scenarios
### Bug Fixes
- **Issue #260**: Fixed `happy_path_error__with_fallback` misuse in save-hook causing false "Missing cwd" errors
- Removed unnecessary `happy_path_error` calls from SDKAgent that were masking real error messages
- Cleaned up migration logging to use `console.log` instead of `console.error` for non-error events
### Logging Improvements
**Timezone-Aware Timestamps**
- Worker logs now use local machine timezone instead of UTC
- Maintains same format (`YYYY-MM-DD HH:MM:SS.mmm`) but reflects local time
- Easier debugging and log correlation with system events
- Enhanced worker-cli logging output format
### Test Coverage
Added comprehensive test suites:
- `tests/error-handling/hook-error-logging.test.ts` - 12 tests for hook error handler behavior
- `tests/services/chroma-sync-errors.test.ts` - ChromaSync error message consistency
- `tests/integration/hook-execution-environments.test.ts` - Bun PATH resolution across shells
- `docs/context/TEST_AUDIT_2025-12-13.md` - Comprehensive audit report
### Files Changed
27 files changed: 1,435 additions, 200 deletions
**What's Changed**
* Standardize and enhance error handling across hooks and worker service by @thedotmack in #295
* Timezone-aware logging for worker service and CLI
* Complete build with all plugin files included
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.12...v7.1.14
## [7.1.13] - 2025-12-14
## Enhanced Error Handling & Logging
This patch release improves error message quality and logging across the claude-mem system.
### Error Message Improvements
**Standardized Hook Error Handling**
- Created shared error handlers (`handleFetchError`, `handleWorkerError`) for consistent error messages
- Platform-aware restart instructions (macOS, Linux, Windows) with correct commands
- Migrated all hooks (context, new, save, summary) to use standardized handlers
- Enhanced error logging with actionable context before throwing restart instructions
**ChromaSync Error Standardization**
- Consistent client initialization checks across all methods
- Enhanced error messages with troubleshooting steps and restart instructions
- Better context about which operation failed
**Worker Service Improvements**
- Enhanced version endpoint error logging with status codes and response text
- Improved worker restart error messages with PM2 commands
- Better context in all worker-related error scenarios
### Bug Fixes
- **Issue #260**: Fixed `happy_path_error__with_fallback` misuse in save-hook causing false "Missing cwd" errors
- Removed unnecessary `happy_path_error` calls from SDKAgent that were masking real error messages
- Cleaned up migration logging to use `console.log` instead of `console.error` for non-error events
### Logging Improvements
**Timezone-Aware Timestamps**
- Worker logs now use local machine timezone instead of UTC
- Maintains same format (`YYYY-MM-DD HH:MM:SS.mmm`) but reflects local time
- Easier debugging and log correlation with system events
### Test Coverage
Added comprehensive test suites:
- `tests/error-handling/hook-error-logging.test.ts` - 12 tests for hook error handler behavior
- `tests/services/chroma-sync-errors.test.ts` - ChromaSync error message consistency
- `tests/integration/hook-execution-environments.test.ts` - Bun PATH resolution across shells
- `docs/context/TEST_AUDIT_2025-12-13.md` - Comprehensive audit report
### Files Changed
27 files changed: 1,435 additions, 200 deletions
**What's Changed**
* Standardize and enhance error handling across hooks and worker service by @thedotmack in #295
* Timezone-aware logging for worker service
**Full Changelog**: https://github.com/thedotmack/claude-mem/compare/v7.1.12...v7.1.13
## [7.1.12] - 2025-12-14
## What's Fixed
+1 -1
View File
@@ -6,7 +6,7 @@
Claude-mem is a Claude Code plugin providing persistent memory across sessions. It captures tool usage, compresses observations using the Claude Agent SDK, and injects relevant context into future sessions.
**Current Version**: 7.1.13
**Current Version**: 7.1.15
## Architecture
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem",
"version": "7.1.13",
"version": "7.1.15",
"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": "7.1.13",
"version": "7.1.15",
"description": "Persistent memory system for Claude Code - seamlessly preserve context across sessions",
"author": {
"name": "Alex Newman"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "claude-mem-plugin",
"version": "7.1.13",
"version": "7.1.15",
"private": true,
"description": "Runtime dependencies for claude-mem bundled hooks",
"type": "module",
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+100 -26
View File
@@ -60,9 +60,18 @@ export class WorkerService {
private searchRoutes: SearchRoutes | null;
private settingsRoutes: SettingsRoutes;
// Initialization tracking
private initializationComplete: Promise<void>;
private resolveInitialization!: () => void;
constructor() {
this.app = express();
// Initialize the promise that will resolve when background initialization completes
this.initializationComplete = new Promise((resolve) => {
this.resolveInitialization = resolve;
});
// Initialize domain services
this.dbManager = new DatabaseManager();
this.sessionManager = new SessionManager(this.dbManager);
@@ -155,6 +164,60 @@ export class WorkerService {
this.dataRoutes.setupRoutes(this.app);
// searchRoutes is set up after database initialization in initializeBackground()
this.settingsRoutes.setupRoutes(this.app);
// Register early handler for /api/context/inject to avoid 404 during startup
// This handler waits for initialization to complete before delegating to SearchRoutes
// NOTE: This duplicates logic from SearchRoutes.handleContextInject by design,
// as we need the route available immediately before SearchRoutes is initialized
this.app.get('/api/context/inject', async (req, res, next) => {
try {
// Wait for initialization to complete (with timeout)
const timeoutMs = 30000; // 30 second timeout
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Initialization timeout')), timeoutMs)
);
await Promise.race([this.initializationComplete, timeoutPromise]);
// If searchRoutes is still null after initialization, something went wrong
if (!this.searchRoutes) {
res.status(503).json({ error: 'Search routes not initialized' });
return;
}
// Delegate to the proper handler by re-processing the request
// Since we're already in the middleware chain, we need to call the handler directly
const projectName = req.query.project as string;
const useColors = req.query.colors === 'true';
if (!projectName) {
res.status(400).json({ error: 'Project parameter is required' });
return;
}
// Import context generator (runs in worker, has access to database)
const { generateContext } = await import('./context-generator.js');
// Use project name as CWD (generateContext uses path.basename to get project)
const cwd = `/context/${projectName}`;
// Generate context
const contextText = await generateContext(
{
session_id: 'context-inject-' + Date.now(),
cwd: cwd
},
useColors
);
// Return as plain text
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.send(contextText);
} catch (error) {
logger.error('WORKER', 'Context inject handler failed', {}, error as Error);
res.status(500).json({ error: error instanceof Error ? error.message : 'Internal server error' });
}
});
}
@@ -228,36 +291,47 @@ export class WorkerService {
* Background initialization - runs after HTTP server is listening
*/
private async initializeBackground(): Promise<void> {
// Clean up any orphaned chroma-mcp processes BEFORE starting our own
await this.cleanupOrphanedProcesses();
try {
// Clean up any orphaned chroma-mcp processes BEFORE starting our own
await this.cleanupOrphanedProcesses();
// Initialize database (once, stays open)
await this.dbManager.initialize();
// Initialize database (once, stays open)
await this.dbManager.initialize();
// Initialize search services (requires initialized database)
const formattingService = new FormattingService();
const timelineService = new TimelineService();
const searchManager = new SearchManager(
this.dbManager.getSessionSearch(),
this.dbManager.getSessionStore(),
this.dbManager.getChromaSync(),
formattingService,
timelineService
);
this.searchRoutes = new SearchRoutes(searchManager);
this.searchRoutes.setupRoutes(this.app); // Setup search routes now that SearchManager is ready
logger.info('WORKER', 'SearchManager initialized and search routes registered');
// Initialize search services (requires initialized database)
const formattingService = new FormattingService();
const timelineService = new TimelineService();
const searchManager = new SearchManager(
this.dbManager.getSessionSearch(),
this.dbManager.getSessionStore(),
this.dbManager.getChromaSync(),
formattingService,
timelineService
);
this.searchRoutes = new SearchRoutes(searchManager);
this.searchRoutes.setupRoutes(this.app); // Setup search routes now that SearchManager is ready
logger.info('WORKER', 'SearchManager initialized and search routes registered');
// Connect to MCP server
const mcpServerPath = path.join(__dirname, 'mcp-server.cjs');
const transport = new StdioClientTransport({
command: 'node',
args: [mcpServerPath],
env: process.env
});
// Connect to MCP server
const mcpServerPath = path.join(__dirname, 'mcp-server.cjs');
const transport = new StdioClientTransport({
command: 'node',
args: [mcpServerPath],
env: process.env
});
await this.mcpClient.connect(transport);
logger.success('WORKER', 'Connected to MCP server');
await this.mcpClient.connect(transport);
logger.success('WORKER', 'Connected to MCP server');
// Signal that initialization is complete
this.resolveInitialization();
logger.info('SYSTEM', 'Background initialization complete');
} catch (error) {
logger.error('SYSTEM', 'Background initialization failed', {}, error as Error);
// Still resolve to prevent hanging requests, but they'll see searchRoutes is null
this.resolveInitialization();
throw error;
}
}
/**
@@ -0,0 +1,53 @@
/**
* Integration Test: Context Inject Early Access
*
* Tests that /api/context/inject endpoint is available immediately
* when worker starts, even before background initialization completes.
*
* This prevents the 404 error described in the issue where the hook
* tries to access the endpoint before SearchRoutes are registered.
*/
import { describe, it, expect } from 'vitest';
import fs from 'fs';
import path from 'path';
describe('Context Inject Early Access', () => {
const workerPath = path.join(__dirname, '../../plugin/scripts/worker-service.cjs');
it('should have /api/context/inject route available immediately on startup', async () => {
// This test verifies the fix by checking that:
// 1. The route exists immediately (no 404)
// 2. The route waits for initialization before processing
// 3. Requests don't fail with "Cannot GET /api/context/inject"
// The fix adds an early handler that:
// - Registers the route in setupRoutes() (called during construction)
// - Waits for initializationComplete promise
// - Processes the request after initialization
// Since we can't easily spin up a full worker in tests,
// we verify the code structure is correct by checking
// the compiled output contains the necessary pieces
const workerCode = fs.readFileSync(workerPath, 'utf-8');
// Verify initialization promise exists
expect(workerCode).toContain('initializationComplete');
expect(workerCode).toContain('resolveInitialization');
// Verify early route handler is registered in setupRoutes
expect(workerCode).toContain('/api/context/inject');
expect(workerCode).toContain('Promise.race');
// Verify the promise is resolved after initialization
expect(workerCode).toContain('this.resolveInitialization()');
});
it('should handle timeout if initialization takes too long', () => {
const workerCode = fs.readFileSync(workerPath, 'utf-8');
// Verify timeout protection (30 seconds)
expect(workerCode).toContain('3e4'); // 30000 in scientific notation
expect(workerCode).toContain('Initialization timeout');
});
});