Skip to content
Open
26 changes: 13 additions & 13 deletions plugin/scripts/mcp-server.cjs

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion plugin/scripts/smart-install.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { execSync, spawnSync } from 'child_process';
import { join } from 'path';
import { homedir } from 'os';

const ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
const CLAUDE_CONFIG = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
const ROOT = join(CLAUDE_CONFIG, 'plugins', 'marketplaces', 'thedotmack');
const MARKER = join(ROOT, '.install-version');
const IS_WINDOWS = process.platform === 'win32';

Expand Down
414 changes: 211 additions & 203 deletions plugin/scripts/worker-service.cjs

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion scripts/smart-install.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import { execSync, spawnSync } from 'child_process';
import { join } from 'path';
import { homedir } from 'os';

const ROOT = join(homedir(), '.claude', 'plugins', 'marketplaces', 'thedotmack');
const CLAUDE_CONFIG = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude');
const ROOT = join(CLAUDE_CONFIG, 'plugins', 'marketplaces', 'thedotmack');
const MARKER = join(ROOT, '.install-version');
const IS_WINDOWS = process.platform === 'win32';

Expand Down
8 changes: 4 additions & 4 deletions src/cli/handlers/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';
import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js';
import { ensureWorkerRunning, getWorkerPort, fetchWithTimeout } from '../../shared/worker-utils.js';
import { getProjectContext } from '../../utils/project-name.js';
import { HOOK_EXIT_CODES } from '../../shared/hook-constants.js';
import { logger } from '../../utils/logger.js';
Expand Down Expand Up @@ -34,10 +34,10 @@ export const contextHandler: EventHandler = {
const projectsParam = context.allProjects.join(',');
const url = `http://127.0.0.1:${port}/api/context/inject?projects=${encodeURIComponent(projectsParam)}`;

// Note: Removed AbortSignal.timeout due to Windows Bun cleanup issue (libuv assertion)
// Worker service has its own timeouts, so client-side timeout is redundant
// Use fetchWithTimeout to prevent indefinite hangs (Issue #1079)
// 15s timeout: generous enough for context generation but prevents hook from blocking forever
try {
const response = await fetch(url);
const response = await fetchWithTimeout(url, {}, 15000);

if (!response.ok) {
// Log but don't throw — context fetch failure should not block session start
Expand Down
7 changes: 3 additions & 4 deletions src/cli/handlers/file-edit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*/

import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';
import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js';
import { ensureWorkerRunning, getWorkerPort, fetchWithTimeout } from '../../shared/worker-utils.js';
import { logger } from '../../utils/logger.js';
import { HOOK_EXIT_CODES } from '../../shared/hook-constants.js';

Expand Down Expand Up @@ -40,7 +40,7 @@ export const fileEditHandler: EventHandler = {
// Send to worker as an observation with file edit metadata
// The observation handler on the worker will process this appropriately
try {
const response = await fetch(`http://127.0.0.1:${port}/api/sessions/observations`, {
const response = await fetchWithTimeout(`http://127.0.0.1:${port}/api/sessions/observations`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
Expand All @@ -50,8 +50,7 @@ export const fileEditHandler: EventHandler = {
tool_response: { success: true },
cwd
})
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
});
}, 10000);

if (!response.ok) {
// Log but don't throw — file edit observation failure should not block editing
Expand Down
7 changes: 3 additions & 4 deletions src/cli/handlers/observation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';
import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js';
import { ensureWorkerRunning, getWorkerPort, fetchWithTimeout } from '../../shared/worker-utils.js';
import { logger } from '../../utils/logger.js';
import { HOOK_EXIT_CODES } from '../../shared/hook-constants.js';
import { isProjectExcluded } from '../../utils/project-filter.js';
Expand Down Expand Up @@ -49,7 +49,7 @@ export const observationHandler: EventHandler = {

// Send to worker - worker handles privacy check and database operations
try {
const response = await fetch(`http://127.0.0.1:${port}/api/sessions/observations`, {
const response = await fetchWithTimeout(`http://127.0.0.1:${port}/api/sessions/observations`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
Expand All @@ -59,8 +59,7 @@ export const observationHandler: EventHandler = {
tool_response: toolResponse,
cwd
})
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
});
}, 10000);

if (!response.ok) {
// Log but don't throw — observation storage failure should not block tool use
Expand Down
6 changes: 3 additions & 3 deletions src/cli/handlers/session-complete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/

import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';
import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js';
import { ensureWorkerRunning, getWorkerPort, fetchWithTimeout } from '../../shared/worker-utils.js';
import { logger } from '../../utils/logger.js';

export const sessionCompleteHandler: EventHandler = {
Expand All @@ -37,13 +37,13 @@ export const sessionCompleteHandler: EventHandler = {

try {
// Call the session complete endpoint by contentSessionId
const response = await fetch(`http://127.0.0.1:${port}/api/sessions/complete`, {
const response = await fetchWithTimeout(`http://127.0.0.1:${port}/api/sessions/complete`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contentSessionId: sessionId
})
});
}, 10000);

if (!response.ok) {
const text = await response.text();
Expand Down
12 changes: 5 additions & 7 deletions src/cli/handlers/session-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';
import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js';
import { ensureWorkerRunning, getWorkerPort, fetchWithTimeout } from '../../shared/worker-utils.js';
import { getProjectName } from '../../utils/project-name.js';
import { logger } from '../../utils/logger.js';
import { HOOK_EXIT_CODES } from '../../shared/hook-constants.js';
Expand Down Expand Up @@ -41,16 +41,15 @@ export const sessionInitHandler: EventHandler = {
logger.debug('HOOK', 'session-init: Calling /api/sessions/init', { contentSessionId: sessionId, project });

// Initialize session via HTTP - handles DB operations and privacy checks
const initResponse = await fetch(`http://127.0.0.1:${port}/api/sessions/init`, {
const initResponse = await fetchWithTimeout(`http://127.0.0.1:${port}/api/sessions/init`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
contentSessionId: sessionId,
project,
prompt
})
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
});
}, 15000);

if (!initResponse.ok) {
// Log but don't throw - a worker 500 should not block the user's prompt
Expand Down Expand Up @@ -90,12 +89,11 @@ export const sessionInitHandler: EventHandler = {
logger.debug('HOOK', 'session-init: Calling /sessions/{sessionDbId}/init', { sessionDbId, promptNumber });

// Initialize SDK agent session via HTTP (starts the agent!)
const response = await fetch(`http://127.0.0.1:${port}/sessions/${sessionDbId}/init`, {
const response = await fetchWithTimeout(`http://127.0.0.1:${port}/sessions/${sessionDbId}/init`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userPrompt: cleanedPrompt, promptNumber })
// Note: Removed signal to avoid Windows Bun cleanup issue (libuv assertion)
});
}, 15000);

if (!response.ok) {
// Log but don't throw - SDK agent failure should not block the user's prompt
Expand Down
8 changes: 4 additions & 4 deletions src/cli/handlers/user-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { basename } from 'path';
import type { EventHandler, NormalizedHookInput, HookResult } from '../types.js';
import { ensureWorkerRunning, getWorkerPort } from '../../shared/worker-utils.js';
import { ensureWorkerRunning, getWorkerPort, fetchWithTimeout } from '../../shared/worker-utils.js';
import { HOOK_EXIT_CODES } from '../../shared/hook-constants.js';

export const userMessageHandler: EventHandler = {
Expand All @@ -23,11 +23,11 @@ export const userMessageHandler: EventHandler = {
const project = basename(input.cwd ?? process.cwd());

// Fetch formatted context directly from worker API
// Note: Removed AbortSignal.timeout to avoid Windows Bun cleanup issue (libuv assertion)
try {
const response = await fetch(
const response = await fetchWithTimeout(
`http://127.0.0.1:${port}/api/context/inject?project=${encodeURIComponent(project)}&colors=true`,
{ method: 'GET' }
{ method: 'GET' },
10000
);

if (!response.ok) {
Expand Down
15 changes: 13 additions & 2 deletions src/services/infrastructure/HealthMonitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,22 @@ export async function httpShutdown(port: number): Promise<boolean> {
/**
* Get the plugin version from the installed marketplace package.json
* This is the "expected" version that should be running
* Returns 'unknown' if package.json cannot be read (e.g., during shutdown race, file locked)
* (Issue #1042)
*/
export function getInstalledPluginVersion(): string {
const packageJsonPath = path.join(MARKETPLACE_ROOT, 'package.json');
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
return packageJson.version;
try {
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
return packageJson.version;
} catch (error) {
const code = (error as NodeJS.ErrnoException).code;
if (code === 'ENOENT' || code === 'EBUSY') {
logger.debug('SYSTEM', 'Could not read plugin version (shutdown race)', { code });
return 'unknown';
}
throw error;
}
}

/**
Expand Down
Loading