( dir: string, )
| 81 | * keep re-prompting after the backend has already GC'd the env. |
| 82 | */ |
| 83 | export async function readBridgePointer( |
| 84 | dir: string, |
| 85 | ): Promise<(BridgePointer & { ageMs: number }) | null> { |
| 86 | const path = getBridgePointerPath(dir) |
| 87 | let raw: string |
| 88 | let mtimeMs: number |
| 89 | try { |
| 90 | // stat for mtime (staleness anchor), then read. Two syscalls, but both |
| 91 | // are needed — mtime IS the data we return, not a TOCTOU guard. |
| 92 | mtimeMs = (await stat(path)).mtimeMs |
| 93 | raw = await readFile(path, 'utf8') |
| 94 | } catch { |
| 95 | return null |
| 96 | } |
| 97 | |
| 98 | const parsed = BridgePointerSchema().safeParse(safeJsonParse(raw)) |
| 99 | if (!parsed.success) { |
| 100 | logForDebugging(`[bridge:pointer] invalid schema, clearing: ${path}`) |
| 101 | await clearBridgePointer(dir) |
| 102 | return null |
| 103 | } |
| 104 | |
| 105 | const ageMs = Math.max(0, Date.now() - mtimeMs) |
| 106 | if (ageMs > BRIDGE_POINTER_TTL_MS) { |
| 107 | logForDebugging(`[bridge:pointer] stale (>4h mtime), clearing: ${path}`) |
| 108 | await clearBridgePointer(dir) |
| 109 | return null |
| 110 | } |
| 111 | |
| 112 | return { ...parsed.data, ageMs } |
| 113 | } |
| 114 | |
| 115 | /** |
| 116 | * Worktree-aware read for `--continue`. The REPL bridge writes its pointer |
no test coverage detected