# Sanitize Codex transcripts on the compact finalize path

`847d475`→[main](/content/gh/entireio/cli/commits/main/index.html)·

computermode·3w ago·2 files·+95 added/-5 removed

The initial-write path (writeTranscript) sanitizes Codex transcripts via
codex.SanitizePortableTranscript before compaction, but the finalize path
(replaceTranscript) passed raw bytes to writeCompactTranscript. Because
sanitization drops compaction/encrypted lines, the checkpoint-scoped compact
(sliced by StartLine) diverged from the initial write for Codex after
UpdateCommitted.

Sanitize the compact's input on the finalize path so it matches the initial
write. The sanitization runs exactly once per path — the initial path already
passes sanitized bytes, so writeCompactTranscript no longer re-sanitizes.

Addresses Cursor Bugbot "Codex sanitize skipped on finalize".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

## Sessions

c6722b602c45View transcript

[?\
Merge Main and Resolve Transcript ConflictsClaude Code·Fable 5·1 step](/content/gh/entireio/cli/session/cde6bd89-1d46-477f-b499-bb2e6b8d2a2e#timeline-c6722b602c45/index.html)

## Changes

2

- cmd/entire/cli/checkpoint

- Mcommitted.go+12/-3

- Mcommitted\_compact\_transcript\_test.go+83/-2

```
839 unmodified lines

840
841
842
843
843
844
845
846
847
848
922 unmodified lines

1771
1772
1773
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785

839 unmodified lines

// compact transcript.jsonl format, scoped to this checkpoint via startLine,
// and records it at sessionPath in the tree. Best-effort: the compact
// transcript is derived data, so failures are logged and never fail the
// checkpoint write.
// checkpoint write. transcriptBytes must already be sanitized for the agent
// (e.g. Codex portable-transcript sanitization); callers sanitize before
// calling so the expensive pass runs exactly once.
func (s *GitStore) writeCompactTranscript(ctx context.Context, agentType types.AgentType, startLine int, transcriptBytes []byte, sessionPath string, entries map[string]object.TreeEntry) {
	compactCtx, compactSpan := perf.Start(ctx, "write_compact_transcript")
	defer compactSpan.End()
922 unmodified lines

// Regenerate the compact transcript from the new content so the pushed
	// transcript.jsonl stays current. Best-effort: on generation failure the
	// previous transcript.jsonl entry (if any) is left in place.
	s.writeCompactTranscript(ctx, agentType, startLine, transcript.Bytes(), sessionPath, entries)
	// previous transcript.jsonl entry (if any) is left in place. Codex
	// transcripts are sanitized first to match the initial-write path
	// (writeTranscript), which sanitizes before compaction; this finalize path
	// otherwise passes raw bytes.
	compactBytes := transcript.Bytes()
	if agentType == agent.AgentTypeCodex {
		compactBytes = codex.SanitizePortableTranscript(compactBytes)
	}
	s.writeCompactTranscript(ctx, agentType, startLine, compactBytes, sessionPath, entries)

return nil
}
```

Mcmd/entire/cli/checkpoint/committed.go+12/-3

```
10 unmodified lines

11
12
13
14
15
14
15
16
17
18
19
20
179 unmodified lines

200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285

10 unmodified lines

"github.com/entireio/cli/cmd/entire/cli/paths"
	"github.com/entireio/cli/redact"

// Registers the Claude Code agent so compactAgentName resolves the
	// "claude-code" slug instead of falling back to the raw agent type.
	// Registers the Claude Code and Codex agents so compactAgentName resolves
	// their slugs instead of falling back to the raw agent type.
	_ "github.com/entireio/cli/cmd/entire/cli/agent/claudecode"
	_ "github.com/entireio/cli/cmd/entire/cli/agent/codex"
	// claudeStyleTranscript returns a Claude Code-format JSONL transcript with two
179 unmodified lines

}

// codexTranscriptWithCompactionBeforeStart returns a Codex-format JSONL
// transcript whose line 1 is a `compaction` entry that
// codex.SanitizePortableTranscript drops. With a checkpoint start of line 2,
// slicing the raw (unsanitized) transcript yields [beta, gamma] while slicing
// the sanitized transcript (compaction removed) yields only [gamma] — so the
// compact transcript diverges unless the finalize path sanitizes like the
// initial-write path does.
func codexTranscriptWithCompactionBeforeStart() []byte {
	lines := []string{
		`{"timestamp":"2026-01-01T00:00:00Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"alpha"}]}}`,
		`{"timestamp":"2026-01-01T00:00:01Z","type":"response_item","payload":{"type":"compaction","encrypted_content":"REDACTED"}}`,
		`{"timestamp":"2026-01-01T00:00:02Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"beta"}]}}`,
		`{"timestamp":"2026-01-01T00:00:03Z","type":"response_item","payload":{"type":"message","role":"user","content":[{"type":"input_text","text":"gamma"}]}}`,
	}
	return []byte(strings.Join(lines, "\n") + "\n")
}

// TestUpdateCommitted_CodexCompactSanitizedLikeInitialWrite guards against the
// finalize path compacting raw Codex bytes while the initial-write path
// compacts sanitized bytes. Both must produce the same checkpoint-scoped
// compact transcript.
func TestUpdateCommitted_CodexCompactSanitizedLikeInitialWrite(t *testing.T) {
	t.Parallel()
	repo, _ := setupTestRepo(t)
	store := NewGitStore(repo, DefaultV1Refs())
	cpID := id.MustCheckpointID("e5f6a1b2c3d4")

raw := codexTranscriptWithCompactionBeforeStart()
	compactPath := cpID.Path() + "/0/" + paths.CompactTranscriptFileName

// Initial write sanitizes before compaction. With start=2 the dropped
	// compaction line shifts the window so only "gamma" survives.
	err := store.WriteCommitted(context.Background(), WriteCommittedOptions{
		CheckpointID:              cpID,
		SessionID:                 "session-001",
		Strategy:                  "manual-commit",
		Transcript:                redact.AlreadyRedacted(raw),
		Agent:                     agent.AgentTypeCodex,
		CheckpointTranscriptStart: 2,
		AuthorName:                "Test",
		AuthorEmail:               "test@test.com",
	})
	if err != nil {
		t.Fatalf("WriteCommitted() error = %v", err)
	}
	initialCompact, ok := readBranchFile(t, store, compactPath)
	if !ok {
		t.Fatal("transcript.jsonl missing after WriteCommitted")
	}
	if strings.Contains(initialCompact, "beta") {
		t.Errorf("initial compact contains pre-start content:\n%s", initialCompact)
	}
	if !strings.Contains(initialCompact, "gamma") {
		t.Errorf("initial compact missing checkpoint-scoped content:\n%s", initialCompact)
	}

// Finalize with the same raw transcript. replaceTranscript must sanitize
	// before compaction; otherwise the raw slice at line 2 would reintroduce
	// "beta".
	err = store.UpdateCommitted(context.Background(), UpdateCommittedOptions{
		CheckpointID: cpID,
		SessionID:    "session-001",
		Transcript:   redact.AlreadyRedacted(raw),
		Agent:        agent.AgentTypeCodex,
	})
	if err != nil {
		t.Fatalf("UpdateCommitted() error = %v", err)
	}
	finalizeCompact, ok := readBranchFile(t, store, compactPath)
	if !ok {
		t.Fatal("transcript.jsonl missing after UpdateCommitted")
	}
	if strings.Contains(finalizeCompact, "beta") {
		t.Errorf("finalize compact contains pre-start content (raw bytes not sanitized):\n%s", finalizeCompact)
	}
	if finalizeCompact != initialCompact {
		t.Errorf("finalize compact diverges from initial write:\ninitial:  %s\nfinalize: %s", initialCompact, finalizeCompact)
	}
}

func TestUpdateCommitted_RegeneratesCompactTranscript(t *testing.T) {
	t.Parallel()
	repo, _ := setupTestRepo(t)
