{"record":{"id":"bfb0afe9c6b9c502","repo":"can1357/oh-my-pi","slug":"session-append-failed-and-its-partial-bytes-could","errorCode":null,"errorMessage":"Session append failed and its partial bytes could not be rolled back","messagePattern":"Session append failed and its partial bytes could not be rolled back","errorType":"exception","errorClass":"AggregateError","httpStatus":null,"severity":"critical","filePath":"packages/coding-agent/src/session/session-storage.ts","lineNumber":146,"sourceCode":"\t}\n\n\t#writeNow(line: string): void {\n\t\tconst originalSize = fs.fstatSync(this.#fd).size;\n\t\tconst buf = Buffer.from(line, \"utf-8\");\n\t\tlet offset = 0;\n\t\ttry {\n\t\t\twhile (offset < buf.length) {\n\t\t\t\tconst written = fs.writeSync(this.#fd, buf, offset, buf.length - offset);\n\t\t\t\tif (written === 0) {\n\t\t\t\t\tthrow new Error(\"Short write\");\n\t\t\t\t}\n\t\t\t\toffset += written;\n\t\t\t}\n\t\t} catch (writeError) {\n\t\t\ttry {\n\t\t\t\tfs.ftruncateSync(this.#fd, originalSize);\n\t\t\t} catch (rollbackError) {\n\t\t\t\tthrow new AggregateError(\n\t\t\t\t\t[toError(writeError), toError(rollbackError)],\n\t\t\t\t\t\"Session append failed and its partial bytes could not be rolled back\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tthrow writeError;\n\t\t}\n\t}\n\n\tappendSync(line: string): void {\n\t\tif (this.#closed) throw new Error(\"Writer closed\");\n\t\tif (this.#error) throw this.#error;\n\t\t// Write in-body so software crash after the call still sees the entry.\n\t\t// Microtask batching used to leave completed transcript lines only in\n\t\t// memory until the next event-loop turn; process crash then lost every\n\t\t// post-checkpoint event. flush/flushSync remain no-op drains (no fsync).\n\t\ttry {\n\t\t\tthis.#writeNow(line);\n\t\t} catch (err) {","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/session/session-storage.ts#L128-L164","documentation":"When appendSync's write fails, SessionStorage tries ftruncateSync to roll the session file back to its original size so no partial JSONL bytes corrupt it. If the write AND the rollback both fail, it throws an AggregateError containing both errors with this message, because the session file is now left in a partially-written state that cannot be safely repaired automatically.","triggerScenarios":"writeSync fails (e.g. ENOSPC, EIO) and the subsequent ftruncateSync also fails — typically because the disk is still full (truncate needs a metadata write), the fd is broken, or the filesystem went read-only.","commonSituations":"Fully saturated disk on a long-running agent session where the original write fails on space and truncation also fails on the same condition; device errors or a volume remounted read-only mid-session.","solutions":["Free disk space or restore write access to the volume immediately — the session file may contain a partial line.","Manually inspect the session .jsonl file and delete the trailing incomplete line to restore a valid JSONL stream.","If the volume went read-only, remount it read-write (or move the session directory to a healthy disk) before continuing.","Copy/backup the session file before any manual repair, then let the app recreate entries from the last valid line."],"exampleFix":"// manual repair of the corrupted tail\nnode -e \"\n  const fs = require('fs');\n  const p = process.argv[1];\n  const lines = fs.readFileSync(p, 'utf8').split('\\n');\n  while (lines.length && (!lines[lines.length-1] || !safeJson(lines[lines.length-1]))) lines.pop();\n  fs.writeFileSync(p, lines.join('\\n') + '\\n');\n\" session-file.jsonl","handlingStrategy":"try-catch","validationCode":"const s = await statfs(sessionDir);\nif (s.bavail * s.bsize < 10 * 1024 * 1024) {\n  // refuse to run sessions on a nearly-full disk\n  throw new Error(\"Insufficient disk space for session storage\");\n}","typeGuard":"function isAggregateWriteFailure(err: unknown): err is AggregateError {\n  return err instanceof AggregateError &&\n    err.message.includes(\"partial bytes could not be rolled back\");\n}","tryCatchPattern":"try {\n  storage.appendSync(line);\n} catch (err) {\n  if (isAggregateWriteFailure(err)) {\n    for (const e of err.errors) logger.error(\"Session append + rollback failed\", { e });\n    // session file may hold a partial line — quarantine/validate it before further use\n  } else throw err;\n}","preventionTips":["Keep ample free space on the session volume; truncation itself needs write access.","Never let the volume go read-only mid-session.","Back up session files before manual repair of a partial line.","Treat this error as data-integrity critical: validate the JSONL tail before reading the session."],"tags":["filesystem","io","data-integrity","session"],"backgroundTag":"write-rollback-failed","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}