{"record":{"id":"56dde4a5c5a6163e","repo":"can1357/oh-my-pi","slug":"session-header-changed-during-stats-cleanup-ses","errorCode":null,"errorMessage":"session header changed during stats cleanup: ${session.path}","messagePattern":"session header changed during stats cleanup: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/cli/gc-cli.ts","lineNumber":1203,"sourceCode":"\t\t\tarchivedByPath.set(path.resolve(session.path), session);\n\t\t}\n\t} catch (error) {\n\t\tresult.errors.push(`stats cleanup scan: ${errorMessage(error)}`);\n\t}\n\ttry {\n\t\tretainedSessions = await listActiveSessions(sessionsRoot);\n\t} catch (error) {\n\t\tresult.errors.push(`stats cleanup scan: ${errorMessage(error)}`);\n\t\treturn;\n\t}\n\n\ttry {\n\t\tawait withStatsSyncLock(dbPath, async () => {\n\t\t\tconst retainedStatsSessions = await Promise.all(\n\t\t\t\tretainedSessions.map(async session => {\n\t\t\t\t\tconst header = await readSessionLineageHeader(session.path);\n\t\t\t\t\tif (!header || header.id !== session.id) {\n\t\t\t\t\t\tthrow new Error(`session header changed during stats cleanup: ${session.path}`);\n\t\t\t\t\t}\n\t\t\t\t\treturn {\n\t\t\t\t\t\tpath: session.path,\n\t\t\t\t\t\tid: session.id,\n\t\t\t\t\t\tparentSession: header.parentSession,\n\t\t\t\t\t\thistoricalPaths: managedHistoricalSessionPaths(header, session.path, sessionsRoot),\n\t\t\t\t\t\tidentities: createStatsIdentities(),\n\t\t\t\t\t};\n\t\t\t\t}),\n\t\t\t);\n\t\t\tconst context = buildStatsCleanupPlans([...archivedByPath.values()], retainedStatsSessions, dbPath);\n\t\t\tawait populateStatsTransferTargets(context);\n\t\t\tresult.statsRowsDeleted = reconcileStatsRowsForSessions(dbPath, context.plans);\n\t\t});\n\t} catch (error) {\n\t\tresult.errors.push(`stats cleanup: ${errorMessage(error)}`);\n\t}\n}","sourceCodeStart":1185,"sourceCodeEnd":1221,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/cli/gc-cli.ts#L1185-L1221","documentation":"During stats cleanup, inside the stats-sync lock, each retained session's lineage header is re-read and its id compared to the id captured earlier. A mismatch means the session file was rewritten/replaced (e.g. by compaction or a new session) while cleanup was in flight, so the run aborts to avoid deleting or relinking the wrong file.","triggerScenarios":"A retained session file's header changes between listing and cleanup: the session was compacted/rewritten, its id rotated, or the file was truncated/corrupted concurrently by an active agent process.","commonSituations":"Running `omp gc` while an interactive coding-agent session is live and rotating its session file; a crash-recovery rewrite altering the header; manually editing or resuming a session during cleanup.","solutions":["Re-run GC when no agent session is actively writing (stop running sessions or retry later)","Verify the session file at the path in the message is healthy — re-read its header and confirm the id matches what the session index recorded","Rebuild/refresh the stats session index so captured ids match current files, then retry"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"import * as fs from \"node:fs/promises\";\n// Before cleanup, confirm each retained session header still matches the recorded id\nfor (const s of retainedSessions) {\n  const header = await readSessionLineageHeader(s.path);\n  if (!header || header.id !== s.id) throw new Error(`stale index entry: ${s.path}`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await runStatsCleanup(retainedSessions);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"session header changed during stats cleanup:\")) {\n    // a live session rotated its file — refresh the session index and retry when idle\n    await refreshSessionIndex();\n    await runStatsCleanup(await listRetainedSessions());\n  } else throw err;\n}","preventionTips":["Run stats cleanup/GC when no interactive agent sessions are actively writing","Always perform cleanup inside the stats-sync lock","Refresh the session index immediately before cleanup so captured ids are current","Retry cleanup on this error instead of forcing deletion"],"tags":["concurrency","gc","stats","race-condition"],"backgroundTag":"concurrent-modification","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}