fix: prune expired session files on session start

This commit is contained in:
Affaan Mustafa
2026-04-05 14:58:10 -07:00
parent b9d0e0b04d
commit 2f0a40a63f
2 changed files with 92 additions and 1 deletions

View File

@@ -30,6 +30,7 @@ const fs = require('fs');
const INSTINCT_CONFIDENCE_THRESHOLD = 0.7;
const MAX_INJECTED_INSTINCTS = 6;
const DEFAULT_SESSION_RETENTION_DAYS = 30;
/**
* Resolve a filesystem path to its canonical (real) form.
@@ -78,6 +79,53 @@ function dedupeRecentSessions(searchDirs) {
.sort((left, right) => right.mtime - left.mtime || left.dirIndex - right.dirIndex);
}
function getSessionRetentionDays() {
const raw = process.env.ECC_SESSION_RETENTION_DAYS;
if (!raw) return DEFAULT_SESSION_RETENTION_DAYS;
const parsed = Number.parseInt(raw, 10);
return Number.isInteger(parsed) && parsed > 0 ? parsed : DEFAULT_SESSION_RETENTION_DAYS;
}
function pruneExpiredSessions(searchDirs, retentionDays) {
const uniqueDirs = Array.from(new Set(searchDirs.filter(dir => typeof dir === 'string' && dir.length > 0)));
let removed = 0;
for (const dir of uniqueDirs) {
if (!fs.existsSync(dir)) continue;
let entries;
try {
entries = fs.readdirSync(dir, { withFileTypes: true });
} catch {
continue;
}
for (const entry of entries) {
if (!entry.isFile() || !entry.name.endsWith('-session.tmp')) continue;
const fullPath = path.join(dir, entry.name);
let stats;
try {
stats = fs.statSync(fullPath);
} catch {
continue;
}
const ageInDays = (Date.now() - stats.mtimeMs) / (1000 * 60 * 60 * 24);
if (ageInDays <= retentionDays) continue;
try {
fs.rmSync(fullPath, { force: true });
removed += 1;
} catch (error) {
log(`[SessionStart] Warning: failed to prune expired session ${fullPath}: ${error.message}`);
}
}
}
return removed;
}
/**
* Select the best matching session for the current working directory.
*
@@ -301,6 +349,7 @@ function summarizeActiveInstincts(observerContext) {
async function main() {
const sessionsDir = getSessionsDir();
const sessionSearchDirs = getSessionSearchDirs();
const learnedDir = getLearnedSkillsDir();
const additionalContextParts = [];
const observerContext = resolveProjectContext();
@@ -309,6 +358,12 @@ async function main() {
ensureDir(sessionsDir);
ensureDir(learnedDir);
const retentionDays = getSessionRetentionDays();
const prunedSessions = pruneExpiredSessions(sessionSearchDirs, retentionDays);
if (prunedSessions > 0) {
log(`[SessionStart] Pruned ${prunedSessions} expired session(s) older than ${retentionDays} day(s)`);
}
const observerSessionId = resolveSessionId();
if (observerSessionId) {
writeSessionLease(observerContext, observerSessionId, {
@@ -326,7 +381,7 @@ async function main() {
}
// Check for recent session files (last 7 days)
const recentSessions = dedupeRecentSessions(getSessionSearchDirs());
const recentSessions = dedupeRecentSessions(sessionSearchDirs);
if (recentSessions.length > 0) {
log(`[SessionStart] Found ${recentSessions.length} recent session(s)`);

View File

@@ -4281,6 +4281,42 @@ async function runTests() {
passed++;
else failed++;
if (
await asyncTest('prunes session files older than the retention window', async () => {
const isoHome = path.join(os.tmpdir(), `ecc-start-prune-${Date.now()}`);
const sessionsDir = getCanonicalSessionsDir(isoHome);
fs.mkdirSync(sessionsDir, { recursive: true });
fs.mkdirSync(path.join(isoHome, '.claude', 'skills', 'learned'), { recursive: true });
const recentFile = path.join(sessionsDir, '2026-02-10-keepme-session.tmp');
fs.writeFileSync(recentFile, '# Recent Session\n\nKEEP ME');
const fiveDaysAgo = new Date(Date.now() - 5 * 24 * 60 * 60 * 1000);
fs.utimesSync(recentFile, fiveDaysAgo, fiveDaysAgo);
const expiredFile = path.join(sessionsDir, '2026-01-01-pruneme-session.tmp');
fs.writeFileSync(expiredFile, '# Expired Session\n\nDELETE ME');
const thirtyOneDaysAgo = new Date(Date.now() - 31 * 24 * 60 * 60 * 1000);
fs.utimesSync(expiredFile, thirtyOneDaysAgo, thirtyOneDaysAgo);
try {
const result = await runScript(path.join(scriptsDir, 'session-start.js'), '', {
HOME: isoHome,
USERPROFILE: isoHome,
ECC_SESSION_RETENTION_DAYS: '30',
});
assert.strictEqual(result.code, 0);
assert.ok(!fs.existsSync(expiredFile), 'Should delete expired session files beyond retention');
assert.ok(fs.existsSync(recentFile), 'Should keep recent session files inside retention');
assert.ok(result.stderr.includes('Pruned 1 expired session'), `Should report pruning activity, stderr: ${result.stderr}`);
} finally {
fs.rmSync(isoHome, { recursive: true, force: true });
}
})
)
passed++;
else failed++;
console.log('\nRound 55: session-start.js (newest session selection):');
if (