Revert "revert: roll back v12.3.3 (Issue Blowout 2026)"

This reverts commit bfc7de377a.
This commit is contained in:
Alex Newman
2026-04-20 12:18:55 -07:00
parent b9836d6c2a
commit 8d166b47c1
30 changed files with 869 additions and 168 deletions
+17 -11
View File
@@ -28,6 +28,7 @@ import { sanitizeEnv } from '../supervisor/env-sanitizer.js';
// ensure the worker daemon is up without importing this entire module — which
// transitively pulls in the SQLite database layer via ChromaSync/DatabaseManager.
import { ensureWorkerStarted as ensureWorkerStartedShared } from './worker-spawner.js';
import { RestartGuard } from './worker/RestartGuard.js';
// Re-export for backward compatibility — canonical implementation in shared/plugin-state.ts
export { isPluginDisabledInClaudeSettings } from '../shared/plugin-state.js';
@@ -482,7 +483,7 @@ export class WorkerService {
// Best-effort loopback MCP self-check
getSupervisor().assertCanSpawn('mcp server');
const transport = new StdioClientTransport({
command: 'node',
command: process.execPath, // Use resolved path, not bare 'node' which fails on non-interactive PATH (#1876)
args: [mcpServerPath],
env: sanitizeEnv(process.env)
});
@@ -558,12 +559,14 @@ export class WorkerService {
}
}
// Purge failed pending messages to prevent unbounded queue growth (#1957)
// Purge stale failed pending messages to prevent unbounded queue growth (#1957)
// Only remove failures older than 1 hour to preserve recent failures for inspection/retry
try {
const pendingStore = this.sessionManager.getPendingMessageStore();
const purged = pendingStore.clearFailed();
const FAILED_MESSAGE_RETENTION_MS = 60 * 60 * 1000; // 1 hour
const purged = pendingStore.clearFailedOlderThan(FAILED_MESSAGE_RETENTION_MS);
if (purged > 0) {
logger.info('SYSTEM', `Purged ${purged} failed pending messages`);
logger.info('SYSTEM', `Purged ${purged} stale failed pending messages (older than 1h)`);
}
} catch (e) {
if (e instanceof Error) {
@@ -816,17 +819,19 @@ export class WorkerService {
}
// Fall through to pending-work restart below
}
const MAX_PENDING_RESTARTS = 3;
if (pendingCount > 0) {
// Track consecutive pending-work restarts to prevent infinite loops (e.g. FK errors)
session.consecutiveRestarts = (session.consecutiveRestarts || 0) + 1;
// Windowed restart guard: only blocks tight-loop restarts, not spread-out ones (#2053)
if (!session.restartGuard) session.restartGuard = new RestartGuard();
const restartAllowed = session.restartGuard.recordRestart();
session.consecutiveRestarts = (session.consecutiveRestarts || 0) + 1; // Keep for logging
if (session.consecutiveRestarts > MAX_PENDING_RESTARTS) {
logger.error('SYSTEM', 'Exceeded max pending-work restarts, stopping to prevent infinite loop', {
if (!restartAllowed) {
logger.error('SYSTEM', 'Restart guard tripped: too many restarts in window, stopping to prevent runaway costs', {
sessionId: session.sessionDbId,
pendingCount,
consecutiveRestarts: session.consecutiveRestarts
restartsInWindow: session.restartGuard.restartsInWindow,
windowMs: session.restartGuard.windowMs,
maxRestarts: session.restartGuard.maxRestarts
});
session.consecutiveRestarts = 0;
this.terminateSession(session.sessionDbId, 'max_restarts_exceeded');
@@ -846,6 +851,7 @@ export class WorkerService {
} else {
// Successful completion with no pending work — clean up session
// removeSessionImmediate fires onSessionDeletedCallback → broadcastProcessingStatus()
session.restartGuard?.recordSuccess();
session.consecutiveRestarts = 0;
this.sessionManager.removeSessionImmediate(session.sessionDbId);
}