You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Press re-review, have the run fail, and every comment you wrote by hand — plus every AI comment you rewrote — is gone from disk. Not hidden, not recoverable: deleted.
The failure does not have to be exotic. A model error, a network blip, a checkout that goes wrong mid-run, claude not being logged in — anything that throws after the run has started. The row comes back as failed, and your writing is not in it.
This contradicts the rule the repo states explicitly in CLAUDE.md:
Don't let a re-review discard human work: comments the user wrote or edited are carried across (carryOverComments), never regenerated away.
That guarantee currently holds only when the run succeeds.
Why it happens
reviewPr (src/runner/review.ts) writes the artifact three times, and the ordering is the bug:
Before calling Claude it builds a fresh artifact literal with comments: [] (:176-207) and saves it (:211). The user's comments leave the disk here — the file now holds an empty list. Everything that protects them from this point on lives in memory, in the existing variable.
On success, carryOverComments(existing, artifact) (:269) puts them back, re-anchored to the new diff. This is the path the rule describes, and it works.
On failure, the catch handler (:278-289) spreads that same pre-run object — still comments: [] — sets status: "failed", and saves. carryOverComments is never reached.
So the human comments are destroyed at step 1 unconditionally, and restored only by step 2. Step 3 makes the deletion permanent.
Reproducing it
// a ready artifact holding one comment the user wroteawaitsaveArtifact(withUserComment("old-sha"));expect((awaitloadArtifact(ID))!.comments).toHaveLength(1);prInfo.mockResolvedValue(pr("new-sha"));claude.mockRejectedValue(newError("model unavailable"));awaitexpect(reviewPr(REF,{withSource: false})).rejects.toThrow();constafter=(awaitloadArtifact(ID))!;expect(after.status).toBe("failed");expect(after.comments).toHaveLength(0);// passes — the comment is gone
Both assertions pass today. Observed output: status=failed comments on disk=0.
Suggested fix
The failure path needs the same carry-over the success path has — the human comments are known (humanComments(existing), src/core/refresh.ts) and nothing about a failed run makes them less valid.
Options, roughly in order of how much they change:
Minimal: in the catch handler, restore the human comments from existing before saving the failed artifact.
Better: don't drop them in the first place. Seed the pre-run artifact from humanComments(existing) rather than [], so no window exists where the file is missing the user's writing. The success path's carryOverComments then re-anchors as it already does.
Belt and braces: have the failure write merge onto what is on disk rather than saving a whole object built minutes earlier — the same discipline mergeConcurrentEdits (src/core/revise.ts) applies to chat turns, and the same one Send during an in-flight run can post the review to GitHub twice #36 needs.
Wants a regression test on the failing path specifically: user comment in, run throws, comment still on disk.
Scope
Found while verifying docs/lifecycle.md (#34) against the code. Third of three defects that review turned up; see also #35 and #36. Priority is set higher than those two because this one silently destroys the user's own writing, and there is no way to get it back.
The problem
Press re-review, have the run fail, and every comment you wrote by hand — plus every AI comment you rewrote — is gone from disk. Not hidden, not recoverable: deleted.
The failure does not have to be exotic. A model error, a network blip, a checkout that goes wrong mid-run,
claudenot being logged in — anything that throws after the run has started. The row comes back asfailed, and your writing is not in it.This contradicts the rule the repo states explicitly in
CLAUDE.md:That guarantee currently holds only when the run succeeds.
Why it happens
reviewPr(src/runner/review.ts) writes the artifact three times, and the ordering is the bug:comments: [](:176-207) and saves it (:211). The user's comments leave the disk here — the file now holds an empty list. Everything that protects them from this point on lives in memory, in theexistingvariable.carryOverComments(existing, artifact)(:269) puts them back, re-anchored to the new diff. This is the path the rule describes, and it works.:278-289) spreads that same pre-run object — stillcomments: []— setsstatus: "failed", and saves.carryOverCommentsis never reached.So the human comments are destroyed at step 1 unconditionally, and restored only by step 2. Step 3 makes the deletion permanent.
Reproducing it
Both assertions pass today. Observed output:
status=failed comments on disk=0.Suggested fix
The failure path needs the same carry-over the success path has — the human comments are known (
humanComments(existing),src/core/refresh.ts) and nothing about a failed run makes them less valid.Options, roughly in order of how much they change:
existingbefore saving thefailedartifact.humanComments(existing)rather than[], so no window exists where the file is missing the user's writing. The success path'scarryOverCommentsthen re-anchors as it already does.mergeConcurrentEdits(src/core/revise.ts) applies to chat turns, and the same one Send during an in-flight run can post the review to GitHub twice #36 needs.Wants a regression test on the failing path specifically: user comment in, run throws, comment still on disk.
Scope
Found while verifying
docs/lifecycle.md(#34) against the code. Third of three defects that review turned up; see also #35 and #36. Priority is set higher than those two because this one silently destroys the user's own writing, and there is no way to get it back.