Emit ULID checkpoint IDs under the git-refs store · Entire

Emit ULID checkpoint IDs under the git-refs store

fbc294a→main·

Soph·1w ago·8 files·+116 added/-11 removed

New checkpoints written under the git-refs primary now get a 26-char ULID instead of a 12-hex id, so a checkpoint's id encodes its storage backend (ULID ⟹ ref) — which lets reads route by id kind without config.

- id.GenerateULID(): timestamp + crypto-random ULID (via oklog/ulid, already a dep), canonical and KindULID-valid. Generate() (hex) is unchanged. - checkpoint.GenerateCheckpointID(ctx): the single place the format is chosen — ULID when the primary is git-refs, else 12-hex. Fail-soft to hex on a missing/malformed config. - Route the checkpoint-id generation sites through it (attach, the two manual-commit hook paths, the two condensation paths). Turn ids and the investigate run id stay hex — they're format-agnostic correlation tokens. - Fix explain's auto-ambiguity guard: it assumed a fixed 12-char id width; use the new id.MaxIDLength (26) so a ULID target isn't wrongly treated as un-prefixable.

Storage/read already handled both formats (ShardFor shards ULIDs on the last two chars, RefName/ParseRef/Validate accept them), so no store changes were needed.

Sessions

ddfc647b27a2View transcript

[?
Build Checkpoints Store Based on DesignClaude Code·Opus 4.8·5 steps](/content/gh/entireio/cli/session/6852b33a-0d22-4364-aa6c-8de706ecc215#timeline-ddfc647b27a2/index.html)

Changes

8

288 unmodified lines  
289  
290  
291  
292  
292  
293  
294  
295  
289 unmodified lines

585  
586  
587  
588  
588  
589  
590  
591  
592  
593  
594  
594  
595  
596  
597

288 unmodified lines

warnEmptyTranscriptMetadata(errW, ag.Name(), meta, opts)

// Determine checkpoint ID: reuse from HEAD if one exists, otherwise generate new.
    checkpointID, isExistingCheckpoint := resolveCheckpointID(headCommit)
    checkpointID, isExistingCheckpoint := resolveCheckpointID(ctx, headCommit)

// If HEAD references an existing checkpoint, make sure we have it locally
    // before writing — otherwise we'd create a fresh session 0 under the same
289 unmodified lines

return "git fetch origin " + ref
}

func resolveCheckpointID(headCommit *object.Commit) (id.CheckpointID, bool) {
func resolveCheckpointID(ctx context.Context, headCommit *object.Commit) (id.CheckpointID, bool) {
_existing := trailers.ParseAllCheckpoints(headCommit.Message)
if len(existing) > 0 {
    return existing[len(existing)-1], true
}

cpID, err := id.Generate()
    cpID, err := cpkg.GenerateCheckpointID(ctx)
    if err != nil {
        // Generation only fails if crypto/rand fails — extremely unlikely.
        // Fall back to empty which will cause WriteCommitted to fail with a clear error.

Mcmd/entire/cli/attach.go+3/-3

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26

package checkpoint

import (
    "context"

"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
    "github.com/entireio/cli/cmd/entire/cli/settings"
)

// GenerateCheckpointID mints a new checkpoint ID in the format the configured
// primary store uses: a ULID under the git-refs store, a legacy 12-hex ID
// otherwise. It is the single place the backend-coupled ID format is decided —
// generation sites call it instead of id.Generate() so a git-refs checkpoint is
// always a ULID, which lets reads route by ID kind (ULID ⟹ ref).
//
// Fail-soft: a missing or malformed checkpoints config resolves to the default
// hex format rather than blocking ID generation (a bad block already surfaces
// through checkpoint.Open).
func GenerateCheckpointID(ctx context.Context) (id.CheckpointID, error) {
    // A malformed/missing config resolves to a nil cfg here; PrimaryIsRefs(nil)
    // is false, so we fall through to the default hex format (fail-soft).
    if cfg, err := settings.LoadCheckpointsConfig(ctx); err == nil && PrimaryIsRefs(cfg) {
        return id.GenerateULID() //nolint:wrapcheck // dispatcher; id.GenerateULID already returns a descriptive error
    }
    return id.Generate() //nolint:wrapcheck // dispatcher; id.Generate already returns a descriptive error
}

Acmd/entire/cli/checkpoint/generate.go+26

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37

package checkpoint

import (
    "context"
    "testing"

"github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"

"github.com/entireio/cli/cmd/entire/cli/checkpoint/id"
)

// Not parallel: uses t.Setenv to drive the checkpoints-config env override.
func TestGenerateCheckpointID(t *testing.T) {
    ctx := context.Background()

t.Run("git-refs primary mints a ULID", func(t *testing.T) {
        t.Setenv("ENTIRE_CHECKPOINTS_PRIMARY", "git-refs")
        cid, err := GenerateCheckpointID(ctx)
        require.NoError(t, err)
        assert.Equal(t, id.KindULID, cid.Kind(), "git-refs primary should mint a ULID")
    })

t.Run("default primary mints legacy hex", func(t *testing.T) {
        t.Setenv("ENTIRE_CHECKPOINTS_PRIMARY", "") // unset → default git-branch
        cid, err := GenerateCheckpointID(ctx)
        require.NoError(t, err)
        assert.Equal(t, id.KindLegacy, cid.Kind(), "default primary should mint a 12-hex id")
    })

t.Run("git-branch primary mints legacy hex", func(t *testing.T) {
        t.Setenv("ENTIRE_CHECKPOINTS_PRIMARY", "git-branch")
        cid, err := GenerateCheckpointID(ctx)
        require.NoError(t, err)
        assert.Equal(t, id.KindLegacy, cid.Kind())
    })
}

Acmd/entire/cli/checkpoint/generate_test.go+37

7 unmodified lines  
8  
9  
10  
11  
12  
13  
14  
34 unmodified lines  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
94 unmodified lines  
154  
155  
156  
157  
158  
159  
160  
161  
162  
163  
164  
165  
166  
167  
168  
169  
170  
171  
172

7 unmodified lines

"encoding/json"
    "fmt"
    "regexp"
    "time"

ulid "github.com/oklog/ulid/v2"
)

// Used for tool use IDs, session IDs, and commit hashes in logs and messages.
const ShortIDLength = 12

// MaxIDLength is the longest a valid checkpoint ID can be — a 26-character ULID.
// Use it (not ShortIDLength) when reasoning about whether a string could be a
// checkpoint ID or a prefix of one, since IDs are no longer fixed-width.
const MaxIDLength = 26

// checkpointIDRegex validates the legacy format: exactly 12 lowercase hex characters.
var checkpointIDRegex = regexp.MustCompile(`^` + Pattern + `$`)

94 unmodified lines

return CheckpointID(hex.EncodeToString(bytes)), nil
}

// GenerateULID creates a new 26-character Crockford base32 ULID checkpoint ID:
// a millisecond timestamp prefix plus crypto-random entropy, so IDs are unique
// and lexicographically time-sortable. It is the format the git-refs store uses
// (chosen by checkpoint.GenerateCheckpointID); the value is canonical and passes
// KindOf/Validate as KindULID.
func GenerateULID() (CheckpointID, error) {
    u, err := ulid.New(ulid.Timestamp(time.Now()), rand.Reader)
    if err != nil {
        return EmptyCheckpointID, fmt.Errorf("failed to generate ULID checkpoint ID: %w", err)
    }
    return CheckpointID(u.String()), nil
}

// Validate checks if a string is a valid checkpoint ID format: either a legacy
// 12-character lowercase hex ID or a 26-character Crockford base32 ULID.
// Returns an error if invalid, nil if valid.

Mcmd/entire/cli/checkpoint/id/id.go+19

7 unmodified lines  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37

7 unmodified lines

// A representative ULID (Crockford base32, 26 chars) used across tests.
const sampleULID = "01KVBJCWYA4YW6J5M9GP655HZN"

func TestGenerateULID(t *testing.T) {
    t.Parallel()
    a, err := GenerateULID()
    if err != nil {
        t.Fatalf("GenerateULID() error = %v", err)
    }
    if err := Validate(string(a)); err != nil {
        t.Errorf("generated ULID %q failed Validate: %v", a, err)
    }
    if a.Kind() != KindULID {
        t.Errorf("Kind() = %v, want KindULID for %q", a.Kind(), a)
    }
    if len(string(a)) != 26 {
        t.Errorf("len = %d, want 26 for %q", len(string(a)), a)
    }
    b, err := GenerateULID()
    if err != nil {
        t.Fatalf("GenerateULID() error = %v", err)
    }
    if a == b {
        t.Errorf("two GenerateULID() calls returned the same id %q", a)
    }
}

func TestCheckpointID_Methods(t *testing.T) {
    t.Run("String", func(t *testing.T) {
        id := CheckpointID("a1b2c3d4e5f6")

Mcmd/entire/cli/checkpoint/id/id_test.go+24

529 unmodified lines  
530  
531  
532  
533  
534  
535  
536  
533  
534  
535  
536  
537  
538

529 unmodified lines

// Best-effort: on repo/list failures we return nil so the main flow
// surfaces the real error instead of double-reporting.
func runExplainAutoAmbiguityGuard(ctx context.Context, target string, lookup *explainCheckpointLookup, lookupErr error) error {
    // Targets longer than a checkpoint ID can't prefix-match one.
    // This is coupled to checkpoint IDs being fixed-width; longer targets
    // cannot be prefixes of committed checkpoint IDs.
    if len(target) > id.ShortIDLength {
    // Targets longer than the longest possible checkpoint ID (a 26-char ULID)
    // can't be a prefix of one, so they can't be an ambiguous checkpoint target.
    if len(target) > id.MaxIDLength {
        return nil
    }
    if lookupErr != nil {

Mcmd/entire/cli/explain.go+3/-4

1071 unmodified lines  
1072  
1073  
1074  
1075  
1075  
1076  
1077  
1078  
93 unmodified lines  
1172  
1173  
1174  
1175  
1175  
1176  
1177  
1178

1071 unmodified lines

}
    defer repo.Close()

checkpointID, err := id.Generate()
    checkpointID, err := cpkg.GenerateCheckpointID(ctx)
    if err != nil {
        return fmt.Errorf("failed to generate checkpoint ID: %w", err)
    }
93 unmodified lines

}
    defer repo.Close()

checkpointID, err := id.Generate()
    checkpointID, err := cpkg.GenerateCheckpointID(logCtx)
    if err != nil {
        logging.Warn(logCtx, "eager condense: failed to generate checkpoint ID",
            slog.String("error", err.Error()),

Mcmd/entire/cli/strategy/manual_commit_condensation.go+2/-2

464 unmodified lines  
465  
466  
467  
468  
468  
469  
470  
471  
1639 unmodified lines  
2111  
2112  
2113  
2114  
2114  
2115  
2116  
2117

464 unmodified lines

// Generate a fresh checkpoint ID and resolve session metadata
    _, resolveMetadataSpan := perf.Start(ctx, "resolve_session_metadata")
    checkpointID, err := id.Generate()
    checkpointID, err := checkpoint.GenerateCheckpointID(ctx)
    if err != nil {
        resolveMetadataSpan.RecordError(err)
        resolveMetadataSpan.End()
1639 unmodified lines

// (ACTIVE session + no TTY). Generates a checkpoint ID and adds the trailer
// directly, bypassing content detection and interactive prompts.
func (s *ManualCommitStrategy) addTrailerForAgentCommit(logCtx context.Context, commitMsgFile string, state *SessionState, source string) error { //nolint:unparam // kept for signature stability
    cpID, err := id.Generate()
    cpID, err := checkpoint.GenerateCheckpointID(logCtx)
    if err != nil {
        return nil //nolint:nilerr // Hook must be silent on failure
    }
}`}