fix: add temp file reaper to prevent unbounded /tmp accumulation
core.cjs output() created gsd-*.json files in /tmp for large payloads but never cleaned them up, causing unbounded disk usage (800+ GB reported in #1251). Similarly, profile-pipeline.cjs created gsd-pipeline-* and gsd-profile-* temp directories without cleanup. Adds reapStaleTempFiles() that removes gsd-prefixed temp files/dirs older than 5 minutes. Called opportunistically before each new temp file/dir creation. Non-critical — cleanup failures never break output. Closes #1251 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,7 @@ const {
|
||||
escapeRegex,
|
||||
generateSlugInternal,
|
||||
normalizePhaseName,
|
||||
reapStaleTempFiles,
|
||||
normalizeMd,
|
||||
comparePhaseNum,
|
||||
safeReadFile,
|
||||
@@ -1315,3 +1316,51 @@ describe('findProjectRoot', () => {
|
||||
assert.strictEqual(findProjectRoot(backendDir), backendDir);
|
||||
});
|
||||
});
|
||||
|
||||
// ─── reapStaleTempFiles ─────────────────────────────────────────────────────
|
||||
|
||||
describe('reapStaleTempFiles', () => {
|
||||
test('removes stale gsd-*.json files older than maxAgeMs', () => {
|
||||
const tmpDir = os.tmpdir();
|
||||
const stalePath = path.join(tmpDir, `gsd-reap-test-${Date.now()}.json`);
|
||||
fs.writeFileSync(stalePath, '{}');
|
||||
// Set mtime to 10 minutes ago
|
||||
const oldTime = new Date(Date.now() - 10 * 60 * 1000);
|
||||
fs.utimesSync(stalePath, oldTime, oldTime);
|
||||
|
||||
reapStaleTempFiles('gsd-reap-test-', { maxAgeMs: 5 * 60 * 1000 });
|
||||
|
||||
assert.ok(!fs.existsSync(stalePath), 'stale file should be removed');
|
||||
});
|
||||
|
||||
test('preserves fresh gsd-*.json files', () => {
|
||||
const tmpDir = os.tmpdir();
|
||||
const freshPath = path.join(tmpDir, `gsd-reap-fresh-${Date.now()}.json`);
|
||||
fs.writeFileSync(freshPath, '{}');
|
||||
|
||||
reapStaleTempFiles('gsd-reap-fresh-', { maxAgeMs: 5 * 60 * 1000 });
|
||||
|
||||
assert.ok(fs.existsSync(freshPath), 'fresh file should be preserved');
|
||||
// Clean up
|
||||
fs.unlinkSync(freshPath);
|
||||
});
|
||||
|
||||
test('removes stale temp directories when present', () => {
|
||||
const tmpDir = os.tmpdir();
|
||||
const staleDir = fs.mkdtempSync(path.join(tmpDir, 'gsd-reap-dir-'));
|
||||
fs.writeFileSync(path.join(staleDir, 'data.jsonl'), 'test');
|
||||
// Set mtime to 10 minutes ago
|
||||
const oldTime = new Date(Date.now() - 10 * 60 * 1000);
|
||||
fs.utimesSync(staleDir, oldTime, oldTime);
|
||||
|
||||
reapStaleTempFiles('gsd-reap-dir-', { maxAgeMs: 5 * 60 * 1000 });
|
||||
|
||||
assert.ok(!fs.existsSync(staleDir), 'stale directory should be removed');
|
||||
});
|
||||
|
||||
test('does not throw on empty or missing prefix matches', () => {
|
||||
assert.doesNotThrow(() => {
|
||||
reapStaleTempFiles('gsd-nonexistent-prefix-xyz-', { maxAgeMs: 0 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user