@@ -90,7 +90,10 @@ func initRepoWithCommit(t *testing.T) (*git.Repository, string) {
9090 t .Fatal (err )
9191 }
9292 if _ , err := wt .Commit ("init" , & git.CommitOptions {
93- Author : & object.Signature {Name : "Test" , Email : "test@test.com" },
93+ // When must be a real timestamp: the anchor resolver's bounded walk
94+ // stops at commits older than its date cutoff, and a zero-value When
95+ // (year 1) would halt the walk at the first commit.
96+ Author : & object.Signature {Name : "Test" , Email : "test@test.com" , When : time .Now ()},
9497 }); err != nil {
9598 t .Fatal (err )
9699 }
@@ -152,6 +155,138 @@ func TestRun_ImportsAndIsIdempotent(t *testing.T) {
152155 }
153156}
154157
158+ // TestRun_StampsLinkCommitSHA proves Options.LinkCommitSHA is copied verbatim
159+ // into each imported checkpoint's commit_sha metadata field, and that leaving
160+ // it unset leaves commit_sha empty. Run resolves nothing itself.
161+ func TestRun_StampsLinkCommitSHA (t * testing.T ) {
162+ t .Parallel ()
163+ repo , repoDir := initRepoWithCommit (t )
164+ const commitSHA = "b01b59663fd4860fd15a9939499be44a14dbf168"
165+
166+ claudeDirWithSHA := t .TempDir ()
167+ writeFixtureSession (t , claudeDirWithSHA , "sess-with-sha.jsonl" )
168+ res , err := Run (context .Background (), repo , claudeImporter {}, Options {
169+ RepoRoot : repoDir , OverridePath : claudeDirWithSHA ,
170+ Now : time .Date (2026 , 6 , 25 , 0 , 0 , 0 , 0 , time .UTC ),
171+ LinkCommitSHA : commitSHA ,
172+ })
173+ if err != nil {
174+ t .Fatal (err )
175+ }
176+ if res .TurnsImported != 2 {
177+ t .Fatalf ("want 2 imported, got %+v" , res )
178+ }
179+
180+ stores , err := cp .Open (context .Background (), repo , cp.OpenOptions {})
181+ if err != nil {
182+ t .Fatal (err )
183+ }
184+ cid := DeriveCheckpointID ("sess-with-sha" , "u1" )
185+ md , err := stores .Persistent .ReadSessionMetadata (context .Background (), cid , 0 )
186+ if err != nil {
187+ t .Fatal (err )
188+ }
189+ if md .CommitSHA != commitSHA {
190+ t .Fatalf ("expected commit_sha %q, got %q" , commitSHA , md .CommitSHA )
191+ }
192+
193+ // A separate session fixture (own sessionID/turn UUIDs) run with
194+ // LinkCommitSHA unset must persist an empty commit_sha. Reusing the same
195+ // session would be idempotently skipped, so this needs its own fixture.
196+ claudeDirNoSHA := t .TempDir ()
197+ writeFixtureSession (t , claudeDirNoSHA , "sess-no-sha.jsonl" )
198+ res2 , err := Run (context .Background (), repo , claudeImporter {}, Options {
199+ RepoRoot : repoDir , OverridePath : claudeDirNoSHA ,
200+ Now : time .Date (2026 , 6 , 25 , 0 , 0 , 0 , 0 , time .UTC ),
201+ })
202+ if err != nil {
203+ t .Fatal (err )
204+ }
205+ if res2 .TurnsImported != 2 {
206+ t .Fatalf ("want 2 imported, got %+v" , res2 )
207+ }
208+
209+ cid2 := DeriveCheckpointID ("sess-no-sha" , "u1" )
210+ md2 , err := stores .Persistent .ReadSessionMetadata (context .Background (), cid2 , 0 )
211+ if err != nil {
212+ t .Fatal (err )
213+ }
214+ if md2 .CommitSHA != "" {
215+ t .Fatalf ("expected empty commit_sha, got %q" , md2 .CommitSHA )
216+ }
217+ }
218+
219+ // TestRun_AnchorsTurnToRecordedCommit proves a turn whose transcript records a
220+ // resolvable, default-branch-reachable commit anchors to that real commit
221+ // instead of the LinkCommitSHA fallback, while a turn with no recorded commit
222+ // still falls back exactly as before.
223+ func TestRun_AnchorsTurnToRecordedCommit (t * testing.T ) {
224+ t .Parallel ()
225+ repo , repoDir := initRepoWithCommit (t )
226+ firstCommit , err := repo .Head ()
227+ if err != nil {
228+ t .Fatal (err )
229+ }
230+ firstSHA := firstCommit .Hash ().String ()
231+
232+ // Second commit on the default branch, so tip != first commit.
233+ wt , err := repo .Worktree ()
234+ if err != nil {
235+ t .Fatal (err )
236+ }
237+ writeAndCommit (t , wt , repoDir , "y" , "second" )
238+ tipHead , err := repo .Head ()
239+ if err != nil {
240+ t .Fatal (err )
241+ }
242+ tipSHA := tipHead .Hash ().String ()
243+
244+ claudeDir := t .TempDir ()
245+ content := strings .Join ([]string {
246+ `{"type":"user","uuid":"u1","timestamp":"2026-06-20T00:00:00Z","message":{"role":"user","content":"first"}}` ,
247+ `{"type":"assistant","uuid":"a1","message":{"id":"m1","model":"claude-x","content":[{"type":"text","text":"ok"}],"usage":{"output_tokens":5}}}` ,
248+ `{"type":"user","uuid":"tr1","toolUseResult":{"gitOperation":{"commit":{"sha":"` + firstSHA [:7 ] + `","kind":"committed"}}}}` ,
249+ `{"type":"user","uuid":"u2","timestamp":"2026-06-20T00:01:00Z","message":{"role":"user","content":"second"}}` ,
250+ }, "\n " ) + "\n "
251+ if err := os .WriteFile (filepath .Join (claudeDir , "sess-anchor.jsonl" ), []byte (content ), 0o644 ); err != nil {
252+ t .Fatal (err )
253+ }
254+
255+ res , err := Run (context .Background (), repo , claudeImporter {}, Options {
256+ RepoRoot : repoDir , OverridePath : claudeDir ,
257+ Now : time .Date (2026 , 6 , 25 , 0 , 0 , 0 , 0 , time .UTC ),
258+ LinkCommitSHA : tipSHA ,
259+ })
260+ if err != nil {
261+ t .Fatal (err )
262+ }
263+ if res .TurnsImported != 2 {
264+ t .Fatalf ("want 2 imported, got %+v" , res )
265+ }
266+
267+ stores , err := cp .Open (context .Background (), repo , cp.OpenOptions {})
268+ if err != nil {
269+ t .Fatal (err )
270+ }
271+ cid1 := DeriveCheckpointID ("sess-anchor" , "u1" )
272+ md1 , err := stores .Persistent .ReadSessionMetadata (context .Background (), cid1 , 0 )
273+ if err != nil {
274+ t .Fatal (err )
275+ }
276+ if md1 .CommitSHA != firstSHA {
277+ t .Fatalf ("turn1 CommitSHA = %q, want recorded commit %q" , md1 .CommitSHA , firstSHA )
278+ }
279+
280+ cid2 := DeriveCheckpointID ("sess-anchor" , "u2" )
281+ md2 , err := stores .Persistent .ReadSessionMetadata (context .Background (), cid2 , 0 )
282+ if err != nil {
283+ t .Fatal (err )
284+ }
285+ if md2 .CommitSHA != tipSHA {
286+ t .Fatalf ("turn2 CommitSHA = %q, want fallback %q" , md2 .CommitSHA , tipSHA )
287+ }
288+ }
289+
155290// TestRun_AppliesConfiguredCustomRedaction proves imported transcripts honor
156291// repo/user-configured custom_redactions (loaded at the command via
157292// strategy.EnsureRedactionConfigured), not just always-on secret scanning.
0 commit comments