Skip to content

Commit 67f5501

Browse files
committed
dealing with corrupted history sessions
1 parent cad0853 commit 67f5501

2 files changed

Lines changed: 53 additions & 2 deletions

File tree

src/session/SessionManager.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,16 @@ export class SessionManager {
222222
private async loadIndex(): Promise<void> {
223223
const indexPath = path.join(this.sessionsDir, 'index.json');
224224
if (await fs.pathExists(indexPath)) {
225-
this.index = await fs.readJson(indexPath) as SessionIndex;
225+
try {
226+
this.index = await fs.readJson(indexPath) as SessionIndex;
227+
} catch (error) {
228+
const backupPath = `${indexPath}.corrupt-${Date.now()}`;
229+
await fs.move(indexPath, backupPath, { overwrite: true });
230+
const reason = error instanceof Error ? error.message : String(error);
231+
console.warn(`Session index was corrupt and has been reset: ${reason}. Backup saved to ${backupPath}`);
232+
this.index = { sessions: [], byProject: {} };
233+
await this.saveIndex();
234+
}
226235
} else {
227236
this.index = { sessions: [], byProject: {} };
228237
}

tests/session/SessionManager.test.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { afterEach, beforeEach, describe, expect, it } from 'vitest';
77
import fs from 'fs-extra';
88
import path from 'node:path';
99
import os from 'node:os';
10-
import { Session } from '../../src/session/SessionManager.js';
10+
import { Session, SessionManager } from '../../src/session/SessionManager.js';
1111
import type { SessionMetadata } from '../../src/session/types.js';
1212

1313
describe('Session', () => {
@@ -50,3 +50,45 @@ describe('Session', () => {
5050
expect(session.metadata.messageCount).toBe(1);
5151
});
5252
});
53+
54+
describe('SessionManager', () => {
55+
let tmpDir: string;
56+
57+
beforeEach(async () => {
58+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'autohand-session-manager-test-'));
59+
});
60+
61+
afterEach(async () => {
62+
await fs.remove(tmpDir);
63+
});
64+
65+
it('recovers from a corrupt session index and initializes empty', async () => {
66+
const indexPath = path.join(tmpDir, 'index.json');
67+
const corruptContent = '{ "sessions": [\n { "id": "broken\x00"';
68+
await fs.writeFile(indexPath, corruptContent);
69+
70+
const manager = new SessionManager(tmpDir);
71+
await expect(manager.initialize()).resolves.toBeUndefined();
72+
73+
const sessions = await manager.listSessions();
74+
expect(sessions).toEqual([]);
75+
76+
const backupFiles = (await fs.readdir(tmpDir)).filter((f) => f.startsWith('index.json.corrupt-'));
77+
expect(backupFiles).toHaveLength(1);
78+
expect(await fs.readFile(path.join(tmpDir, backupFiles[0]), 'utf-8')).toBe(corruptContent);
79+
});
80+
81+
it('recovers from an empty session index file', async () => {
82+
const indexPath = path.join(tmpDir, 'index.json');
83+
await fs.writeFile(indexPath, '');
84+
85+
const manager = new SessionManager(tmpDir);
86+
await expect(manager.initialize()).resolves.toBeUndefined();
87+
88+
const sessions = await manager.listSessions();
89+
expect(sessions).toEqual([]);
90+
91+
const backupFiles = (await fs.readdir(tmpDir)).filter((f) => f.startsWith('index.json.corrupt-'));
92+
expect(backupFiles).toHaveLength(1);
93+
});
94+
});

0 commit comments

Comments
 (0)