diff --git a/scripts/loop-status.js b/scripts/loop-status.js index 1b0912c2..d6f32d5c 100644 --- a/scripts/loop-status.js +++ b/scripts/loop-status.js @@ -634,12 +634,20 @@ function atomicWriteJson(filePath, payload) { function getSnapshotPath(outputDir, session, usedNames) { const baseName = sanitizeSnapshotName(session.sessionId); - let fileName = `${baseName}.json`; - if (usedNames.has(fileName)) { - fileName = `${baseName}-${hashString(session.transcriptPath || session.sessionId).slice(0, 8)}.json`; + const hashSuffix = hashString(session.transcriptPath || session.sessionId).slice(0, 8); + let attempt = 0; + + while (attempt < 1000) { + const suffix = attempt === 0 ? '' : `-${hashSuffix}${attempt === 1 ? '' : `-${attempt}`}`; + const fileName = `${baseName}${suffix}.json`; + if (!usedNames.has(fileName)) { + usedNames.add(fileName); + return path.join(outputDir, fileName); + } + attempt += 1; } - usedNames.add(fileName); - return path.join(outputDir, fileName); + + throw new Error(`Could not allocate a snapshot filename for session ${session.sessionId}`); } function writeStatusSnapshots(payload, writeDir) { @@ -685,6 +693,19 @@ function writeStatusSnapshots(payload, writeDir) { }; } +function tryWriteStatusSnapshots(payload, options) { + if (!options.writeDir) { + return null; + } + + try { + return writeStatusSnapshots(payload, options.writeDir); + } catch (error) { + console.error(`[loop-status] WARNING: could not write status snapshots: ${error.message}`); + return null; + } +} + function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } @@ -717,7 +738,7 @@ async function runWatch(options) { console.log(''); } const payload = buildStatus(normalizedOptions); - writeStatusSnapshots(payload, normalizedOptions.writeDir); + tryWriteStatusSnapshots(payload, normalizedOptions); writeStatus(payload, normalizedOptions); exitCode = Math.max(exitCode, getStatusExitCode(payload)); iteration += 1; @@ -748,7 +769,7 @@ async function main() { } const payload = buildStatus(options); - writeStatusSnapshots(payload, options.writeDir); + tryWriteStatusSnapshots(payload, options); writeStatus(payload, options); if (options.exitCode) { process.exitCode = getStatusExitCode(payload); @@ -770,5 +791,6 @@ module.exports = { getStatusExitCode, parseArgs, runWatch, + tryWriteStatusSnapshots, writeStatusSnapshots, }; diff --git a/tests/scripts/loop-status.test.js b/tests/scripts/loop-status.test.js index c4d7f56f..76735978 100644 --- a/tests/scripts/loop-status.test.js +++ b/tests/scripts/loop-status.test.js @@ -594,6 +594,35 @@ function runTests() { } })) passed++; else failed++; + if (test('write-dir failures do not suppress normal stdout', () => { + const homeDir = createTempHome(); + + try { + const blockedPath = path.join(homeDir, 'snapshot-target-is-a-file'); + fs.writeFileSync(blockedPath, 'not a directory\n', 'utf8'); + writeTranscript(homeDir, '-Users-affoon-project-write-error', 'session-write-error.jsonl', [ + assistantMessage('2026-04-30T09:55:00.000Z', 'session-write-error', 'Loop checkpoint.'), + ]); + + const result = run([ + '--home', + homeDir, + '--now', + NOW, + '--json', + '--write-dir', + blockedPath, + ]); + + assert.strictEqual(result.code, 0, result.stderr); + const payload = parsePayload(result.stdout); + assert.strictEqual(payload.schemaVersion, 'ecc.loop-status.v1'); + assert.strictEqual(payload.sessions[0].sessionId, 'session-write-error'); + } finally { + fs.rmSync(homeDir, { recursive: true, force: true }); + } + })) passed++; else failed++; + console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`); process.exit(failed > 0 ? 1 : 0); }