diff --git a/scripts/agent/run-scheduler-cycle.mjs b/scripts/agent/run-scheduler-cycle.mjs index ba8ccfc..b89b054 100644 --- a/scripts/agent/run-scheduler-cycle.mjs +++ b/scripts/agent/run-scheduler-cycle.mjs @@ -164,7 +164,9 @@ function formatDurationMs(ms) { function buildLockBackendRemediation({ cadence, retryWindowMs, maxDeferrals, incidentSignalId = null }) { const steps = [ + 'Likely backend/relay connectivity issue during lock acquisition.', `Retry window: ${formatDurationMs(retryWindowMs)} (max deferrals: ${maxDeferrals}).`, + `Retry command: npm run scheduler:${cadence}`, `Run health check: npm run lock:health -- --cadence ${cadence}`, `Review incident runbook: ${LOCK_INCIDENT_LINK}`, ]; diff --git a/test/__snapshots__/lock-backend-error-detail.snapshot.txt b/test/__snapshots__/lock-backend-error-detail.snapshot.txt new file mode 100644 index 0000000..89809d0 --- /dev/null +++ b/test/__snapshots__/lock-backend-error-detail.snapshot.txt @@ -0,0 +1 @@ +Lock backend error (relay_publish_quorum_failure) after 3 attempt(s). Prompt not executed. Recommended auto-remediation: Likely backend/relay connectivity issue during lock acquisition. Retry window: 60 minute(s) (max deferrals: 3). Retry command: npm run scheduler:daily Run health check: npm run lock:health -- --cadence daily Review incident runbook: docs/agent-handoffs/learnings/2026-02-15-relay-health-preflight-job.md diff --git a/test/__snapshots__/prompt-parse-error-detail.snapshot.txt b/test/__snapshots__/prompt-parse-error-detail.snapshot.txt new file mode 100644 index 0000000..c6aa6ed --- /dev/null +++ b/test/__snapshots__/prompt-parse-error-detail.snapshot.txt @@ -0,0 +1 @@ +Prompt not executed; unable to read prompt file at : ENOENT: no such file or directory, open '' diff --git a/test/run-scheduler-cycle-memory-policy.test.mjs b/test/run-scheduler-cycle-memory-policy.test.mjs index e10ec06..bf1b383 100644 --- a/test/run-scheduler-cycle-memory-policy.test.mjs +++ b/test/run-scheduler-cycle-memory-policy.test.mjs @@ -4,8 +4,10 @@ import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; import { spawn } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; const SOURCE_SCRIPT = path.resolve('scripts/agent/run-scheduler-cycle.mjs'); +const SNAPSHOT_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), '__snapshots__'); async function runNode(scriptPath, args, { cwd, env }) { return new Promise((resolve) => { @@ -32,6 +34,25 @@ async function runNode(scriptPath, args, { cwd, env }) { }); } +function extractDetailLine(markdownBody) { + const line = String(markdownBody) + .split(/\r?\n/) + .find((entry) => entry.startsWith('- detail: ')); + return line ? line.slice('- detail: '.length) : ''; +} + +function normalizePromptFileDetail(detailLine) { + return String(detailLine) + .replace(/at\s+.*?agent-a\.md:/, 'at :') + .replace(/in\s+.*?agent-a\.md\./, 'in .') + .replace(/'[^']*agent-a\.md'/g, "''"); +} + +async function assertSnapshot(snapshotName, actual) { + const expected = await fs.readFile(path.join(SNAPSHOT_DIR, snapshotName), 'utf8'); + assert.equal(actual, expected); +} + async function setupFixture({ memoryPolicy, lockShellBody = '', @@ -204,10 +225,16 @@ test('records backend failure metadata when lock command exits with code 2', { c assert.match(failedBody, /lock_failure_reason_distribution: '\{\}'/); assert.match(failedBody, /lock_attempt_id: '3'/); assert.match(failedBody, /lock_correlation_id: 'daily:agent-a:/); + assert.match(failedBody, /Likely backend\/relay connectivity issue during lock acquisition\./); + assert.match(failedBody, /Retry command: npm run scheduler:daily/); assert.match(failedBody, /Recommended auto-remediation:/); assert.match(failedBody, /Run health check: npm run lock:health -- --cadence daily/); assert.match(failedBody, /Review incident runbook: docs\/agent-handoffs\/learnings\/2026-02-15-relay-health-preflight-job.md/); + assert.match(failedBody, /Prompt not executed\./); assert.match(failedBody, /platform: 'codex'/); + + const detail = extractDetailLine(failedBody); + await assertSnapshot('lock-backend-error-detail.snapshot.txt', `${detail}\n`); }); test('defers backend failures in non-strict mode and reuses idempotency key on successful retry', { concurrency: false }, async () => { @@ -428,6 +455,10 @@ stderr: ${result.stderr}`); const failedBody = await fs.readFile(path.join(fixture.root, 'task-logs', 'daily', failedLog), 'utf8'); assert.match(failedBody, /reason: Prompt file parse\/read failed/); assert.match(failedBody, /failure_category: 'prompt_parse_error'/); + assert.doesNotMatch(failedBody, /Recommended auto-remediation|Run health check: npm run lock:health|Retry command: npm run scheduler:/); + + const detail = normalizePromptFileDetail(extractDetailLine(failedBody)); + await assertSnapshot('prompt-parse-error-detail.snapshot.txt', `${detail}\n`); }); test('categorizes invalid prompt schema as prompt_schema_error', { concurrency: false }, async () => {