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
cmd/entire/cli
Mattach.go+3/-3
checkpoint
Agenerate.go+26
Agenerate_test.go+37
id
Mid.go+19
Mid_test.go+24
Mexplain.go+3/-4
strategy
Mmanual_commit_condensation.go+2/-2
Mmanual_commit_hooks.go+2/-2
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
}
}`}