fix: handle bare path strings in files_modified/files_read columns (#1359)

JSON.parse('/path/to/file') throws SyntaxError, crashing the viewer and
any code reading observations with legacy bare-path data in those columns.

- Add parseFileList() helper in observations/files.ts — tries JSON.parse,
  falls back to wrapping bare strings in an array
- Replace unsafe JSON.parse calls in files.ts, SessionStore.ts, ChromaSync.ts
- Add 9 unit tests covering null, empty, valid JSON, bare paths, invalid JSON

Closes #1359

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ousama Ben Younes
2026-04-01 06:17:35 +00:00
parent 3651a34e96
commit 2a304d59eb
4 changed files with 69 additions and 26 deletions
+3 -12
View File
@@ -14,6 +14,7 @@ import {
} from '../../types/database.js';
import type { PendingMessageStore } from './PendingMessageStore.js';
import { computeObservationContentHash, findDuplicateObservation } from './observations/store.js';
import { parseFileList } from './observations/files.js';
/**
* Session data store for SDK sessions, observations, and summaries
@@ -1307,20 +1308,10 @@ export class SessionStore {
for (const row of rows) {
// Parse files_read
if (row.files_read) {
const files = JSON.parse(row.files_read);
if (Array.isArray(files)) {
files.forEach(f => filesReadSet.add(f));
}
}
parseFileList(row.files_read).forEach(f => filesReadSet.add(f));
// Parse files_modified
if (row.files_modified) {
const files = JSON.parse(row.files_modified);
if (Array.isArray(files)) {
files.forEach(f => filesModifiedSet.add(f));
}
}
parseFileList(row.files_modified).forEach(f => filesModifiedSet.add(f));
}
return {
+17 -12
View File
@@ -7,6 +7,21 @@ import { Database } from 'bun:sqlite';
import { logger } from '../../../utils/logger.js';
import type { SessionFilesResult } from './types.js';
/**
* Safely parse a JSON array string from the DB.
* Handles legacy bare-path strings (e.g. "/foo/bar.ts") by wrapping them
* in an array instead of crashing with a SyntaxError (fix for #1359).
*/
export function parseFileList(value: string | null | undefined): string[] {
if (!value) return [];
try {
const parsed = JSON.parse(value);
return Array.isArray(parsed) ? parsed : [String(parsed)];
} catch {
return [value];
}
}
/**
* Get aggregated files from all observations for a session
*/
@@ -30,20 +45,10 @@ export function getFilesForSession(
for (const row of rows) {
// Parse files_read
if (row.files_read) {
const files = JSON.parse(row.files_read);
if (Array.isArray(files)) {
files.forEach(f => filesReadSet.add(f));
}
}
parseFileList(row.files_read).forEach(f => filesReadSet.add(f));
// Parse files_modified
if (row.files_modified) {
const files = JSON.parse(row.files_modified);
if (Array.isArray(files)) {
files.forEach(f => filesModifiedSet.add(f));
}
}
parseFileList(row.files_modified).forEach(f => filesModifiedSet.add(f));
}
return {