@@ -34,13 +34,18 @@ Checks performed:
3434 entire/checkpoints/v1 branches share no common ancestor (caused by a
3535 previous bug). Fixes by cherry-picking local checkpoints onto remote tip.
3636
37+ 2. Checkpoint read mirror (checkpoints v1.1 only): detects when the
38+ local-only refs/entire/checkpoints/v1.1 read mirror is missing, stale,
39+ or diverged relative to entire/checkpoints/v1, which makes reads miss
40+ checkpoints. Fixes by pointing the mirror at the v1 tip.
41+
3742 When Codex hooks are installed:
38- 2 . Codex hook trust: warn when hooks declared in .codex/hooks.json
43+ 3 . Codex hook trust: warn when hooks declared in .codex/hooks.json
3944 lack a trusted_hash entry in the user's Codex config (i.e. /hooks
4045 review hasn't run yet on this machine, or a newer entire release
4146 added a hook the user hasn't approved yet).
4247
43- 3 . Stuck sessions: sessions stuck in ACTIVE or ENDED phase that need cleanup.
48+ 4 . Stuck sessions: sessions stuck in ACTIVE or ENDED phase that need cleanup.
4449
4550A session is considered stuck if:
4651 - It is in ACTIVE phase with no interaction for over 1 hour
@@ -90,6 +95,15 @@ func runSessionsFix(cmd *cobra.Command, force bool) error {
9095 fmt .Fprintf (cmd .ErrOrStderr (), "Error: metadata check failed: %v\n " , metadataErr )
9196 finalErr = NewSilentError (fmt .Errorf ("metadata check failed: %w" , metadataErr ))
9297 }
98+
99+ // Check 2: v1.1 read-mirror drift (after check 1: reconciliation rewrites
100+ // v1 and re-mirrors).
101+ if mirrorErr := checkCommittedMetadataMirror (cmd , force ); mirrorErr != nil {
102+ fmt .Fprintf (cmd .ErrOrStderr (), "Error: checkpoint read mirror check failed: %v\n " , mirrorErr )
103+ if finalErr == nil {
104+ finalErr = NewSilentError (fmt .Errorf ("checkpoint read mirror check failed: %w" , mirrorErr ))
105+ }
106+ }
93107 fmt .Fprintln (cmd .OutOrStdout ())
94108
95109 ctx := cmd .Context ()
@@ -355,22 +369,11 @@ func checkDisconnectedMetadata(cmd *cobra.Command, force bool) error {
355369 fmt .Fprintln (w , " Fix: cherry-pick local checkpoints onto remote tip (preserves all data)." )
356370
357371 if ! force {
358- var confirmed bool
359- form := NewAccessibleForm (
360- huh .NewGroup (
361- huh .NewConfirm ().
362- Title ("Fix disconnected metadata branches?" ).
363- Value (& confirmed ),
364- ),
365- )
366- if formErr := form .Run (); formErr != nil {
367- if errors .Is (formErr , huh .ErrUserAborted ) {
368- return nil
369- }
370- return fmt .Errorf ("prompt failed: %w" , formErr )
372+ proceed , promptErr := confirmDoctorFix (ctx , w , "Fix disconnected metadata branches?" )
373+ if promptErr != nil {
374+ return promptErr
371375 }
372- if ! confirmed {
373- fmt .Fprintln (w , " -> Skipped" )
376+ if ! proceed {
374377 return nil
375378 }
376379 }
@@ -383,6 +386,113 @@ func checkDisconnectedMetadata(cmd *cobra.Command, force bool) error {
383386 return nil
384387}
385388
389+ // checkCommittedMetadataMirror detects and optionally repairs v1.1 read-mirror
390+ // drift. Read paths use the mirror as-is, so doctor is the repair tool.
391+ // Silent when the topology has no mirror.
392+ func checkCommittedMetadataMirror (cmd * cobra.Command , force bool ) error {
393+ ctx := cmd .Context ()
394+ repo , err := openRepository (ctx )
395+ if err != nil {
396+ return fmt .Errorf ("failed to open repository: %w" , err )
397+ }
398+ defer repo .Close ()
399+
400+ diag , err := strategy .DiagnoseCommittedMetadataMirror (ctx , repo )
401+ if err != nil {
402+ return fmt .Errorf ("could not check checkpoint read mirror state: %w" , err )
403+ }
404+
405+ w := cmd .OutOrStdout ()
406+ primary := diag .Refs .Primary .Short ()
407+
408+ switch diag .Status {
409+ case strategy .MirrorNotConfigured :
410+ return nil
411+ case strategy .MirrorOK :
412+ fmt .Fprintln (w , "✓ Checkpoint read mirror: OK" )
413+ return nil
414+ case strategy .MirrorNoMetadata :
415+ fmt .Fprintln (w , "✓ Checkpoint read mirror: OK (no committed metadata yet)" )
416+ return nil
417+ case strategy .MirrorPrimaryMissing :
418+ fmt .Fprintf (w , "Checkpoint read mirror: %s\n " , diag .Status )
419+ fmt .Fprintf (w , " The read mirror %s exists, but the %s branch it mirrors is gone.\n " ,
420+ diag .Refs .Mirror , primary )
421+ fmt .Fprintln (w , " Restore the branch and re-run doctor:" )
422+ fmt .Fprintf (w , " git fetch origin %s:%s\n " , primary , primary )
423+ return nil
424+ case strategy .MirrorMissing :
425+ fmt .Fprintf (w , "Checkpoint read mirror: %s\n " , diag .Status )
426+ fmt .Fprintf (w , " %s does not exist; reads will find no checkpoints.\n " , diag .Refs .Mirror )
427+ fmt .Fprintf (w , " Fix: seed the mirror at the %s tip.\n " , primary )
428+ case strategy .MirrorBehind :
429+ fmt .Fprintf (w , "Checkpoint read mirror: %s\n " , diag .Status )
430+ fmt .Fprintf (w , " Mirror is at %s, behind %s at %s; reads miss newer checkpoints.\n " ,
431+ shortMirrorHash (diag .Mirror ), primary , shortMirrorHash (diag .Primary ))
432+ fmt .Fprintf (w , " Fix: advance the mirror to the %s tip.\n " , primary )
433+ case strategy .MirrorDiverged :
434+ fmt .Fprintf (w , "Checkpoint read mirror: %s\n " , diag .Status )
435+ fmt .Fprintf (w , " Mirror at %s has commits not on %s (at %s). Writes never target the\n " ,
436+ shortMirrorHash (diag .Mirror ), primary , shortMirrorHash (diag .Primary ))
437+ fmt .Fprintln (w , " mirror, so something outside entire moved it." )
438+ fmt .Fprintf (w , " Fix: reset the mirror to the %s tip — the diverged mirror commits\n " , primary )
439+ fmt .Fprintf (w , " are discarded (%s is the source of truth).\n " , primary )
440+ }
441+
442+ if ! force {
443+ proceed , promptErr := confirmDoctorFix (ctx , w , "Repair checkpoint read mirror?" )
444+ if promptErr != nil {
445+ return promptErr
446+ }
447+ if ! proceed {
448+ return nil
449+ }
450+ }
451+
452+ if fixErr := strategy .MirrorCommittedMetadataRef (ctx , repo , diag .Refs ); fixErr != nil {
453+ return fmt .Errorf ("failed to repair checkpoint read mirror: %w" , fixErr )
454+ }
455+ fmt .Fprintf (w , " ✓ Fixed: mirror now points at the %s tip\n " , primary )
456+ return nil
457+ }
458+
459+ // confirmDoctorFix prompts to apply a doctor fix. Declining (which prints
460+ // "-> Skipped"), aborting (Ctrl+C), and context cancellation all return false
461+ // with no error.
462+ func confirmDoctorFix (ctx context.Context , w io.Writer , title string ) (bool , error ) {
463+ // huh opens the TTY during form startup regardless of context state, so
464+ // guard explicitly to honor an already-cancelled command context.
465+ if ctx .Err () != nil {
466+ return false , nil //nolint:nilerr // cancelled context is a clean skip, not an error
467+ }
468+ var confirmed bool
469+ form := NewAccessibleForm (
470+ huh .NewGroup (
471+ huh .NewConfirm ().
472+ Title (title ).
473+ Value (& confirmed ),
474+ ),
475+ )
476+ if err := form .RunWithContext (ctx ); err != nil {
477+ if errors .Is (err , huh .ErrUserAborted ) || errors .Is (err , context .Canceled ) {
478+ return false , nil
479+ }
480+ return false , fmt .Errorf ("prompt failed: %w" , err )
481+ }
482+ if ! confirmed {
483+ fmt .Fprintln (w , " -> Skipped" )
484+ }
485+ return confirmed , nil
486+ }
487+
488+ // shortMirrorHash abbreviates a hash for mirror-check output; "none" when zero.
489+ func shortMirrorHash (h plumbing.Hash ) string {
490+ if h .IsZero () {
491+ return "none"
492+ }
493+ return h .String ()[:7 ]
494+ }
495+
386496// checkCodexHookTrust warns about two kinds of drift in the Codex hook
387497// setup:
388498//
0 commit comments