From 64a0b65adc0c0171345fe36003ec2c22d6330410 Mon Sep 17 00:00:00 2001 From: AntSentry <13_flits_mansion@icloud.com> Date: Thu, 6 Aug 2026 10:45:55 -0700 Subject: [PATCH] fix(ModelRungGuard): seek the transcript tail instead of reading the whole file liveModel() computes a 256KB tail offset and then readFileSync's the entire transcript to reach it - O(session length) work for a fixed-size read. The hook runs on every UserPromptSubmit, so the cost grows with the session and only bites late in long ones, where it presents as a flaky hook timeout rather than a bug. Measured on a 152MB transcript (isolated processes, warm cache): before 22.1 ms/call 183 MB RSS after 0.1 ms/call 30 MB RSS Behaviour-preserving: a differential run of the old and new implementations over every transcript >100KB on one machine agrees on all 7763 files, covering the null path, partial trailing lines, and files smaller than the tail window. Nothing else in the file changes. --- LifeOS/install/hooks/ModelRungGuard.hook.ts | 29 ++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/LifeOS/install/hooks/ModelRungGuard.hook.ts b/LifeOS/install/hooks/ModelRungGuard.hook.ts index 2e0b1c75e9..b37e22b3d1 100755 --- a/LifeOS/install/hooks/ModelRungGuard.hook.ts +++ b/LifeOS/install/hooks/ModelRungGuard.hook.ts @@ -35,7 +35,16 @@ for (const __k of ["LIFEOS_DIR", "LIFEOS_CONFIG_DIR", "PROJECTS_DIR"]) { * Failure mode: any error logs to stderr and exits 0, never blocking prompts. */ -import { appendFileSync, existsSync, mkdirSync, readFileSync, statSync } from "node:fs"; +import { + appendFileSync, + closeSync, + existsSync, + mkdirSync, + openSync, + readFileSync, + readSync, + statSync, +} from "node:fs"; import { join } from "node:path"; const STDIN_TIMEOUT_MS = 300; @@ -115,8 +124,22 @@ export function liveModel(transcriptPath: string | undefined): string | null { try { if (!transcriptPath || !existsSync(transcriptPath)) return null; const size = statSync(transcriptPath).size; - const fd = readFileSync(transcriptPath); - const tail = fd.subarray(Math.max(0, size - TAIL_BYTES)).toString("utf8"); + // Seek to the tail rather than reading the whole file. `readFileSync` here + // cost O(session length) to keep a fixed 256 KB: on a 152 MB transcript that + // measured 22.1 ms and 183 MB RSS per prompt, against 0.1 ms and 30 MB for + // the seek. This hook runs on every UserPromptSubmit, so the cost grows with + // session length and only bites late in long sessions — where it reads as a + // flaky hook timeout rather than a bug. + const start = Math.max(0, size - TAIL_BYTES); + const buf = Buffer.allocUnsafe(Math.min(size, TAIL_BYTES)); + const fd = openSync(transcriptPath, "r"); + let read = 0; + try { + read = readSync(fd, buf, 0, buf.length, start); + } finally { + closeSync(fd); + } + const tail = buf.subarray(0, read).toString("utf8"); const lines = tail.split("\n").filter((l) => l.trim().length > 0); for (let i = lines.length - 1; i >= 0; i--) { try {