Build checkpoint subtree paths via path.Join helper (no tree change) · Entire

Build checkpoint subtree paths via path.Join helper (no tree change)

5cc6d2b·

Soph·2w ago·2 files·+117 added/-66 removed

A reviewer flagged fmt.Sprintf("%s%d/%s", basePath, idx, paths.MetadataFileName)
as looking like a missing slash — it's correct only because basePath carries a
trailing "/". Replace the string-concat path construction with a
checkpointSubtreePath(base, segs...) helper over stdlib path.Join, so paths join
correctly without relying on that invariant.

No behavior change: committed-tree assertions (checkpoint write/read, tripwire,
update), integration (378), and both canary modes (git-branch 59/59, git-refs
58/59 +1 skip) are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com

Sessions

973f8248e820View transcript

Changes

2

9 unmodified lines  
10  
11  
12  
13  
14  
15  
16  
122 unmodified lines  
139  
140  
141  
142  
143  
144  
145  
146  
147  
148  
149  
150  
151  
152  
153  
154  
155  
156  
82 unmodified lines  
239  
240  
241  
229  
242  
243  
244  
245  
32 unmodified lines  
278  
279  
280  
268  
281  
282  
283  
284  
6 unmodified lines  
291  
292  
293  
281  
294  
295  
296  
297  
36 unmodified lines  
334  
335  
336  
324  
337  
338  
339  
340  
11 unmodified lines  
352  
353  
354  
342  
355  
356  
357  
358  
11 unmodified lines  
370  
371  
372  
360  
373  
374  
375  
376  
377  
378  
366  
379  
380  
381  
382  
6 unmodified lines  
389  
390  
391  
379  
392  
393  
394  
395  
3 unmodified lines  
399  
400  
401  
389  
390  
402  
403  
404  
405  
406  
20 unmodified lines  
427  
428  
429  
417  
418  
430  
431  
432  
433  
434  
435  
436  
437  
438  
425  
439  
440  
441  
442  
3 unmodified lines  
446  
447  
448  
435  
449  
450  
451  
438  
452  
453  
440  
454  
455  
456  
457  
444  
458  
459  
460  
461  
14 unmodified lines  
476  
477  
478  
465  
479  
480  
481  
482  
3 unmodified lines  
486  
487  
488  
475  
489  
490  
491  
492  
9 unmodified lines  
502  
503  
504  
491  
505  
506  
507  
508  
19 unmodified lines  
528  
529  
530  
517  
531  
532  
533  
534  
3 unmodified lines  
538  
539  
540  
527  
528  
541  
542  
543  
544  
545  
14 unmodified lines  
560  
561  
562  
549  
563  
564  
565  
566  
18 unmodified lines  
585  
586  
587  
574  
588  
589  
590  
591  
9 unmodified lines  
601  
602  
603  
590  
591  
604  
605  
606  
607  
608  
609  
610  
611  
598  
612  
613  
614  
615  
17 unmodified lines  
633  
634  
635  
622  
636  
637  
638  
639  
11 unmodified lines  
651  
652  
653  
640  
654  
655  
656  
643  
644  
657  
658  
659  
660  
646  
661  
662  
663  
664  
1 unmodified line  
666  
667  
668  
654  
669  
670  
671  
672  
673  
659  
660  
674  
675  
676  
677  
663  
664  
678  
679  
680  
681  
682  
5 unmodified lines  
688  
689  
690  
676  
677  
691  
692  
693  
694  
695  
696  
697  
698  
699  
700  
37 unmodified lines  
738  
739  
740  
725  
726  
741  
742  
743  
744  
745  
746  
730  
747  
748  
749  
750  
13 unmodified lines  
764  
765  
766  
750  
767  
768  
769  
770  
37 unmodified lines  
808  
809  
810  
794  
795  
811  
812  
813  
814  
815  
47 unmodified lines  
863  
864  
865  
849  
866  
867  
868  
869  
22 unmodified lines  
892  
893  
894  
878  
895  
896  
897  
898  
83 unmodified lines  
982  
983  
984  
968  
985  
986  
987  
988  
38 unmodified lines  
1027  
1028  
1029  
1013  
1030  
1031  
1032  
1033  
19 unmodified lines  
1053  
1054  
1055  
1039  
1040  
1056  
1057  
1058  
1059  
1060  
1061  
3 unmodified lines  
1065  
1066  
1067  
1050  
1068  
1069  
1070  
1071  
20 unmodified lines  
1092  
1093  
1094  
1077  
1095  
1096  
1097  
1098  
1099  
1082  
1100  
1101  
1102  
1103  
32 unmodified lines  
1136  
1137  
1138  
1121  
1139  
1140  
1141  
1142  
617 unmodified lines  
1760  
1761  
1762  
1745  
1746  
1763  
1764  
1765  
1766  
1767  
34 unmodified lines  
1802  
1803  
1804  
1787  
1805  
1806  
1807  
1808  
10 unmodified lines  
1819  
1820  
1821  
1804  
1822  
1823  
1824  
1825  
7 unmodified lines  
1833  
1834  
1835  
1818  
1836  
1837  
1838  
1839  
21 unmodified lines  
1861  
1862  
1863  
1846  
1864  
1865  
1866  
1867  
28 unmodified lines  
1896  
1897  
1898  
1881  
1899  
1900  
1901  
1902  
142 unmodified lines  
2045  
2046  
2047  
2030  
2048  
2049  
2050  
2051  
44 unmodified lines  
2096  
2097  
2098  
2081  
2099  
2100  
2101  
2102  
9 unmodified lines

"io"
    "log/slog"
    "os"
    "path"
    "path/filepath"
    "sort"
    "strconv"
122 unmodified lines

return subtree, nil
}

// checkpointSubtreePath joins a checkpoint-relative git tree path from a base and
// trailing segments using path.Join. Git tree paths are always "/"-separated, so
// this uses the stdlib path package (never path/filepath, which would emit "\\" on
// Windows and corrupt tree keys). path.Join cleans separators, so base may be ""
// (per-checkpoint-ref root), a clean dir ("a3/b2.../0"), or a trailing-slash dir
// ("a3/b2.../"): checkpointSubtreePath("", "0", "metadata.json") == "0/metadata.json"
// and checkpointSubtreePath("a3/b2.../", "0", "metadata.json") == "a3/b2.../0/metadata.json".
// Callers therefore need not maintain the trailing-slash invariant by hand.
func checkpointSubtreePath(base string, segs ...string) string {
    return path.Join(append([]string{base}, segs...)...)
}

// flattenExisting flattens a checkpoint's current subtree into a path->entry map
// keyed under basePath, so the per-checkpoint write helpers (which build paths as
// basePath+"<n>/<file>") see the existing files. basePath is ""

return plumbing.ZeroHash, err
}

rootMetadataPath := basePath + paths.MetadataFileName
rootMetadataPath := checkpointSubtreePath(basePath, paths.MetadataFileName)
entry, exists := entries[rootMetadataPath]
if !exists {
    return plumbing.ZeroHash, ErrCheckpointNotFound
}

// Find the latest session's metadata path (0-based indexing)
latestIndex := len(checkpointSummary.Sessions) - 1
sessionMetadataPath := fmt.Sprintf("%s%d/%s", basePath, latestIndex, paths.MetadataFileName)
sessionMetadataPath := checkpointSubtreePath(basePath, strconv.Itoa(latestIndex), paths.MetadataFileName)
sessionEntry, exists := entries[sessionMetadataPath]
if !exists {
    return plumbing.ZeroHash, "", fmt.Errorf("session metadata not found at %s", sessionMetadataPath)
}

return plumbing.ZeroHash, err
}

sessionIndex := -1
var sessionMeta *Metadata
for i := range len(checkpointSummary.Sessions) {
    metaPath := fmt.Sprintf("%s%d/%s", basePath, i, paths.MetadataFileName)
    metaPath := checkpointSubtreePath(basePath, strconv.Itoa(i), paths.MetadataFileName)
    if metaEntry, metaExists := entries[metaPath]; metaExists {
        meta, metaErr := s.readMetadataFromBlob(metaEntry.Hash)
        if metaErr == nil && meta.SessionID == opts.SessionID {
        }
    }
}

sessionPath := fmt.Sprintf("%s%d/", basePath, sessionIndex)
sessionDir := checkpointSubtreePath(basePath, strconv.Itoa(sessionIndex))

// Replace transcript (full replace, not append).
// Transcript is pre-redacted by the caller (enforced by RedactedBytes type).
if err := s.replaceTranscript(ctx, opts.Transcript, agentType, startLine, opts.PrecomputedBlobs, sessionPath, entries); err != nil {
    if err := s.replaceTranscript(ctx, opts.Transcript, agentType, startLine, opts.PrecomputedBlobs, sessionDir, entries); err != nil {
        return plumbing.ZeroHash, fmt.Errorf("failed to replace transcript: %w", err)
    }
}

// succeeds now), so re-derive the pointer from the tree entry and rewrite
// the root summary when it changed.
compactPath := ""
if _, ok := entries[sessionPath+paths.CompactTranscriptFileName]; ok {
    compactPath = "/" + sessionPath + paths.CompactTranscriptFileName
if _, ok := entries[checkpointSubtreePath(sessionDir, paths.CompactTranscriptFileName)]; ok {
    compactPath = "/" + checkpointSubtreePath(sessionDir, paths.CompactTranscriptFileName)
}
if checkpointSummary.Sessions[sessionIndex].CompactTranscript != compactPath {
    checkpointSummary.Sessions[sessionIndex].CompactTranscript = compactPath
}

if err != nil {
    return plumbing.ZeroHash, fmt.Errorf("failed to create prompt blob: %w", err)
}
entries[sessionPath+paths.PromptFileName] = object.TreeEntry{
    Name: sessionPath + paths.PromptFileName,
promptPath := checkpointSubtreePath(sessionDir, paths.PromptFileName)
entries[promptPath] = object.TreeEntry{
    Name: promptPath,
    Mode: filemode.Regular,
    Hash: blobHash,
}

if len(opts.SkillEvents) > 0 {
    if err := s.replaceSkillEvents(opts.SkillEvents, sessionPath, entries); err != nil {
    if err := s.replaceSkillEvents(opts.SkillEvents, sessionDir, entries); err != nil {
        return plumbing.ZeroHash, fmt.Errorf("failed to replace skill events: %w", err)
    }
}
}

// writeTaskCheckpointEntries writes task-specific checkpoint entries and returns the task metadata path.
func (s *treeWriter) writeTaskCheckpointEntries(ctx context.Context, opts WriteOptions, basePath string, entries map[string]object.TreeEntry) (string, error) {
taskPath := basePath + "tasks/" + opts.ToolUseID + "/"
taskDir := checkpointSubtreePath(basePath, "tasks", opts.ToolUseID)

if opts.IsIncremental {
    return s.writeIncrementalTaskCheckpoint(opts, taskPath, entries)
}
return s.writeFinalTaskCheckpoint(ctx, opts, taskPath, entries)
}

// writeIncrementalTaskCheckpoint writes an incremental checkpoint file during task execution.
func (s *treeWriter) writeIncrementalTaskCheckpoint(opts WriteOptions, taskPath string, entries map[string]object.TreeEntry) (string, error) {

// writeFinalTaskCheckpoint writes the final checkpoint.json and subagent transcript.
func (s *treeWriter) writeFinalTaskCheckpoint(ctx context.Context, opts WriteOptions, taskPath string, entries map[string]object.TreeEntry) (string, error) {

}

// writeStandardCheckpointEntries writes session files to numbered subdirectories and
// returns the absolute file paths from the git tree root for the sessions map.
func (s *treeWriter) writeStandardCheckpointEntries(ctx context.Context, opts WriteOptions, basePath string, entries map[string]object.TreeEntry, checkpointVersion string) error {

var existingSummary *CheckpointSummary
metadataPath := basePath + paths.MetadataFileName
metadataPath := checkpointSubtreePath(basePath, paths.MetadataFileName)
if entry, exists := entries[metadataPath]; exists {
    existing, err := s.readSummaryFromBlob(entry.Hash)
    if err == nil {
    
}
}

// We read and capture BEFORE writeSessionToSubdirectory clears the subtree,
// otherwise we'd only ever see our own write.
if sessionIndex == 0 {
    if entry, exists := entries[fmt.Sprintf("%s0/%s", basePath, paths.MetadataFileName)]; exists {
        if existingMeta, readErr := s.readMetadataFromBlob(entry.Hash); readErr == nil && existingMeta.SessionID != opts.SessionID {
            logging.Error(ctx, "refusing checkpoint write: session 0 holds a different sessionID",
                slog.String("checkpoint_id", opts.CheckpointID.String()),
            )
        }
    }
}

// Write session files to numbered subdirectory
sessionPath := fmt.Sprintf("%s%d/", basePath, sessionIndex)
sessionFilePaths, err := s.writeSessionToSubdirectory(ctx, opts, sessionPath, entries)
sessionDir := checkpointSubtreePath(basePath, strconv.Itoa(sessionIndex))
sessionFilePaths, err := s.writeSessionToSubdirectory(ctx, opts, sessionDir, entries)
if err != nil {
    return err
}

// Copy additional metadata files from directory if specified (to session subdirectory)
if opts.MetadataDir != "" {
    if err := s.copyMetadataDir(ctx, opts.MetadataDir, sessionPath, entries); err != nil {
        if err := s.copyMetadataDir(ctx, opts.MetadataDir, sessionDir, entries); err != nil {
            return fmt.Errorf("failed to copy metadata directory: %w", err)
        }
    }
}

// metadata with a DIFFERENT sessionID, that's the exact bug shape.
// Loud WARN so we get a log trace instead of only the symptom.
if sessionIndex == 0 {
    path := fmt.Sprintf("%s0/%s", basePath, paths.MetadataFileName)
    path := checkpointSubtreePath(basePath, "0", paths.MetadataFileName)
    if entry, exists := entries[path]; exists {
        if existingMeta, readErr := s.readMetadataFromBlob(entry.Hash); readErr == nil && existingMeta.SessionID != opts.SessionID {
            logging.Warn(ctx, "checkpoint write overwrites session 0 with a different sessionID — potential overwrite regression",
                slog.String("checkpoint_id", opts.CheckpointID.String()),
            )
        }
    }
}

// writeSessionToSubdirectory writes a single session's files to a numbered subdirectory.
// Returns the absolute file paths from the git tree root for the sessions map.
func (s *treeWriter) writeSessionToSubdirectory(ctx context.Context, opts WriteOptions, sessionPath string, entries map[string]object.TreeEntry) (SessionFilePaths, error) {
func (s *treeWriter) writeSessionToSubdirectory(ctx context.Context, opts WriteOptions, sessionDir string, entries map[string]object.TreeEntry) (SessionFilePaths, error) {

filePaths := SessionFilePaths{}

// Clear any existing entries at this path so stale files from a previous
// write (e.g. prompt.txt) don't persist on overwrite.
// Clear any existing entries under this session dir so stale files from a
// previous write (e.g. prompt.txt) don't persist on overwrite. Match on the
// dir plus "/" so a sibling session (e.g. "10") isn't caught by "1".
for key := range entries {
    if strings.HasPrefix(key, sessionPath) {
        delete(entries, key)
    }
}

// Write transcript. Transcript points at full.jsonl (CLI
// rewind/resume/explain read it by filename); the compact transcript.jsonl,
// when written, is also pushed and pointed at by CompactTranscript.
wroteTranscript, err := s.writeTranscript(ctx, opts, sessionPath, entries)
wroteTranscript, err := s.writeTranscript(ctx, opts, sessionDir, entries)
if err != nil {
    return filePaths, err
}
if wroteTranscript {
    filePaths.Transcript = "/" + sessionPath + paths.TranscriptFileName
    filePaths.ContentHash = "/" + sessionPath + paths.ContentHashFileName
    filePaths.Transcript = "/" + checkpointSubtreePath(sessionDir, paths.TranscriptFileName)
    filePaths.ContentHash = "/" + checkpointSubtreePath(sessionDir, paths.ContentHashFileName)
    // Point at the compact transcript only when it was actually written
    // (best-effort), deriving from the tree entry so the path can't dangle.
    if _, ok := entries[sessionPath+paths.CompactTranscriptFileName]; ok {
        filePaths.CompactTranscript = "/" + sessionPath + paths.CompactTranscriptFileName
        if _, ok := entries[checkpointSubtreePath(sessionDir, paths.CompactTranscriptFileName)]; ok {
            filePaths.CompactTranscript = "/" + checkpointSubtreePath(sessionDir, paths.CompactTranscriptFileName)
        }
    }
}

if err != nil {
        return filePaths, err
    }
    entries[sessionPath+paths.PromptFileName] = object.TreeEntry{
        Name: sessionPath + paths.PromptFileName,
        promptPath := checkpointSubtreePath(sessionDir, paths.PromptFileName)
        entries[promptPath] = object.TreeEntry{
            Name: promptPath,
            Mode: filemode.Regular,
            Hash: blobHash,
        }
        filePaths.Prompt = "/" + sessionPath + paths.PromptFileName
        filePaths.Prompt = "/" + promptPath

// Write session-level metadata.json (Metadata with all fields including initial_attribution)
}

if err != nil {
    return filePaths, err
}
entries[sessionPath+paths.MetadataFileName] = object.TreeEntry{
    Name: sessionPath + paths.MetadataFileName,
    sessionMetadataPath := checkpointSubtreePath(sessionDir, paths.MetadataFileName)
    entries[sessionMetadataPath] = object.TreeEntry{
        Name: sessionMetadataPath,
        Mode: filemode.Regular,
        Hash: metadataHash,
    }
    filePaths.Metadata = "/" + sessionPath + paths.MetadataFileName
    filePaths.Metadata = "/" + sessionMetadataPath
}

return filePaths, nil
}

}

// copyMetadataDir copies all files from a directory to the checkpoint path.
// Used to include additional metadata files like task checkpoints, subagent transcripts, etc.
func (s *treeWriter) copyMetadataDir(ctx context.Context, metadataDir, sessionDir string, entries map[string]object.TreeEntry) error {
func (s *treeWriter) copyMetadataDir(ctx context.Context, metadataDir, basePath string, entries map[string]object.TreeEntry) error {
    error := filepath.Walk(metadataDir, func(path string, info os.FileInfo, err error) error {
    if err != nil {
        return err
    }

}
})
...