Skip to content

Commit 59be645

Browse files
committed
feat(import): plumb commit_sha anchor through checkpoint writes
Adds WriteOptions.CommitSHA, persisted as commit_sha on both the session Metadata and root CheckpointSummary (omitempty, so non-import writers stay byte-identical). agentimport.Options gains LinkCommitSHA, copied verbatim into each imported turn's WriteOptions.CommitSHA; resolution of the actual SHA is left to a later task. Entire-Checkpoint: 01KY33NAEBCQFV87TK2QZM2XYD
1 parent f502d6b commit 59be645

5 files changed

Lines changed: 192 additions & 9 deletions

File tree

api/checkpoint/metadata.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ type WriteOptions struct {
3737
// Branch is the branch name where the checkpoint was created (empty if detached HEAD)
3838
Branch string
3939

40+
// CommitSHA links this checkpoint to an existing commit without a trailer.
41+
// Set only by `entire import`: imported history has no Entire-Checkpoint
42+
// trailer (we never rewrite existing commits), so import stamps the default
43+
// branch's head SHA here as an anchor — "imported at this point in time" —
44+
// not attribution. Empty for all other writers.
45+
CommitSHA string
46+
4047
// Transcript is the session transcript content (full.jsonl).
4148
// Must be pre-redacted (via redact.JSONLBytes or redact.AlreadyRedacted for trusted sources).
4249
Transcript redact.RedactedBytes
@@ -316,13 +323,17 @@ type SessionContent struct {
316323

317324
// Metadata contains the metadata stored in metadata.json for each checkpoint.
318325
type Metadata struct {
319-
CLIVersion string `json:"cli_version,omitempty"`
320-
CheckpointID id.CheckpointID `json:"checkpoint_id"`
321-
SessionID string `json:"session_id"`
322-
Strategy string `json:"strategy"`
323-
CreatedAt time.Time `json:"created_at"`
324-
Branch string `json:"branch,omitempty"` // Branch where checkpoint was created (empty if detached HEAD)
325-
CheckpointsCount int `json:"checkpoints_count"`
326+
CLIVersion string `json:"cli_version,omitempty"`
327+
CheckpointID id.CheckpointID `json:"checkpoint_id"`
328+
SessionID string `json:"session_id"`
329+
Strategy string `json:"strategy"`
330+
CreatedAt time.Time `json:"created_at"`
331+
Branch string `json:"branch,omitempty"` // Branch where checkpoint was created (empty if detached HEAD)
332+
// CommitSHA anchors an imported checkpoint to an existing commit (the
333+
// default branch head at import time). Empty for non-imported checkpoints,
334+
// which link to commits via the Entire-Checkpoint trailer instead.
335+
CommitSHA string `json:"commit_sha,omitempty"`
336+
CheckpointsCount int `json:"checkpoints_count"`
326337
// SaveStepCount is the number of SaveStep-recorded steps for this session.
327338
// Honest "real checkpoint work happened" signal (0 = commit-only/fallback
328339
// session), kept separate from the displayed CheckpointsCount prompt count.
@@ -478,6 +489,7 @@ type CheckpointSummary struct {
478489
CheckpointID id.CheckpointID `json:"checkpoint_id"`
479490
Strategy string `json:"strategy"`
480491
Branch string `json:"branch,omitempty"`
492+
CommitSHA string `json:"commit_sha,omitempty"`
481493
CheckpointsCount int `json:"checkpoints_count"`
482494
FilesTouched []string `json:"files_touched"`
483495
Sessions []SessionFilePaths `json:"sessions"`

cmd/entire/cli/agentimport/agentimport.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@ type Options struct {
9898
SessionFilter []string
9999
Now time.Time
100100
DryRun bool
101+
102+
// LinkCommitSHA, when non-empty, is written to each imported checkpoint's
103+
// metadata as commit_sha — the anchor commit the UI shows imported sessions
104+
// against. The caller resolves it (default branch head); Run does not.
105+
LinkCommitSHA string
101106
}
102107

103108
// Result summarizes an import run.
@@ -170,7 +175,7 @@ func Run(ctx context.Context, repo *git.Repository, imp Importer, opts Options)
170175
red = r
171176
redacted = true
172177
}
173-
if err := writeTurn(ctx, stores, imp, cid, sf, red, turn); err != nil {
178+
if err := writeTurn(ctx, stores, imp, cid, sf, red, turn, opts.LinkCommitSHA); err != nil {
174179
return res, err
175180
}
176181
existing[cid.String()] = true
@@ -261,7 +266,7 @@ func writeSessionState(ctx context.Context, imp Importer, sf SessionFile, turns
261266
return nil
262267
}
263268

264-
func writeTurn(ctx context.Context, stores *cp.Stores, imp Importer, cid id.CheckpointID, sf SessionFile, red redact.RedactedBytes, turn Turn) error {
269+
func writeTurn(ctx context.Context, stores *cp.Stores, imp Importer, cid id.CheckpointID, sf SessionFile, red redact.RedactedBytes, turn Turn, linkCommitSHA string) error {
265270
if err := stores.Persistent.Write(ctx, cp.Session(cp.WriteOptions{
266271
CheckpointID: cid,
267272
SessionID: sf.SessionID,
@@ -275,6 +280,7 @@ func writeTurn(ctx context.Context, stores *cp.Stores, imp Importer, cid id.Chec
275280
CheckpointsCount: 1,
276281
CheckpointTranscriptStart: turn.LineStart,
277282
TokenUsage: turn.Tokens,
283+
CommitSHA: linkCommitSHA,
278284
})); err != nil {
279285
return fmt.Errorf("write imported checkpoint %s: %w", cid, err)
280286
}

cmd/entire/cli/agentimport/agentimport_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,67 @@ func TestRun_ImportsAndIsIdempotent(t *testing.T) {
152152
}
153153
}
154154

155+
// TestRun_StampsLinkCommitSHA proves Options.LinkCommitSHA is copied verbatim
156+
// into each imported checkpoint's commit_sha metadata field, and that leaving
157+
// it unset leaves commit_sha empty. Run resolves nothing itself.
158+
func TestRun_StampsLinkCommitSHA(t *testing.T) {
159+
t.Parallel()
160+
repo, repoDir := initRepoWithCommit(t)
161+
const commitSHA = "b01b59663fd4860fd15a9939499be44a14dbf168"
162+
163+
claudeDirWithSHA := t.TempDir()
164+
writeFixtureSession(t, claudeDirWithSHA, "sess-with-sha.jsonl")
165+
res, err := Run(context.Background(), repo, claudeImporter{}, Options{
166+
RepoRoot: repoDir, OverridePath: claudeDirWithSHA,
167+
Now: time.Date(2026, 6, 25, 0, 0, 0, 0, time.UTC),
168+
LinkCommitSHA: commitSHA,
169+
})
170+
if err != nil {
171+
t.Fatal(err)
172+
}
173+
if res.TurnsImported != 2 {
174+
t.Fatalf("want 2 imported, got %+v", res)
175+
}
176+
177+
stores, err := cp.Open(context.Background(), repo, cp.OpenOptions{})
178+
if err != nil {
179+
t.Fatal(err)
180+
}
181+
cid := DeriveCheckpointID("sess-with-sha", "u1")
182+
md, err := stores.Persistent.ReadSessionMetadata(context.Background(), cid, 0)
183+
if err != nil {
184+
t.Fatal(err)
185+
}
186+
if md.CommitSHA != commitSHA {
187+
t.Fatalf("expected commit_sha %q, got %q", commitSHA, md.CommitSHA)
188+
}
189+
190+
// A separate session fixture (own sessionID/turn UUIDs) run with
191+
// LinkCommitSHA unset must persist an empty commit_sha. Reusing the same
192+
// session would be idempotently skipped, so this needs its own fixture.
193+
claudeDirNoSHA := t.TempDir()
194+
writeFixtureSession(t, claudeDirNoSHA, "sess-no-sha.jsonl")
195+
res2, err := Run(context.Background(), repo, claudeImporter{}, Options{
196+
RepoRoot: repoDir, OverridePath: claudeDirNoSHA,
197+
Now: time.Date(2026, 6, 25, 0, 0, 0, 0, time.UTC),
198+
})
199+
if err != nil {
200+
t.Fatal(err)
201+
}
202+
if res2.TurnsImported != 2 {
203+
t.Fatalf("want 2 imported, got %+v", res2)
204+
}
205+
206+
cid2 := DeriveCheckpointID("sess-no-sha", "u1")
207+
md2, err := stores.Persistent.ReadSessionMetadata(context.Background(), cid2, 0)
208+
if err != nil {
209+
t.Fatal(err)
210+
}
211+
if md2.CommitSHA != "" {
212+
t.Fatalf("expected empty commit_sha, got %q", md2.CommitSHA)
213+
}
214+
}
215+
155216
// TestRun_AppliesConfiguredCustomRedaction proves imported transcripts honor
156217
// repo/user-configured custom_redactions (loaded at the command via
157218
// strategy.EnsureRedactionConfigured), not just always-on secret scanning.

cmd/entire/cli/checkpoint/persistent.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,6 +739,7 @@ func (s *treeWriter) writeSessionToSubdirectory(ctx context.Context, opts WriteO
739739
Strategy: opts.Strategy,
740740
CreatedAt: checkpointCreatedAt(opts),
741741
Branch: opts.Branch,
742+
CommitSHA: opts.CommitSHA,
742743
CheckpointsCount: opts.CheckpointsCount,
743744
SaveStepCount: opts.SaveStepCount,
744745
FilesTouched: opts.FilesTouched,
@@ -800,6 +801,7 @@ func (s *treeWriter) writeCheckpointSummary(opts WriteOptions, basePath string,
800801
// was imported (Kind == "imported"). Compared as a literal because the
801802
// session package imports checkpoint, so we can't reference its constant.
802803
imported := opts.Kind == "imported"
804+
commitSHA := opts.CommitSHA
803805
rootMetadataPath := checkpointSubtreePath(basePath, paths.MetadataFileName)
804806
if entry, exists := entries[rootMetadataPath]; exists {
805807
existingSummary, readErr := s.readSummaryFromBlob(entry.Hash)
@@ -816,6 +818,9 @@ func (s *treeWriter) writeCheckpointSummary(opts WriteOptions, basePath string,
816818
if !imported {
817819
imported = existingSummary.Imported
818820
}
821+
if commitSHA == "" {
822+
commitSHA = existingSummary.CommitSHA
823+
}
819824
}
820825
}
821826

@@ -824,6 +829,7 @@ func (s *treeWriter) writeCheckpointSummary(opts WriteOptions, basePath string,
824829
CLIVersion: versioninfo.Version,
825830
Strategy: opts.Strategy,
826831
Branch: opts.Branch,
832+
CommitSHA: commitSHA,
827833
CheckpointsCount: checkpointsCount,
828834
FilesTouched: filesTouched,
829835
Sessions: sessions,

cmd/entire/cli/checkpoint/persistent_imported_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package checkpoint
22

33
import (
4+
"bytes"
45
"context"
6+
"encoding/json"
57
"testing"
68

79
"github.com/go-git/go-git/v6"
@@ -63,3 +65,99 @@ func TestWrite_ImportedSurfacesOnList(t *testing.T) {
6365
t.Fatalf("expected 1 imported checkpoint, got %+v", infos)
6466
}
6567
}
68+
69+
func TestImportedCheckpoint_CommitSHAPersisted(t *testing.T) {
70+
t.Parallel()
71+
tempDir := t.TempDir()
72+
testutil.InitRepo(t, tempDir)
73+
repo, err := git.PlainOpen(tempDir)
74+
if err != nil {
75+
t.Fatalf("open repo: %v", err)
76+
}
77+
// Initial commit so HEAD exists.
78+
wt, err := repo.Worktree()
79+
if err != nil {
80+
t.Fatal(err)
81+
}
82+
testutil.WriteFile(t, tempDir, "f.txt", "x")
83+
if _, err := wt.Add("f.txt"); err != nil {
84+
t.Fatal(err)
85+
}
86+
if _, err := wt.Commit("init", &git.CommitOptions{
87+
Author: &object.Signature{Name: "Test", Email: "test@test.com"},
88+
}); err != nil {
89+
t.Fatal(err)
90+
}
91+
92+
store := NewGitStore(repo, DefaultV1Refs())
93+
red, err := redact.JSONLBytes([]byte(`{"type":"user","uuid":"u1","message":{"role":"user","content":"hi"}}` + "\n"))
94+
if err != nil {
95+
t.Fatal(err)
96+
}
97+
98+
const commitSHA = "b01b59663fd4860fd15a9939499be44a14dbf168"
99+
ctx := context.Background()
100+
cid := id.MustCheckpointID("aabbccddeeff")
101+
err = store.Write(ctx, Session{
102+
CheckpointID: cid,
103+
SessionID: "s1",
104+
Strategy: "import",
105+
Kind: "imported",
106+
Agent: agent.AgentTypeClaudeCode,
107+
Transcript: red,
108+
Prompts: []string{"hi"},
109+
CheckpointsCount: 1,
110+
CommitSHA: commitSHA,
111+
})
112+
if err != nil {
113+
t.Fatalf("write imported checkpoint: %v", err)
114+
}
115+
116+
md, err := store.ReadSessionMetadata(ctx, cid, 0)
117+
if err != nil {
118+
t.Fatalf("read session metadata: %v", err)
119+
}
120+
if md.CommitSHA != commitSHA {
121+
t.Fatalf("expected session metadata commit_sha %q, got %q", commitSHA, md.CommitSHA)
122+
}
123+
124+
summary, err := store.Read(ctx, cid)
125+
if err != nil {
126+
t.Fatalf("read checkpoint summary: %v", err)
127+
}
128+
if summary.CommitSHA != commitSHA {
129+
t.Fatalf("expected checkpoint summary commit_sha %q, got %q", commitSHA, summary.CommitSHA)
130+
}
131+
132+
// omitempty guard: a checkpoint written without CommitSHA must not surface
133+
// "commit_sha" in the marshaled metadata at all.
134+
cid2 := id.MustCheckpointID("112233445566")
135+
err = store.Write(ctx, Session{
136+
CheckpointID: cid2,
137+
SessionID: "s1",
138+
Strategy: "import",
139+
Kind: "imported",
140+
Agent: agent.AgentTypeClaudeCode,
141+
Transcript: red,
142+
Prompts: []string{"hi"},
143+
CheckpointsCount: 1,
144+
})
145+
if err != nil {
146+
t.Fatalf("write second imported checkpoint: %v", err)
147+
}
148+
149+
md2, err := store.ReadSessionMetadata(ctx, cid2, 0)
150+
if err != nil {
151+
t.Fatalf("read second session metadata: %v", err)
152+
}
153+
if md2.CommitSHA != "" {
154+
t.Fatalf("expected empty commit_sha, got %q", md2.CommitSHA)
155+
}
156+
rawMD, err := json.Marshal(md2)
157+
if err != nil {
158+
t.Fatalf("marshal metadata: %v", err)
159+
}
160+
if bytes.Contains(rawMD, []byte(`"commit_sha"`)) {
161+
t.Fatalf("expected marshaled metadata to omit commit_sha when unset, got %s", rawMD)
162+
}
163+
}

0 commit comments

Comments
 (0)