Support resuming from squash merge commits with multiple checkpoints#534
Support resuming from squash merge commits with multiple checkpoints#534gtrrz-victor merged 25 commits intomainfrom
Conversation
Change checkpointID field to checkpointIDs slice, update findBranchCheckpoint and findCheckpointInHistory to use ParseAllCheckpoints, and add test for squash merge commits with multiple Entire-Checkpoint trailers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: e49c84c2d596
Add ParseAllCheckpoints() to trailers package for extracting all Entire-Checkpoint trailers from squash merge commits. Add resumeMultipleCheckpoints() to resume.go that iterates over all checkpoint IDs, restores sessions for each, and displays aggregated resume commands. Refactor test helper to support custom checkpoint IDs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 43abb5b45ab5
Entire-Checkpoint: c585b5e466a1
PR SummaryMedium Risk Overview The new multi-checkpoint path sorts checkpoints by metadata Adds Written by Cursor Bugbot for commit b0d0093. Configure here. |
There was a problem hiding this comment.
Pull request overview
Adds support for resuming sessions from squash-merge commits that contain multiple Entire-Checkpoint trailers, enabling entire resume <branch> to restore more than one session when multiple checkpoints are embedded in a single commit message.
Changes:
- Add
trailers.ParseAllCheckpoints()to extract and deduplicate multiple checkpoint trailers from a commit message. - Update resume branch-checkpoint discovery to return multiple checkpoint IDs and add a multi-checkpoint resume flow.
- Add unit + integration tests (including a squash-merge simulation) and a new integration test helper to create commits with multiple checkpoint trailers.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| cmd/entire/cli/trailers/trailers.go | Adds ParseAllCheckpoints() to return all checkpoint trailers (deduped, ordered). |
| cmd/entire/cli/trailers/trailers_test.go | Adds unit tests covering single/multiple/dedup/invalid/mixed checkpoint trailers. |
| cmd/entire/cli/resume.go | Switches checkpoint discovery to multiple IDs and introduces resumeMultipleCheckpoints(). |
| cmd/entire/cli/resume_test.go | Adds unit tests for multiple-checkpoint history/branch discovery and a helper to create distinct checkpoint metadata. |
| cmd/entire/cli/integration_test/testenv.go | Adds GitCommitWithMultipleCheckpoints() helper for squash-merge style commit messages. |
| cmd/entire/cli/integration_test/resume_test.go | Adds integration test validating restore from squash-merge commit containing two checkpoints. |
The sorting, header formatting, and resume command printing was duplicated between resumeSession and resumeMultipleCheckpoints. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 0ff68845c557
RestoreLogsOnly already resolves agents per-session from session-level metadata, so an unknown checkpoint-level agent shouldn't block the restore attempt. The session dir creation was also redundant since RestoreLogsOnly handles it internally. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 831e4afa5e83
Split the error and empty-sessions cases into separate log entries so the actual failure reason is visible in debug logs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 932c2642d8ac
When a single session spans multiple commits, different checkpoint IDs can contain the same session. In a squash merge this would produce duplicate resume commands. Keep the entry with the latest CreatedAt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: e92b8f8525e9
|
As someone who prefers¹ squash-merge to messy merge-tennis with careless commit messages, this effort really pleases me.
|
The inline dedup logic in resumeMultipleCheckpoints did not update the seen map's CreatedAt after replacing a session, so a third occurrence could incorrectly overwrite the newest entry. Extract to a standalone function with a correct update and add targeted unit tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Entire-Checkpoint: 7132c7b6328a
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Entire-Checkpoint: 27bbbac46736
…uash-merge-resume
Entire-Checkpoint: 9c67547d59c3
Entire-Checkpoint: 8664f2d41cb3
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…uash-merge-resume Entire-Checkpoint: 4ff8bd5bc20c
Entire-Checkpoint: dc311b24d1d0
Entire-Checkpoint: f0894592c6ef
Entire-Checkpoint: dc723823f0b4
Entire-Checkpoint: af287eac4686
|
@cursor review |
Add end2end tests for `entire resume` for squash merged commits
Summary
entire resumeon squash merge commits that contain multipleEntire-CheckpointtrailersSessionIDacross checkpoints, keeping the most recent transcriptProblem
When a feature branch with multiple commits (each with its own
Entire-Checkpointtrailer) is squash-merged, all trailers end up in a single commit message.entire resumepreviously only parsed the first trailer, losing all other sessions.Additionally, GitHub squash merges list trailers chronologically (oldest first), but git CLI squash merges list them in reverse order (newest first). Since
RestoreLogsOnlywrites session files eagerly, the last checkpoint processed wins on disk — meaning reverse-ordered trailers caused the oldest transcript to overwrite the newest.Changes
cmd/entire/cli/trailers/trailers.go— AddParseAllCheckpoints()to extract allEntire-Checkpointtrailers from a commit message (not just the first).cmd/entire/cli/resume.go—ParseCheckpoint(single) toParseAllCheckpoints(multi) throughoutfindBranchCheckpointandfindCheckpointInHistorybranchCheckpointResult.checkpointID→checkpointIDs []CheckpointIDresumeMultipleCheckpoints: reads metadata for all checkpoint IDs, sorts byCreatedAtascending, then restores in order so the newest checkpoint always writes lastdeduplicateSessions: merges sessions across checkpoints, keeping the one with the latestCreatedAtwhen aSessionIDappears in multiple checkpointsdisplayRestoredSessionsto share session display logic between single and multi-checkpoint pathscmd/entire/cli/resume_test.go— Add unit tests fordeduplicateSessions(including three-occurrence staleness edge case),findCheckpointInHistorywith multiple trailers,findBranchCheckpointwith squash merge, and checkpoint timestamp sorting.cmd/entire/cli/integration_test/resume_test.go— Add integration test simulating a full squash merge workflow: two sessions on a feature branch, squash merge to main, thenentire resumerestoring both sessions.cmd/entire/cli/trailers/trailers_test.go— Add tests forParseAllCheckpoints.Caveat: Optimization
I thought about optimizing this process by only restoring the very latest checkpoint, but realized that this wouldn't work as expected:
A committed checkpoint only contains the sessions that were condensed at that specific commit. When a feature branch is squash merged, Git/GitHub preserves all Entire-Checkpoint trailers from the original commits in the squash commit body. Each trailer points to a different checkpoint on entire/checkpoints/v1, and different checkpoints may contain different sessions.
Since there is no single checkpoint that aggregates all sessions from the branch, the resume command must iterate over every checkpoint ID found in the squash commit and restore each one. Sessions are de-duplicated by ID, keeping the entry with the latest CreatedAt.
Optimizing by only restoring the latest checkpoint would silently drop any session whose last activity was on an earlier commit.
Test plan
mise run test:cipasses (unit + integration)TestResume_SquashMergeMultipleCheckpointscovers the end-to-end squash merge flowTestResumeMultipleCheckpoints_SortsByCreatedAtverifies reverse-ordered checkpoints are sorted correctlydeduplicateSessionscover no-duplicates, newer-wins, older-loses, three-occurrence, and mixed scenarios