feat(import): refuse rewind to imported checkpoints with clear message · Entire
feat(import): refuse rewind to imported checkpoints with clear message
abc9840→main·
computermode·3w ago·2 files·+104 added/-0 removed
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Sessions
9643642e02f9View transcript
[?
Implement Claude History Import FeatureClaude Code·Opus 4.8·3 steps](/content/gh/entireio/cli/session/897809d4-ecab-4dc6-aa21-97661cb62607#timeline-9643642e02f9/index.html)
Changes
2
cmd/entire/cli
Mrewind.go+36
- Arewind_imports_test.go+68
411 unmodified lines
411 unmodified lines
return runRewindToInternal(ctx, w, errW, commitID, logsOnly, reset)
// refuseIfImportedCheckpoint blocks rewinding to imported (read-only,
// commit-less) checkpoints on entire/imports/v1. It matches commitID against
// imported checkpoint IDs (full or >=7-char prefix). Best-effort: when the
// imports ref is absent or unreadable it returns nil so normal rewind proceeds.
func refuseIfImportedCheckpoint(ctx context.Context, errW io.Writer, commitID string) error {
repo, err := strategy.OpenRepository(ctx)
if err != nil {
return nil //nolint:nilerr // fall through to normal rewind resolution
}
defer repo.Close()
importsRefs := checkpoint.ImportsRefs()
stores, err := checkpoint.Open(ctx, repo, checkpoint.OpenOptions{Refs: &importsRefs})
if err != nil {
return nil //nolint:nilerr
}
infos, err := stores.Persistent.List(ctx)
if err != nil {
return nil //nolint:nilerr
}
for _, in := range infos {
idStr := in.CheckpointID.String()
if idStr == commitID || (len(commitID) >= 7 && strings.HasPrefix(idStr, commitID)) {
fmt.Fprintln(errW, "This checkpoint was imported from existing agent history. Imported history is read-only and not rewindable.")
return NewSilentError(errors.New("rewind refused: imported checkpoint"))
}
}
return nil
}
func runRewindToInternal(ctx context.Context, w, errW io.Writer, commitID string, logsOnly bool, reset bool) error {
start := GetStrategy(ctx)
// Imported history is read-only: refuse rewinding to it with a clear message
// rather than a confusing "rewind point not found".
if err := refuseIfImportedCheckpoint(ctx, errW, commitID); err != nil {
return err
}
// Check for uncommitted changes (skip for reset which handles this itself)
if !reset {
canRewind, changeMsg, err := start.CanRewind(ctx)
Mcmd/entire/cli/rewind.go+36
package cli
import (
"bytes"
"context"
"strings"
"testing"
"github.com/go-git/go-git/v6"
"github.com/go-git/go-git/v6/plumbing/object"
"github.com/entireio/cli/cmd/entire/cli/checkpoint"
"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
"github.com/entireio/cli/cmd/entire/cli/testutil"
"github.com/entireio/cli/redact"
)
func TestRefuseIfImportedCheckpoint(t *testing.T) {
// Not parallel: uses t.Chdir for CWD-based repo resolution.
repoDir := t.TempDir()
testutil.InitRepo(t, repoDir)
repo, err := git.PlainOpen(repoDir)
if err != nil {
t.Fatal(err)
}
wt, err := repo.Worktree()
if err != nil {
t.Fatal(err)
}
testutil.WriteFile(t, repoDir, "f.txt", "x")
if _, err := wt.Add("f.txt"); err != nil {
t.Fatal(err)
}
if _, err := wt.Commit("init", &git.CommitOptions{
Author: &object.Signature{Name: "Test", Email: "test@test.com"},
}); err != nil {
t.Fatal(err)
}
t.Chdir(repoDir)
cid := id.MustCheckpointID("aabbccddeeff")
store := checkpoint.NewGitStore(repo, checkpoint.ImportsRefs())
red, err := redact.JSONLBytes([]byte(`{"type":"user","uuid":"u1","message":{"role":"user","content":"hi"}}` + "\n"))
if err != nil {
t.Fatal(err)
}
if err := store.Write(context.Background(), checkpoint.Session(checkpoint.WriteOptions{
CheckpointID: cid, SessionID: "s", Strategy: "import", Kind: "imported",
Transcript: red, Prompts: []string{"hi"}, CheckpointsCount: 1,
})); err != nil {
t.Fatal(err)
}
var out bytes.Buffer
err = refuseIfImportedCheckpoint(context.Background(), &out, cid.String())
if err == nil {
t.Fatal("expected refusal error for imported checkpoint")
}
if !strings.Contains(out.String(), "read-only and not rewindable") {
t.Fatalf("missing clear refusal message, got: %q", out.String())
}
// A non-imported ID must not be refused.
out.Reset()
if err := refuseIfImportedCheckpoint(context.Background(), &out, "ffffffffffff"); err != nil {
t.Fatalf("non-imported id should not be refused: %v", err)
}
}