Problem
programa snapshot restore can kill the app instead of recovering it. If an archived snapshot in session-history/ contains two panels sharing a UUID, window reconstruction reaches Dictionary(uniqueKeysWithValues:) at Sources/Workspace+Persistence.swift:104, which traps on duplicate keys, and the app dies on a fatal error.
This is the command you reach for when a layout is already lost, so crashing here costs more than a crash anywhere else.
v2SnapshotRestore (Sources/TerminalController+Snapshot.swift:30-60) checks that the file decodes, that version matches the current schema, that windows is non-empty, and since #228 it clamps the window count. It never checks that panel IDs are unique within a workspace.
The same trap is reachable from startup restore reading session-<bundleId>.json, so this is not new behaviour. Snapshot restore adds a second door to it, and a worse one, because the input is a file the user may have been editing by hand while trying to recover.
Repro
- Launch programa, let it save a session, then relaunch so the file is archived into
~/Library/Application Support/programa/session-history/.
- Open the newest archive and duplicate one entry inside a workspace's
panels array, keeping the id identical.
- Run
programa snapshot restore <id> for that archive.
- The app terminates on a duplicate key fatal error instead of returning an error.
Fix sketch
Validate the decoded snapshot before handing windows to createMainWindow: reject workspaces whose panels contain repeated IDs and return an invalid_params error, or de-duplicate and keep the first. Putting it in one shared validator would cover the startup path at the same time.
Found while reviewing #228.
Problem
programa snapshot restorecan kill the app instead of recovering it. If an archived snapshot insession-history/contains two panels sharing a UUID, window reconstruction reachesDictionary(uniqueKeysWithValues:)atSources/Workspace+Persistence.swift:104, which traps on duplicate keys, and the app dies on a fatal error.This is the command you reach for when a layout is already lost, so crashing here costs more than a crash anywhere else.
v2SnapshotRestore(Sources/TerminalController+Snapshot.swift:30-60) checks that the file decodes, thatversionmatches the current schema, thatwindowsis non-empty, and since #228 it clamps the window count. It never checks that panel IDs are unique within a workspace.The same trap is reachable from startup restore reading
session-<bundleId>.json, so this is not new behaviour. Snapshot restore adds a second door to it, and a worse one, because the input is a file the user may have been editing by hand while trying to recover.Repro
~/Library/Application Support/programa/session-history/.panelsarray, keeping theididentical.programa snapshot restore <id>for that archive.Fix sketch
Validate the decoded snapshot before handing windows to
createMainWindow: reject workspaces whosepanelscontain repeated IDs and return aninvalid_paramserror, or de-duplicate and keep the first. Putting it in one shared validator would cover the startup path at the same time.Found while reviewing #228.