fix: handle corrupted JSON files gracefully with error logging

This commit is contained in:
mirsella 2025-12-15 15:44:10 +01:00
parent 2ca118db59
commit 81059b123e
No known key found for this signature in database
GPG key ID: E53202A06B2614A4
4 changed files with 53 additions and 13 deletions

View file

@ -64,13 +64,27 @@ async function getAllSessions(): Promise<Session.Info[]> {
const sessions: Session.Info[] = []
const projectKeys = await Storage.list(["project"])
const projects = await Promise.all(projectKeys.map((key) => Storage.read<Project.Info>(key)))
const projects = await Promise.all(
projectKeys.map((key) =>
Storage.read<Project.Info>(key).catch((e) => {
console.error(`Skipping corrupted project ${key.join("/")}:`, e)
return null
}),
),
)
for (const project of projects) {
if (!project) continue
const sessionKeys = await Storage.list(["session", project.id])
const projectSessions = await Promise.all(sessionKeys.map((key) => Storage.read<Session.Info>(key)))
const projectSessions = await Promise.all(
sessionKeys.map((key) =>
Storage.read<Session.Info>(key).catch((e) => {
console.error(`Skipping corrupted session ${key.join("/")}:`, e)
return null
}),
),
)
for (const session of projectSessions) {
if (session) {

View file

@ -66,9 +66,13 @@ export const prettier: Info = {
async enabled() {
const items = await Filesystem.findUp("package.json", Instance.directory, Instance.worktree)
for (const item of items) {
const json = await Bun.file(item).json()
if (json.dependencies?.prettier) return true
if (json.devDependencies?.prettier) return true
try {
const json = await Bun.file(item).json()
if (json.dependencies?.prettier) return true
if (json.devDependencies?.prettier) return true
} catch (e) {
console.error(`Skipping corrupted package.json ${item}:`, e)
}
}
return false
},

View file

@ -542,10 +542,14 @@ export namespace MessageV2 {
export const stream = fn(Identifier.schema("session"), async function* (sessionID) {
const list = await Array.fromAsync(await Storage.list(["message", sessionID]))
for (let i = list.length - 1; i >= 0; i--) {
yield await get({
sessionID,
messageID: list[i][2],
})
try {
yield await get({
sessionID,
messageID: list[i][2],
})
} catch (e) {
console.error(`Skipping corrupted message ${list[i][2]}:`, e)
}
}
})
@ -608,7 +612,7 @@ export namespace MessageV2 {
},
{ cause: e },
).toObject()
case APICallError.isInstance(e):
case APICallError.isInstance(e): {
const message = iife(() => {
let msg = e.message
const transformed = ProviderTransform.error(ctx.providerID, e)
@ -641,6 +645,7 @@ export namespace MessageV2 {
},
{ cause: e },
).toObject()
}
case e instanceof Error:
return new NamedError.Unknown({ message: e.toString() }, { cause: e }).toObject()
default:

View file

@ -170,8 +170,15 @@ export namespace Storage {
const target = path.join(dir, ...key) + ".json"
return withErrorHandling(async () => {
using _ = await Lock.read(target)
const result = await Bun.file(target).json()
return result as T
try {
const result = await Bun.file(target).json()
return result as T
} catch (e) {
if (e instanceof SyntaxError) {
throw new Error(`Failed to parse JSON in file ${target}: ${e.message}`)
}
throw e
}
})
}
@ -180,7 +187,17 @@ export namespace Storage {
const target = path.join(dir, ...key) + ".json"
return withErrorHandling(async () => {
using _ = await Lock.write(target)
const content = await Bun.file(target).json()
let content: T
try {
content = await Bun.file(target).json()
} catch (e) {
if (e instanceof SyntaxError) {
console.error(`Corrupted JSON in ${target}, resetting to empty object:`, e.message)
content = {} as T
} else {
throw e
}
}
fn(content)
await Bun.write(target, JSON.stringify(content, null, 2))
return content as T