godcrm/backend/routes/v3/ai-agents/stuckProcessingCleanup.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

70 lines
3.7 KiB
JavaScript

/**
* Stuck Processing Cleanup
* Periodic safety net: clears is_processing flags stuck for >30 minutes.
*
* BUG-504/BUG-41441: increased from 60s to 1800s to match Claude CLI timeout (30 min)
* Ticket #36708: original safety net for permanent stuck states
*
* FIX-C: REMOVED premature is_processing clearing on startup.
* Previously an IIFE cleared ALL is_processing flags before recoverStuckJobs()
* ran in server.js, which interfered with job recovery (Phase 0 and Phase 1
* need is_processing to still be set to identify which conversations had active
* jobs). The clearing now happens ONLY in recoverStuckJobs() Phase 3, which
* properly checks for orphaned conversations with no corresponding agent_job.
* The processing_started_at column migration is handled elsewhere.
*/
import { dbRun, dbAll, isPostgres } from '../../../database/connection.js';
import { apiLogger } from '../../../utils/logger.js';
/**
* Start the periodic cleanup interval.
* Runs every 60 seconds, clears conversations stuck in processing for >30min.
*/
export function startStuckProcessingCleanup() {
setInterval(async () => {
try {
// BUG-41441: Find conversations stuck in processing for >30min, save timeout message, then clear
const stuckConversations = await dbAll(
isPostgres()
? `SELECT id, processing_agent_name FROM conversations
WHERE is_processing = true AND processing_started_at < NOW() - INTERVAL '30 minutes'`
: `SELECT id, processing_agent_name FROM conversations
WHERE is_processing = 1 AND processing_started_at < datetime('now', '-1800 seconds')`
);
if (stuckConversations && stuckConversations.length > 0) {
for (const conv of stuckConversations) {
// Save a system message so the user sees feedback about the timeout
try {
const agentLabel = conv.processing_agent_name || 'AI Agent';
await dbRun(
isPostgres()
? `INSERT INTO messages (conversation_id, sender_type, role, content, content_type, created_at, updated_at)
VALUES ($1, 'system', 'system', $2, 'system', NOW(), NOW())`
: `INSERT INTO messages (conversation_id, sender_type, role, content, content_type, created_at, updated_at)
VALUES (?, 'system', 'system', ?, 'system', datetime('now'), datetime('now'))`,
[conv.id, `Agent "${agentLabel}" processing timed out after 30 minutes. The task was too complex or the connection was lost. You can try sending your message again.`]
);
} catch (msgErr) {
apiLogger.debug({ err: msgErr, conversationId: conv.id }, 'Ticket #36708: Failed to save timeout message');
}
}
// ADR-093 Task 8: clear processing_agent_id and processing_agent_name
const result = await dbRun(
isPostgres()
? `UPDATE conversations SET is_processing = false, processing_started_at = NULL, processing_agent_id = NULL, processing_agent_name = NULL, updated_at = NOW()
WHERE is_processing = true AND processing_started_at < NOW() - INTERVAL '30 minutes'`
: `UPDATE conversations SET is_processing = 0, processing_started_at = NULL, processing_agent_id = NULL, processing_agent_name = NULL, updated_at = datetime('now')
WHERE is_processing = 1 AND processing_started_at < datetime('now', '-1800 seconds')`
);
if (result?.changes > 0) {
apiLogger.warn({ count: result.changes }, 'BUG-41441: Cleared stuck is_processing flags (>30min timeout)');
}
}
} catch (err) {
// Silently ignore if column doesn't exist yet
}
}, 60000);
}