graphcode reap has been dead since #308 shipped in 0.1.64-beta3 today. It aborts on every workspace, permanently, and it fails closed behind a message that reads as caution rather than breakage — so it is unlikely to be reported by whoever hits it.
$ graphcode reap --dry-run
graphcode: a graph or terminal layout failed to decode — refusing to guess which sessions it owns
This is a stable blocker. reap is the on-demand recovery tool for a machine that has run out of PTYs (#197). It is broken precisely when someone needs it most.
Cause
OrphanedSessionReaper.liveSessionIDs treats every .json under a workspace's projects/ directory as a LoopGraph, and returns nil — refusing to reap — if any one of them fails to decode:
let graphFiles = (try? FileManager.default.contentsOfDirectory(at: projects, ...))?
.filter { $0.pathExtension == "json" } ?? []
for file in graphFiles {
guard let data = try? Data(contentsOf: file),
let graph = try? JSONDecoder().decode(LoopGraph.self, from: data)
else { return nil }
...
}
That assumption was true until #307/#308 moved the Mailroom out of the graph file and into <project>.mailroom.json in that same directory. The room file is a JSON array of posts, so it can never decode as a LoopGraph, and every reap now bails.
Refusing to guess is the right behaviour for a genuinely damaged graph — a graph this build cannot read still owns sessions, and reaping them would be a sweep rather than a reap. The defect is that a sidecar is not a damaged graph.
Evidence
Reproduced on 0.1.64-beta5 (build 255). To rule out a coincidentally corrupt graph, every .json under projects/ in all four workspaces on this machine (~/.graphcode, ~/.graphcode-opencode, ~/.graphcode-client, and the current override) was decoded individually:
| Workspace |
projects/*.json |
Decodes as a graph |
~/.graphcode |
16 |
16 |
~/.graphcode-opencode |
3 |
2 |
~/.graphcode-client |
4 |
4 |
The single exception is _Volumes_SCG_wd_graphcode.mailroom.json — a JSON array of 386 posts. It is the only non-graph .json in any workspace, so it is the cause and not a coincidence.
The abort is also workspace-independent: liveSessionIDs scans every discovered workspace, so one room file anywhere disables reap everywhere, including from a support dir that has no room file of its own. Confirmed by running reap --dry-run against an isolated GRAPHCODE_SUPPORT_DIR holding only a valid graph — it still aborted.
Blast radius
- Fails closed: nothing is killed, no data is lost. The tool is simply unavailable.
Workspace.contents() has the same "every .json in projects/ is a graph" assumption but continues instead of bailing, so it is not user-visible today — it is the next place this bites.
Fix direction
Not a filename blocklist in the reaper — that breaks again the next time a sidecar is added. The rule should be positive and owned by ProjectPersistence, which mints both names, and it must keep failing closed for a file that is genuinely corrupt.
Regression from #308 (11e33a3), shipped in 0.1.64-beta3.
Found while verifying #307's fix (mailroom split + GraphWriter) on beta5.
graphcode reaphas been dead since #308 shipped in 0.1.64-beta3 today. It aborts on every workspace, permanently, and it fails closed behind a message that reads as caution rather than breakage — so it is unlikely to be reported by whoever hits it.This is a stable blocker.
reapis the on-demand recovery tool for a machine that has run out of PTYs (#197). It is broken precisely when someone needs it most.Cause
OrphanedSessionReaper.liveSessionIDstreats every.jsonunder a workspace'sprojects/directory as aLoopGraph, and returnsnil— refusing to reap — if any one of them fails to decode:That assumption was true until #307/#308 moved the Mailroom out of the graph file and into
<project>.mailroom.jsonin that same directory. The room file is a JSON array of posts, so it can never decode as aLoopGraph, and every reap now bails.Refusing to guess is the right behaviour for a genuinely damaged graph — a graph this build cannot read still owns sessions, and reaping them would be a sweep rather than a reap. The defect is that a sidecar is not a damaged graph.
Evidence
Reproduced on 0.1.64-beta5 (build 255). To rule out a coincidentally corrupt graph, every
.jsonunderprojects/in all four workspaces on this machine (~/.graphcode,~/.graphcode-opencode,~/.graphcode-client, and the current override) was decoded individually:projects/*.json~/.graphcode~/.graphcode-opencode~/.graphcode-clientThe single exception is
_Volumes_SCG_wd_graphcode.mailroom.json— a JSON array of 386 posts. It is the only non-graph.jsonin any workspace, so it is the cause and not a coincidence.The abort is also workspace-independent:
liveSessionIDsscans every discovered workspace, so one room file anywhere disables reap everywhere, including from a support dir that has no room file of its own. Confirmed by runningreap --dry-runagainst an isolatedGRAPHCODE_SUPPORT_DIRholding only a valid graph — it still aborted.Blast radius
Workspace.contents()has the same "every.jsoninprojects/is a graph" assumption butcontinues instead of bailing, so it is not user-visible today — it is the next place this bites.Fix direction
Not a filename blocklist in the reaper — that breaks again the next time a sidecar is added. The rule should be positive and owned by
ProjectPersistence, which mints both names, and it must keep failing closed for a file that is genuinely corrupt.Regression from #308 (
11e33a3), shipped in 0.1.64-beta3.Found while verifying #307's fix (mailroom split +
GraphWriter) on beta5.