recover: say when a snapshot is a before-image - #162
Merged
Conversation
Reported as lost work: an agent wrote a file, was killed before committing, and the restored branch did not contain it. The restore was correct. The snapshot never held the file, because it was a *baseline* — the workspace as the run started, captured before the agent ran — and nothing said so. A run started from Studio records only that: studioapi takes one before-image at launch and closes the session immediately. So killing an agent before it commits leaves exactly one session, holding the state from before it did anything. On the reporter's machine every one of seven OneNutri sessions was a baseline, and `recover list` reported all of them as `clean` — the same word a finished run's snapshot gets. The daemon already guards against this: #150 taught it to skip baselines when looking for something to recover and to hide them from the snapshot listing. That fix never reached internal/cli, which did no filtering on Outcome at all. Same defect, other front end. `OutcomeBaseline` moves to internal/rescue beside the other outcomes, and studioapi's private literal now refers to it. Its comment there was already right — "a literal that has to match in three places is one typo away from offering a run's starting state as its work" — and it had matched in two, which is how the CLI came to be blind to a value the daemon has been writing for weeks. `Status()` names it, ahead of the closed-session case that was swallowing it, and `IsBaseline` is the question callers actually ask. The listing counts them and says what they are; the restore says it *before* the git commands it prints, because it changes what those commands are for — somebody who reads "created branch X" and goes looking will find the state they had before the run and conclude the snapshot lost their work rather than that it never held it. Marked rather than hidden, and that is a deliberate difference from the daemon. Studio hides them because its screen offers a Restore button beside every row. This listing is what somebody reads while hunting for lost work, where "no snapshots recorded" is the worse answer: there *was* a run, and this is all it left. Claude-Session: https://claude.ai/code/session_011ba7PUJ23wb1V1aBhVbnE8
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Three findings. The first is the one that mattered: the baseline notice printed in the branch case alone, which left it out of the two modes where it costs most. `--into-worktree` **overwrites**. Restoring a baseline there replaces the tree with the state from before the run and then prints "Keep it with: git commit" — so somebody hunting for missing work could commit the pre-run state over the agent's, which is a sharper version of the exact report this PR exists to prevent. `--patch` writes a diff nothing labels either, and `recover show` streams one with nothing saying which side of the run it is from. The notice is now printed before the mode-specific output, so every mode carries it, and `show` says it before handing off to git — after that point git streams its own output and anything appended scrolls past. Two smaller ones, both in text I had just written. "%d of these are baselines" reads "1 of these are baselines" in the *common* case, since a daemon run leaves exactly one session; the singular is not an edge worth skipping. And the same line emphasised a word with markdown asterisks, which a terminal prints literally — `printFindings` a few lines up already uses ANSI for the same job. `docs/usage/recovery.md` and `docs/GUIDE.md` both describe the STATE column and neither listed the new value, while the website copy added alongside did. The sample listing in those docs is what people compare their own output against, so it now shows a baseline row and says what the values mean. Claude-Session: https://claude.ai/code/session_011ba7PUJ23wb1V1aBhVbnE8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reported as lost work: an agent wrote a file, was killed before committing, and the restored branch did not contain it.
The restore was correct. The snapshot never held the file, because it was a baseline — the workspace as the run started, captured before the agent ran — and nothing said so.
Why every recent snapshot is one
A run started from Studio records only a baseline: the daemon takes one before-image at launch and closes the session immediately. So killing an agent before it commits leaves exactly one session, holding the state from before it did anything.
On the reporter's machine all seven OneNutri sessions were baselines, and
recover listreported every one asclean— the same word a finished run's snapshot gets:Same defect, other front end
The daemon already guards against this — #150 taught it to skip baselines when looking for something to recover and to hide them from the listing. That fix never reached
internal/cli, which did no filtering onOutcomeat all.runs.go's comment beside the literal was already right:It matched in two.
OutcomeBaselinenow lives ininternal/rescuebeside the other outcomes, and the daemon's private literal refers to it — which is what makes the third place impossible to forget.What changed
Status()names it, ahead of the closed-session case that was swallowing it.IsBaseline()is the question callers actually ask.Marked, not hidden — deliberately different from the daemon
Studio hides baselines because its screen offers a Restore button beside every row. This listing is what somebody reads while hunting for lost work, where "no snapshots recorded" is the worse answer: there was a run, and this is all it left.
Not fixed here
That a Studio run has no periodic snapshots at all is the real gap, and it is deliberate-for-now — see
docs/security/open-items.md. This change makes it legible; it does not close it. Raised separately with the repro.🤖 Generated with Claude Code
https://claude.ai/code/session_011ba7PUJ23wb1V1aBhVbnE8