Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions plugin/hooks/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
{
"type": "command",
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" start",
"timeout": 15
"timeout": 45
},
{
"type": "command",
Expand All @@ -34,7 +34,7 @@
{
"type": "command",
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" start",
"timeout": 15
"timeout": 45
},
{
"type": "command",
Expand All @@ -51,7 +51,7 @@
{
"type": "command",
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" start",
"timeout": 15
"timeout": 45
},
{
"type": "command",
Expand All @@ -67,7 +67,7 @@
{
"type": "command",
"command": "bun \"${CLAUDE_PLUGIN_ROOT}/scripts/worker-service.cjs\" start",
"timeout": 15
"timeout": 45
},
{
"type": "command",
Expand Down
37 changes: 32 additions & 5 deletions src/services/worker-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,8 @@ async function waitForHealth(port: number, timeoutMs: number = 30000): Promise<b
while (Date.now() - start < timeoutMs) {
try {
// Note: Removed AbortSignal.timeout to avoid Windows Bun cleanup issue (libuv assertion)
const response = await fetch(`http://127.0.0.1:${port}/api/readiness`);
// Uses /api/health (not /api/readiness) - start command just needs to know server is listening
const response = await fetch(`http://127.0.0.1:${port}/api/health`);
if (response.ok) return true;
} catch {
// Not ready yet
Expand Down Expand Up @@ -263,7 +264,11 @@ export class WorkerService {
private startTime: number = Date.now();
private mcpClient: Client;

// Initialization flags for MCP/SDK readiness tracking
// Initialization flags for staged readiness tracking
// coreReady: Database and SearchManager initialized - hooks can work
// mcpReady: MCP server connected - Claude's memory tools can work
// initializationCompleteFlag: Everything done including background tasks
private coreReady: boolean = false;
private mcpReady: boolean = false;
private initializationCompleteFlag: boolean = false;
private isShuttingDown: boolean = false;
Expand Down Expand Up @@ -387,13 +392,30 @@ export class WorkerService {
hasIpc: typeof process.send === 'function',
platform: process.platform,
pid: process.pid,
initialized: this.initializationCompleteFlag,
coreReady: this.coreReady,
mcpReady: this.mcpReady,
initialized: this.initializationCompleteFlag,
});
});

// Readiness check endpoint - returns 503 until full initialization completes
// Used by ProcessManager and worker-utils to ensure worker is fully ready before routing requests
// Core readiness check - returns 200 when database and SearchManager are initialized
// Used by hooks which only need core functionality (no MCP dependency)
this.app.get('/api/core-ready', (_req, res) => {
if (this.coreReady) {
res.status(200).json({
status: 'ready',
mcpReady: this.mcpReady,
});
} else {
res.status(503).json({
status: 'initializing',
message: 'Core services still initializing, please retry',
});
}
});

// Full readiness check endpoint - returns 503 until full initialization completes (including MCP)
// Used for diagnostics and anything that requires MCP to be connected
this.app.get('/api/readiness', (_req, res) => {
if (this.initializationCompleteFlag) {
res.status(200).json({
Expand Down Expand Up @@ -687,6 +709,11 @@ export class WorkerService {
this.searchRoutes.setupRoutes(this.app); // Setup search routes now that SearchManager is ready
logger.info('WORKER', 'SearchManager initialized and search routes registered');

// Core services are ready - hooks can now work (database + SearchManager)
// MCP connection happens next but hooks don't need it
this.coreReady = true;
logger.info('SYSTEM', 'Core services ready (hooks can now proceed)');

// Connect to MCP server with timeout guard
const mcpServerPath = path.join(__dirname, 'mcp-server.cjs');
const transport = new StdioClientTransport({
Expand Down
7 changes: 4 additions & 3 deletions src/shared/worker-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,14 @@ export function clearPortCache(): void {
}

/**
* Check if worker is responsive and fully initialized by trying the readiness endpoint
* Changed from /health to /api/readiness to ensure MCP initialization is complete
* Check if worker core services are ready (database + SearchManager)
* Uses /api/core-ready - hooks don't need MCP, only core services
* Full readiness (including MCP) is checked via /api/readiness for diagnostics
*/
async function isWorkerHealthy(): Promise<boolean> {
const port = getWorkerPort();
// Note: Removed AbortSignal.timeout to avoid Windows Bun cleanup issue (libuv assertion)
const response = await fetch(`http://127.0.0.1:${port}/api/readiness`);
const response = await fetch(`http://127.0.0.1:${port}/api/core-ready`);
return response.ok;
}

Expand Down