fail multi-checkpoint resume on read errors · Entire

fail multi-checkpoint resume on read errors

2a1f64d→main·

pfleidi·3w ago·4 files·+44 added/-17 removed

Return checkpoint metadata read errors while resolving squash-merge checkpoint trailers instead of selecting another readable checkpoint.

Update integration and canary expectations to describe selecting the latest checkpoint only after all listed checkpoints were read successfully.

Sessions

8fb17d9e05ccView transcript

[?
Implement Checkpoint Policy Management SystemCodex·GPT-5.5·5 steps](/content/gh/entireio/cli/session/019ef111-70d5-7203-b653-e4834b8b92c0#timeline-8fb17d9e05cc/index.html)

Changes

4

1022 unmodified lines

1023
1024
1025
1026
1027
1028
1026
1027
1028
1029
1030
1031

1022 unmodified lines

t.Logf("Resume output:\n%s", output)

// Should show info about skipped checkpoints
    if !strings.Contains(output, "older checkpoints skipped") {
        t.Errorf("expected 'older checkpoints skipped' in output, got: %s", output)
    // Should show info about choosing the latest checkpoint.
    if !strings.Contains(output, "latest checkpoint") {
        t.Errorf("expected 'latest checkpoint' in output, got: %s", output)
    }

// Should only resume the latest session (session2), not session1

Mcmd/entire/cli/integration_test/resume_test.go+3/-3

299 unmodified lines

300
301
302
303
304
305
303
304
305
306
307
308
28 unmodified lines

337
338
339
340
340
341
342
343
344
345
346
347
348
346
347
348
349
353
350
351
352
353

299 unmodified lines

len(result.checkpointIDs), result.commitHash[:7])
        return checkRemoteMetadata(ctx, w, errW, result.checkpointIDs[0], stores.Refs())
    }
    skipped := len(result.checkpointIDs) - 1
    fmt.Fprintf(w, "Found %d checkpoints for commit %s, resuming from the latest readable checkpoint (%d skipped)\n",
        len(result.checkpointIDs), result.commitHash[:7], skipped)
    olderSkipped := len(result.checkpointIDs) - 1
    fmt.Fprintf(w, "Found %d checkpoints for commit %s, resuming from the latest checkpoint (%d older checkpoint(s) skipped)\n",
        len(result.checkpointIDs), result.commitHash[:7], olderSkipped)
    checkpointID = latestMetadata.CheckpointID
    metadata = latestMetadata
    
}
28 unmodified lines

// resolveLatestCheckpoint reads metadata for each checkpoint ID and returns the
// readable checkpoint with the latest CreatedAt.
// checkpoint with the latest CreatedAt.
func resolveLatestCheckpoint(ctx context.Context, store checkpointInfoReader, checkpointIDs []id.CheckpointID) (*strategy.CheckpointInfo, bool, error) {
    infoMap := make(map[id.CheckpointID]strategy.CheckpointInfo, len(checkpointIDs))
    for _, cpID := range checkpointIDs {
        metadata, readErr := readCheckpointInfoFromStore(ctx, store, cpID)
        if readErr != nil {
            if checkpointpolicy.IsUnsupportedVersion(readErr) {
                return nil, false, readErr
            }
            logging.Debug(ctx, "resolveLatestCheckpoint: checkpoint metadata read failed",
                slog.String("checkpoint_id", cpID.String()),
                slog.String("error", readErr.Error()),
            )
            continue
            return nil, false, readErr
        }
        infoMap[cpID] = *metadata
    }
}

Mcmd/entire/cli/resume.go+5/-8

2 unmodified lines

3
4
5
6
7
8
9
676 unmodified lines

686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
200 unmodified lines

921
922
923
894
924
925
926
927
22 unmodified lines

950
951
952
923
953
954
955
956

2 unmodified lines

import (
    "bytes"
    "context"
    "errors"
    "fmt"
    "io"
    "os"
676 unmodified lines

}
}

func TestResolveLatestCheckpointReturnsErrorWhenAnyCheckpointCannotBeRead(t *testing.T) {
    t.Parallel()

missingID := id.MustCheckpointID("aaa111bbb222")
    newID := id.MustCheckpointID("ccc333ddd444")
    reader := &resumeCheckpointInfoReaderStub{
        summaries: map[id.CheckpointID]*checkpoint.CheckpointSummary{
            newID: {Sessions: []checkpoint.SessionFilePaths{{Metadata: "new"}}},
        },
        metadata: map[id.CheckpointID][]checkpoint.CommittedMetadata{
            newID: {{
                SessionID: "new-session",
                CreatedAt: time.Date(2025, 1, 1, 11, 0, 0, 0, time.UTC),
            }},
        },
    }

_, found, err := resolveLatestCheckpoint(context.Background(), reader, []id.CheckpointID{missingID, newID})
    if err == nil {
        t.Fatal("resolveLatestCheckpoint() error = nil, want read error")
    }
    if found {
        t.Fatal("resolveLatestCheckpoint() found = true")
    }
    if !errors.Is(err, checkpoint.ErrCheckpointNotFound) {
        t.Fatalf("resolveLatestCheckpoint() error = %v, want checkpoint not found", err)
    }
}

type resumeCheckpointInfoReaderStub struct {
    summaries map[id.CheckpointID]*checkpoint.CheckpointSummary
    metadata  map[id.CheckpointID][]checkpoint.CommittedMetadata
200 unmodified lines

}
}

func TestResumeFromCurrentBranch_MultipleCheckpointsSaysLatestReadable(t *testing.T) {
func TestResumeFromCurrentBranch_MultipleCheckpointsSaysLatest(t *testing.T) {
tmpDir := t.TempDir()
    t.Chdir(tmpDir)
    t.Setenv("ENTIRE_TEST_CLAUDE_PROJECT_DIR", filepath.Join(tmpDir, "claude-projects"))
22 unmodified lines

t.Fatalf("resumeFromCurrentBranch() error = %v\nstdout: %s\nstderr: %s", err, stdout.String(), stderr.String())
    }

want := "resuming from the latest readable checkpoint"
    want := "resuming from the latest checkpoint"
    if !strings.Contains(stdout.String(), want) {
        t.Fatalf("stdout = %q, want substring %q", stdout.String(), want)
    }
}

Mcmd/entire/cli/resume_test.go+32/-2

121 unmodified lines

122
123
124
125
126
125
126
127
128
129
24 unmodified lines

154
155
156
157
158
157
158
159
160
161

121 unmodified lines

out, err := entire.Resume(s.Dir, mainBranch)
        require.NoError(t, err, "github format: entire resume failed: %s", out)
        assert.Contains(t, out, "older checkpoints skipped",
            "github format: squash merge should skip older checkpoints")
        assert.Contains(t, out, "latest checkpoint",
            "github format: squash merge should resume the latest checkpoint")

// Reset main to before the squash merge for the next format test.
        s.Git(t, "reset", "--hard", mainHead)
24 unmodified lines

out, err = entire.Resume(s.Dir, mainBranch)
        require.NoError(t, err, "git-cli format: entire resume failed: %s", out)
        assert.Contains(t, out, "older checkpoints skipped",
            "git-cli format: squash merge should skip older checkpoints")
        assert.Contains(t, out, "latest checkpoint",
            "git-cli format: squash merge should resume the latest checkpoint")
    })
}