From 4df67dc311516a593bda91450098c4dfc22d9c26 Mon Sep 17 00:00:00 2001 From: Peyton Montei Date: Thu, 23 Jul 2026 17:54:03 -0400 Subject: [PATCH] fix(import): stamp importer git identity on checkpoint commits entire import wrote checkpoint commits with an empty git author signature. On the GitHub->mirror ingestion path, the data plane has no pusher identity for imported sessions and falls back to the checkpoint commit's git author; an empty author meant imported sessions couldn't be attributed to the importer. Resolve the author once per import run via the existing checkpoint.GetGitAuthorFromRepo (author email is HMAC'd, never stored) and thread it into every written checkpoint's commit, fixing attribution via existing, de-identifying machinery. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01QUDkzmpwgC4BiZ894UfnTy --- cmd/entire/cli/agentimport/agentimport.go | 12 +- .../cli/agentimport/agentimport_test.go | 116 ++++++++++++++++++ 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/cmd/entire/cli/agentimport/agentimport.go b/cmd/entire/cli/agentimport/agentimport.go index ab2bd30331..1a414d46a0 100644 --- a/cmd/entire/cli/agentimport/agentimport.go +++ b/cmd/entire/cli/agentimport/agentimport.go @@ -156,6 +156,12 @@ func Run(ctx context.Context, repo *git.Repository, imp Importer, opts Options) // whole import run pays for at most one history walk, not one per turn. anchorResolver := newTurnAnchorResolver(repo, opts.LinkCommitSHA, opts.Now) + // Resolve the importer's git identity once per run (not per turn): + // checkpoint commits otherwise carry an empty author, which the + // GitHub->mirror ingestion path falls back to when it has no pusher + // identity for imported sessions, leaving them unattributed. + authorName, authorEmail := cp.GetGitAuthorFromRepo(repo) + for _, sf := range files { res.SessionsScanned++ full, readErr := os.ReadFile(sf.Path) @@ -195,7 +201,7 @@ func Run(ctx context.Context, repo *git.Repository, imp Importer, opts Options) logging.Debug(ctx, "import: turn anchor fell back", "sessionID", sf.SessionID, "turnUUID", turn.UUID, "candidates", len(turn.CommitSHAs)) } - if err := writeTurn(ctx, stores, imp, cid, sf, red, turn, anchor); err != nil { + if err := writeTurn(ctx, stores, imp, cid, sf, red, turn, anchor, authorName, authorEmail); err != nil { return res, err } existing[cid.String()] = true @@ -286,7 +292,7 @@ func writeSessionState(ctx context.Context, imp Importer, sf SessionFile, turns return nil } -func writeTurn(ctx context.Context, stores *cp.Stores, imp Importer, cid id.CheckpointID, sf SessionFile, red redact.RedactedBytes, turn Turn, anchorCommitSHA string) error { +func writeTurn(ctx context.Context, stores *cp.Stores, imp Importer, cid id.CheckpointID, sf SessionFile, red redact.RedactedBytes, turn Turn, anchorCommitSHA, authorName, authorEmail string) error { if err := stores.Persistent.Write(ctx, cp.Session(cp.WriteOptions{ CheckpointID: cid, SessionID: sf.SessionID, @@ -301,6 +307,8 @@ func writeTurn(ctx context.Context, stores *cp.Stores, imp Importer, cid id.Chec CheckpointTranscriptStart: turn.LineStart, TokenUsage: turn.Tokens, CommitSHA: anchorCommitSHA, + AuthorName: authorName, + AuthorEmail: authorEmail, })); err != nil { return fmt.Errorf("write imported checkpoint %s: %w", cid, err) } diff --git a/cmd/entire/cli/agentimport/agentimport_test.go b/cmd/entire/cli/agentimport/agentimport_test.go index ea4c84af8e..424787d345 100644 --- a/cmd/entire/cli/agentimport/agentimport_test.go +++ b/cmd/entire/cli/agentimport/agentimport_test.go @@ -384,6 +384,122 @@ func TestRun_CursorImporterEndToEnd(t *testing.T) { } } +// TestRun_StampsImporterGitAuthorOnCheckpointCommit proves an imported +// checkpoint's underlying git commit on entire/checkpoints/v1 carries the +// importer's configured git identity (resolved once per Run via +// checkpoint.GetGitAuthorFromRepo), not an empty signature. +// +// Motivation: on the GitHub->mirror ingestion path, the data plane has no +// pusher identity for imported sessions and falls back to the checkpoint +// commit's git author. An empty author meant imported sessions couldn't be +// attributed to the importer. +func TestRun_StampsImporterGitAuthorOnCheckpointCommit(t *testing.T) { + t.Parallel() + repo, repoDir := initRepoWithCommit(t) + claudeDir := t.TempDir() + writeFixtureSession(t, claudeDir, "sess-author.jsonl") + + res, err := Run(context.Background(), repo, claudeImporter{}, Options{ + RepoRoot: repoDir, OverridePath: claudeDir, + Now: time.Date(2026, 6, 25, 0, 0, 0, 0, time.UTC), + }) + if err != nil { + t.Fatal(err) + } + if res.TurnsImported != 2 { + t.Fatalf("want 2 imported, got %+v", res) + } + + stores, err := cp.Open(context.Background(), repo, cp.OpenOptions{}) + if err != nil { + t.Fatal(err) + } + ar, ok := stores.Persistent.(cp.AuthorReader) + if !ok { + t.Fatalf("persistent store %T does not implement AuthorReader", stores.Persistent) + } + cid := DeriveCheckpointID("sess-author", "u1") + author, err := ar.GetCheckpointAuthor(context.Background(), cid) + if err != nil { + t.Fatal(err) + } + // initRepoWithCommit uses testutil.InitRepo, which configures this + // repo-local git identity. + const wantName, wantEmail = "Test User", "test@example.com" + if author.Name != wantName || author.Email != wantEmail { + t.Fatalf("checkpoint commit author = %+v, want Name=%q Email=%q (the repo's configured git identity)", + author, wantName, wantEmail) + } +} + +// TestRun_UnconfiguredGitIdentityFallsBackToDefaults proves that when the +// importer's repo has no configured git user (no local or global user.name / +// user.email), the imported checkpoint commit still gets a signature — the +// same "Unknown"/"unknown@local" default checkpoint.GetGitAuthorFromRepo +// already applies elsewhere, rather than an empty one. +func TestRun_UnconfiguredGitIdentityFallsBackToDefaults(t *testing.T) { + // Cannot use t.Parallel(): isolates HOME via t.Setenv so this repo's + // global git config resolution can't see the developer's real ~/.gitconfig. + home := t.TempDir() + t.Setenv("HOME", home) + t.Setenv("XDG_CONFIG_HOME", "") + + repoDir := t.TempDir() + repo, err := git.PlainInit(repoDir, false) + if err != nil { + t.Fatal(err) + } + // Seed one commit with a real timestamp (the anchor resolver's bounded + // walk stops at commits older than its date cutoff; a zero-value When + // would halt it immediately). The commit's own author signature is + // independent of GetGitAuthorFromRepo's config-based resolution under + // test here. + 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: "Seed", Email: "seed@test.com", When: time.Now()}, + }); err != nil { + t.Fatal(err) + } + + claudeDir := t.TempDir() + writeFixtureSession(t, claudeDir, "sess-noauthor.jsonl") + + res, err := Run(context.Background(), repo, claudeImporter{}, Options{ + RepoRoot: repoDir, OverridePath: claudeDir, + Now: time.Date(2026, 6, 25, 0, 0, 0, 0, time.UTC), + }) + if err != nil { + t.Fatal(err) + } + if res.TurnsImported != 2 { + t.Fatalf("want 2 imported, got %+v", res) + } + + stores, err := cp.Open(context.Background(), repo, cp.OpenOptions{}) + if err != nil { + t.Fatal(err) + } + ar, ok := stores.Persistent.(cp.AuthorReader) + if !ok { + t.Fatalf("persistent store %T does not implement AuthorReader", stores.Persistent) + } + cid := DeriveCheckpointID("sess-noauthor", "u1") + author, err := ar.GetCheckpointAuthor(context.Background(), cid) + if err != nil { + t.Fatal(err) + } + if author.Name != "Unknown" || author.Email != "unknown@local" { + t.Fatalf("checkpoint commit author = %+v, want the GetGitAuthorFromRepo defaults", author) + } +} + func TestRun_DryRunWritesNothing(t *testing.T) { t.Parallel() repo, repoDir := initRepoWithCommit(t)