@@ -382,11 +382,28 @@ func DiscoverClaudeProjects(projectsDir string) []DiscoveredFile {
382382 return files
383383}
384384
385- // DiscoverCodexSessions finds all JSONL files under the Codex
386- // sessions dir ( year/month/day structure) .
385+ // DiscoverCodexSessions finds all Codex JSONL session files under
386+ // either the standard year/month/day layout or a flat archived dir .
387387func DiscoverCodexSessions (sessionsDir string ) []DiscoveredFile {
388388 var files []DiscoveredFile
389389
390+ entries , err := os .ReadDir (sessionsDir )
391+ if err != nil {
392+ return nil
393+ }
394+ for _ , entry := range entries {
395+ if entry .IsDir () {
396+ continue
397+ }
398+ if ! isCodexSessionFilename (entry .Name ()) {
399+ continue
400+ }
401+ files = append (files , DiscoveredFile {
402+ Path : filepath .Join (sessionsDir , entry .Name ()),
403+ Agent : AgentCodex ,
404+ })
405+ }
406+
390407 walkCodexDayDirs (sessionsDir , func (dayPath string ) bool {
391408 entries , err := os .ReadDir (dayPath )
392409 if err != nil {
@@ -396,7 +413,7 @@ func DiscoverCodexSessions(sessionsDir string) []DiscoveredFile {
396413 if sf .IsDir () {
397414 continue
398415 }
399- if ! strings . HasSuffix (sf .Name (), ".jsonl" ) {
416+ if ! isCodexSessionFilename (sf .Name ()) {
400417 continue
401418 }
402419 files = append (files , DiscoveredFile {
@@ -473,16 +490,34 @@ func FindClaudeSourceFile(
473490}
474491
475492// FindCodexSourceFile finds a Codex session file by UUID.
476- // Searches the year/month/day directory structure for files matching
477- // rollout-{timestamp}-{uuid}.jsonl .
493+ // Prefers the standard year/month/day live path when present,
494+ // then falls back to a flat archived dir entry .
478495func FindCodexSourceFile (sessionsDir , sessionID string ) string {
479496 if ! IsValidSessionID (sessionID ) {
480497 return ""
481498 }
482499
483- var result string
500+ var archived string
501+ entries , err := os .ReadDir (sessionsDir )
502+ if err == nil {
503+ for _ , f := range entries {
504+ if f .IsDir () {
505+ continue
506+ }
507+ name := f .Name ()
508+ if ! isCodexSessionFilename (name ) {
509+ continue
510+ }
511+ if extractUUIDFromRollout (name ) == sessionID {
512+ archived = filepath .Join (sessionsDir , name )
513+ break
514+ }
515+ }
516+ }
517+
518+ var live string
484519 walkCodexDayDirs (sessionsDir , func (dayPath string ) bool {
485- if result != "" {
520+ if live != "" {
486521 return false
487522 }
488523 entries , err := os .ReadDir (dayPath )
@@ -494,18 +529,83 @@ func FindCodexSourceFile(sessionsDir, sessionID string) string {
494529 continue
495530 }
496531 name := f .Name ()
497- if ! strings .HasPrefix (name , "rollout-" ) ||
498- ! strings .HasSuffix (name , ".jsonl" ) {
532+ if ! isCodexSessionFilename (name ) {
499533 continue
500534 }
501535 if extractUUIDFromRollout (name ) == sessionID {
502- result = filepath .Join (dayPath , name )
536+ live = filepath .Join (dayPath , name )
503537 return false
504538 }
505539 }
506540 return true
507541 })
508- return result
542+ if live != "" {
543+ return live
544+ }
545+ return archived
546+ }
547+
548+ func isCodexSessionFilename (name string ) bool {
549+ return strings .HasPrefix (name , "rollout-" ) &&
550+ strings .HasSuffix (name , ".jsonl" )
551+ }
552+
553+ // CodexSessionUUIDFromFilename extracts the canonical session UUID
554+ // from a Codex rollout filename. Returns "" when the filename does
555+ // not match Codex session naming.
556+ func CodexSessionUUIDFromFilename (name string ) string {
557+ if ! isCodexSessionFilename (name ) {
558+ return ""
559+ }
560+ return extractUUIDFromRollout (name )
561+ }
562+
563+ // CodexLayout reports which on-disk layout a Codex session path uses.
564+ type CodexLayout int
565+
566+ const (
567+ CodexLayoutUnknown CodexLayout = iota
568+ CodexLayoutArchivedFlat
569+ CodexLayoutDated
570+ )
571+
572+ // CodexSessionPathInfo parses a Codex path relative to a configured
573+ // root and reports whether it is a valid session path plus its layout
574+ // and canonical session UUID.
575+ func CodexSessionPathInfo (root , path string ) (CodexLayout , string , bool ) {
576+ root = filepath .Clean (root )
577+ path = filepath .Clean (path )
578+ rel , err := filepath .Rel (root , path )
579+ if err != nil {
580+ return CodexLayoutUnknown , "" , false
581+ }
582+ sep := string (filepath .Separator )
583+ if rel == "." || rel == ".." || strings .HasPrefix (rel , ".." + sep ) {
584+ return CodexLayoutUnknown , "" , false
585+ }
586+ if ! strings .HasSuffix (path , ".jsonl" ) {
587+ return CodexLayoutUnknown , "" , false
588+ }
589+ parts := strings .Split (rel , sep )
590+ switch len (parts ) {
591+ case 1 :
592+ if ! isCodexSessionFilename (parts [0 ]) {
593+ return CodexLayoutUnknown , "" , false
594+ }
595+ return CodexLayoutArchivedFlat ,
596+ CodexSessionUUIDFromFilename (parts [0 ]), true
597+ case 4 :
598+ if ! IsDigits (parts [0 ]) || ! IsDigits (parts [1 ]) || ! IsDigits (parts [2 ]) {
599+ return CodexLayoutUnknown , "" , false
600+ }
601+ if ! isCodexSessionFilename (parts [3 ]) {
602+ return CodexLayoutUnknown , "" , false
603+ }
604+ return CodexLayoutDated ,
605+ CodexSessionUUIDFromFilename (parts [3 ]), true
606+ default :
607+ return CodexLayoutUnknown , "" , false
608+ }
509609}
510610
511611// walkCodexDayDirs traverses a Codex sessions directory with
0 commit comments