2659ec3231
* Refactor CLAUDE.md and related files for December 2025 updates - Updated CLAUDE.md in src/services/worker with new entries for December 2025, including changes to Search.ts, GeminiAgent.ts, SDKAgent.ts, and SessionManager.ts. - Revised CLAUDE.md in src/shared to reflect updates and new entries for December 2025, including paths.ts and worker-utils.ts. - Modified hook-constants.ts to clarify exit codes and their behaviors. - Added comprehensive hooks reference documentation for Claude Code, detailing usage, events, and examples. - Created initial CLAUDE.md files in various directories to track recent activity. * fix: Merge user-message-hook output into context-hook hookSpecificOutput - Add footer message to additionalContext in context-hook.ts - Remove user-message-hook from SessionStart hooks array - Fixes issue where stderr+exit(1) approach was silently discarded Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Update logs and documentation for recent plugin and worker service changes - Added detailed logs for worker service activities from Dec 10, 2025 to Jan 7, 2026, including initialization patterns, cleanup confirmations, and diagnostic logging. - Updated plugin documentation with recent activities, including plugin synchronization and configuration changes from Dec 3, 2025 to Jan 7, 2026. - Enhanced the context hook and worker service logs to reflect improvements and fixes in the plugin architecture. - Documented the migration and verification processes for the Claude memory system and its integration with the marketplace. * Refactor hooks architecture and remove deprecated user-message-hook - Updated hook configurations in CLAUDE.md and hooks.json to reflect changes in session start behavior. - Removed user-message-hook functionality as it is no longer utilized in Claude Code 2.1.0; context is now injected silently. - Enhanced context-hook to handle session context injection without user-visible messages. - Cleaned up documentation across multiple files to align with the new hook structure and removed references to obsolete hooks. - Adjusted timing and command execution for hooks to improve performance and reliability. * fix: Address PR #610 review issues - Replace USER_MESSAGE_ONLY test with BLOCKING_ERROR test in hook-constants.test.ts - Standardize Claude Code 2.1.0 note wording across all three documentation files - Exclude deprecated user-message-hook.ts from logger-usage-standards test Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: Remove hardcoded fake token counts from context injection Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Address PR #610 review issues by fixing test files, standardizing documentation notes, and verifying code quality improvements. * fix: Add path validation to CLAUDE.md distribution to prevent invalid directory creation - Add isValidPathForClaudeMd() function to reject invalid paths: - Tilde paths (~) that Node.js doesn't expand - URLs (http://, https://) - Paths with spaces (likely command text or PR references) - Paths with # (GitHub issue/PR references) - Relative paths that escape project boundary - Integrate validation in updateFolderClaudeMdFiles loop - Add 6 unit tests for path validation - Update .gitignore to prevent accidental commit of malformed directories - Clean up existing invalid directories (~/, PR #610..., git diff..., https:) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix: Implement path validation in CLAUDE.md generation to prevent invalid directory creation - Added `isValidPathForClaudeMd()` function to validate file paths in `src/utils/claude-md-utils.ts`. - Integrated path validation in `updateFolderClaudeMdFiles` to skip invalid paths. - Added 6 new unit tests in `tests/utils/claude-md-utils.test.ts` to cover various rejection cases. - Updated `.gitignore` to prevent tracking of invalid directories. - Cleaned up existing invalid directories in the repository. * feat: Promote critical WARN logs to ERROR level across codebase Comprehensive log-level audit promoting 38+ WARN messages to ERROR for improved debugging and incident response: - Parser: observation type errors, data contamination - SDK/Agents: empty init responses (Gemini, OpenRouter) - Worker/Queue: session recovery, auto-recovery failures - Chroma: sync failures, search failures (now treated as critical) - SQLite: search failures (primary data store) - Session/Generator: failures, missing context - Infrastructure: shutdown, process management failures - File Operations: CLAUDE.md updates, config reads - Branch Management: recovery checkout failures Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * fix: Address PR #614 review issues - Remove incorrectly tracked tilde-prefixed files from git - Fix absolute path validation to check projectRoot boundaries - Add test coverage for absolute path validation edge cases Closes review issues: - Issue 1: ~/ prefixed files removed from tracking - Issue 3: Absolute paths now validated against projectRoot - Issue 4: Added 3 new test cases for absolute path scenarios Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * build assets and context --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
144 lines
5.0 KiB
TypeScript
144 lines
5.0 KiB
TypeScript
/**
|
|
* HealthMonitor - Port monitoring, health checks, and version checking
|
|
*
|
|
* Extracted from worker-service.ts monolith to provide centralized health monitoring.
|
|
* Handles:
|
|
* - Port availability checking
|
|
* - Worker health/readiness polling
|
|
* - Version mismatch detection (critical for plugin updates)
|
|
* - HTTP-based shutdown requests
|
|
*/
|
|
|
|
import path from 'path';
|
|
import { homedir } from 'os';
|
|
import { readFileSync } from 'fs';
|
|
import { logger } from '../../utils/logger.js';
|
|
|
|
/**
|
|
* Check if a port is in use by querying the health endpoint
|
|
*/
|
|
export async function isPortInUse(port: number): Promise<boolean> {
|
|
try {
|
|
// Note: Removed AbortSignal.timeout to avoid Windows Bun cleanup issue (libuv assertion)
|
|
const response = await fetch(`http://127.0.0.1:${port}/api/health`);
|
|
return response.ok;
|
|
} catch (error) {
|
|
// [ANTI-PATTERN IGNORED]: Health check polls every 500ms, logging would flood
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for the worker to become fully ready (passes readiness check)
|
|
* @param port Worker port to check
|
|
* @param timeoutMs Maximum time to wait in milliseconds
|
|
* @returns true if worker became ready, false if timeout
|
|
*/
|
|
export async function waitForHealth(port: number, timeoutMs: number = 30000): Promise<boolean> {
|
|
const start = Date.now();
|
|
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`);
|
|
if (response.ok) return true;
|
|
} catch (error) {
|
|
// [ANTI-PATTERN IGNORED]: Retry loop - expected failures during startup, will retry
|
|
logger.debug('SYSTEM', 'Service not ready yet, will retry', { port }, error as Error);
|
|
}
|
|
await new Promise(r => setTimeout(r, 500));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Wait for a port to become free (no longer responding to health checks)
|
|
* Used after shutdown to confirm the port is available for restart
|
|
*/
|
|
export async function waitForPortFree(port: number, timeoutMs: number = 10000): Promise<boolean> {
|
|
const start = Date.now();
|
|
while (Date.now() - start < timeoutMs) {
|
|
if (!(await isPortInUse(port))) return true;
|
|
await new Promise(r => setTimeout(r, 500));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Send HTTP shutdown request to a running worker
|
|
* @param port Worker port
|
|
* @returns true if shutdown request was acknowledged, false otherwise
|
|
*/
|
|
export async function httpShutdown(port: number): Promise<boolean> {
|
|
try {
|
|
// Note: Removed AbortSignal.timeout to avoid Windows Bun cleanup issue (libuv assertion)
|
|
const response = await fetch(`http://127.0.0.1:${port}/api/admin/shutdown`, {
|
|
method: 'POST'
|
|
});
|
|
if (!response.ok) {
|
|
logger.warn('SYSTEM', 'Shutdown request returned error', { port, status: response.status });
|
|
return false;
|
|
}
|
|
return true;
|
|
} catch (error) {
|
|
// Connection refused is expected if worker already stopped
|
|
if (error instanceof Error && error.message?.includes('ECONNREFUSED')) {
|
|
logger.debug('SYSTEM', 'Worker already stopped', { port }, error);
|
|
return false;
|
|
}
|
|
// Unexpected error - log full details
|
|
logger.error('SYSTEM', 'Shutdown request failed unexpectedly', { port }, error as Error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the plugin version from the installed marketplace package.json
|
|
* This is the "expected" version that should be running
|
|
*/
|
|
export function getInstalledPluginVersion(): string {
|
|
const marketplaceRoot = path.join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
|
|
const packageJsonPath = path.join(marketplaceRoot, 'package.json');
|
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
return packageJson.version;
|
|
}
|
|
|
|
/**
|
|
* Get the running worker's version via API
|
|
* This is the "actual" version currently running
|
|
*/
|
|
export async function getRunningWorkerVersion(port: number): Promise<string | null> {
|
|
try {
|
|
const response = await fetch(`http://127.0.0.1:${port}/api/version`);
|
|
if (!response.ok) return null;
|
|
const data = await response.json() as { version: string };
|
|
return data.version;
|
|
} catch {
|
|
// Expected: worker not running or version endpoint unavailable
|
|
logger.debug('SYSTEM', 'Could not fetch worker version', { port });
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export interface VersionCheckResult {
|
|
matches: boolean;
|
|
pluginVersion: string;
|
|
workerVersion: string | null;
|
|
}
|
|
|
|
/**
|
|
* Check if worker version matches plugin version
|
|
* Critical for detecting when plugin is updated but worker is still running old code
|
|
* Returns true if versions match or if we can't determine (assume match for graceful degradation)
|
|
*/
|
|
export async function checkVersionMatch(port: number): Promise<VersionCheckResult> {
|
|
const pluginVersion = getInstalledPluginVersion();
|
|
const workerVersion = await getRunningWorkerVersion(port);
|
|
|
|
// If we can't get worker version, assume it matches (graceful degradation)
|
|
if (!workerVersion) {
|
|
return { matches: true, pluginVersion, workerVersion };
|
|
}
|
|
|
|
return { matches: pluginVersion === workerVersion, pluginVersion, workerVersion };
|
|
}
|