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

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)
    }
}