@@ -112,7 +112,7 @@ fn project_entry_for_progress_file(
112112 index. project_started . user_message . as_deref ( ) ,
113113 )
114114 . unwrap_or_default ( ) ;
115- let rounds = project_list_rounds ( & status , & index) ;
115+ let rounds = project_list_rounds ( & index) ;
116116 let started_at_unix_secs = project_started_at_unix_secs ( workdir, & index) ;
117117
118118 Ok ( DiscoveredOverlayProject {
@@ -168,27 +168,28 @@ fn project_list_status(index: &PotterRolloutResumeIndex) -> PotterProjectListSta
168168 }
169169}
170170
171- fn project_list_rounds ( status : & PotterProjectListStatus , index : & PotterRolloutResumeIndex ) -> u32 {
172- if * status == PotterProjectListStatus :: Succeeded
173- && let Some ( rounds) = index. completed_rounds . iter ( ) . rev ( ) . find_map ( |round| {
174- round
175- . project_succeeded
176- . as_ref ( )
177- . map ( |succeeded| succeeded. rounds )
178- } )
179- {
180- return rounds;
181- }
171+ fn project_list_rounds ( index : & PotterRolloutResumeIndex ) -> u32 {
172+ // Count total rounds across the entire project lifecycle (including prior `resume` windows).
173+ //
174+ // Older/partially-corrupted logs might contain only the latest round marker. Preserve the
175+ // previous best-effort behavior by ensuring we never report fewer rounds than the last
176+ // recorded round index.
177+ let completed_rounds: u32 = index. completed_rounds . len ( ) . try_into ( ) . unwrap_or ( u32:: MAX ) ;
178+ let counted = completed_rounds. saturating_add ( u32:: from ( index. unfinished_round . is_some ( ) ) ) ;
182179
183- if let Some ( unfinished) = index. unfinished_round . as_ref ( ) {
184- return unfinished. round_current ;
185- }
186-
187- index
188- . completed_rounds
189- . last ( )
180+ let last_round_current = index
181+ . unfinished_round
182+ . as_ref ( )
190183 . map ( |round| round. round_current )
191- . unwrap_or_default ( )
184+ . or_else ( || {
185+ index
186+ . completed_rounds
187+ . last ( )
188+ . map ( |round| round. round_current )
189+ } )
190+ . unwrap_or_default ( ) ;
191+
192+ counted. max ( last_round_current)
192193}
193194
194195fn project_started_at_unix_secs ( workdir : & Path , index : & PotterRolloutResumeIndex ) -> Option < u64 > {
@@ -367,7 +368,7 @@ original goal line
367368 fn write_potter_rollout (
368369 project_dir : & Path ,
369370 user_prompt_file : & Path ,
370- round_current : u32 ,
371+ rounds : u32 ,
371372 round_total : u32 ,
372373 rollout_path : & Path ,
373374 outcome : PotterRoundOutcome ,
@@ -388,46 +389,55 @@ original goal line
388389 )
389390 . expect ( "append project_started" ) ;
390391
391- crate :: workflow:: rollout:: append_line (
392- & potter_rollout_path,
393- & crate :: workflow:: rollout:: PotterRolloutLine :: RoundStarted {
394- current : round_current,
395- total : round_total,
396- } ,
397- )
398- . expect ( "append round_started" ) ;
399-
400- crate :: workflow:: rollout:: append_line (
401- & potter_rollout_path,
402- & crate :: workflow:: rollout:: PotterRolloutLine :: RoundConfigured {
403- thread_id,
404- rollout_path : rollout_path. to_path_buf ( ) ,
405- service_tier : None ,
406- rollout_path_raw : None ,
407- rollout_base_dir : None ,
408- } ,
409- )
410- . expect ( "append round_configured" ) ;
392+ for round_current in 1 ..=rounds {
393+ crate :: workflow:: rollout:: append_line (
394+ & potter_rollout_path,
395+ & crate :: workflow:: rollout:: PotterRolloutLine :: RoundStarted {
396+ current : round_current,
397+ total : round_total,
398+ } ,
399+ )
400+ . expect ( "append round_started" ) ;
411401
412- if let Some ( rounds) = succeeded_rounds {
413402 crate :: workflow:: rollout:: append_line (
414403 & potter_rollout_path,
415- & crate :: workflow:: rollout:: PotterRolloutLine :: ProjectSucceeded {
416- rounds ,
417- duration_secs : 1 ,
418- user_prompt_file : user_prompt_file . to_path_buf ( ) ,
419- git_commit_start : "" . to_string ( ) ,
420- git_commit_end : "" . to_string ( ) ,
404+ & crate :: workflow:: rollout:: PotterRolloutLine :: RoundConfigured {
405+ thread_id ,
406+ rollout_path : rollout_path . to_path_buf ( ) ,
407+ service_tier : None ,
408+ rollout_path_raw : None ,
409+ rollout_base_dir : None ,
421410 } ,
422411 )
423- . expect ( "append project_succeeded" ) ;
424- }
412+ . expect ( "append round_configured" ) ;
413+
414+ if round_current == rounds
415+ && let Some ( project_succeeded_rounds) = succeeded_rounds
416+ {
417+ crate :: workflow:: rollout:: append_line (
418+ & potter_rollout_path,
419+ & crate :: workflow:: rollout:: PotterRolloutLine :: ProjectSucceeded {
420+ rounds : project_succeeded_rounds,
421+ duration_secs : 1 ,
422+ user_prompt_file : user_prompt_file. to_path_buf ( ) ,
423+ git_commit_start : "" . to_string ( ) ,
424+ git_commit_end : "" . to_string ( ) ,
425+ } ,
426+ )
427+ . expect ( "append project_succeeded" ) ;
428+ }
425429
426- crate :: workflow:: rollout:: append_line (
427- & potter_rollout_path,
428- & crate :: workflow:: rollout:: PotterRolloutLine :: RoundFinished { outcome } ,
429- )
430- . expect ( "append round_finished" ) ;
430+ let outcome = if round_current == rounds {
431+ outcome. clone ( )
432+ } else {
433+ PotterRoundOutcome :: Completed
434+ } ;
435+ crate :: workflow:: rollout:: append_line (
436+ & potter_rollout_path,
437+ & crate :: workflow:: rollout:: PotterRolloutLine :: RoundFinished { outcome } ,
438+ )
439+ . expect ( "append round_finished" ) ;
440+ }
431441 }
432442
433443 #[ test]
@@ -566,6 +576,34 @@ original goal line
566576 } ,
567577 )
568578 . expect ( "append project_started" ) ;
579+
580+ crate :: workflow:: rollout:: append_line (
581+ & potter_rollout_path,
582+ & crate :: workflow:: rollout:: PotterRolloutLine :: RoundStarted {
583+ current : 1 ,
584+ total : 10 ,
585+ } ,
586+ )
587+ . expect ( "append round_started" ) ;
588+ crate :: workflow:: rollout:: append_line (
589+ & potter_rollout_path,
590+ & crate :: workflow:: rollout:: PotterRolloutLine :: RoundConfigured {
591+ thread_id,
592+ rollout_path : Path :: new ( "live.jsonl" ) . to_path_buf ( ) ,
593+ service_tier : None ,
594+ rollout_path_raw : None ,
595+ rollout_base_dir : None ,
596+ } ,
597+ )
598+ . expect ( "append round_configured" ) ;
599+ crate :: workflow:: rollout:: append_line (
600+ & potter_rollout_path,
601+ & crate :: workflow:: rollout:: PotterRolloutLine :: RoundFinished {
602+ outcome : PotterRoundOutcome :: Completed ,
603+ } ,
604+ )
605+ . expect ( "append round_finished" ) ;
606+
569607 crate :: workflow:: rollout:: append_line (
570608 & potter_rollout_path,
571609 & crate :: workflow:: rollout:: PotterRolloutLine :: RoundStarted {
@@ -592,6 +630,107 @@ original goal line
592630 assert_eq ! ( rows[ 0 ] . rounds, 2 ) ;
593631 }
594632
633+ #[ test]
634+ fn discover_projects_rounds_count_across_resume_windows ( ) {
635+ let temp = tempfile:: tempdir ( ) . expect ( "tempdir" ) ;
636+ let workdir = temp. path ( ) ;
637+
638+ let main = write_main ( workdir, ".codexpotter/projects/2026/03/04/1" , None ) ;
639+ let rollout = workdir. join ( "rollout.jsonl" ) ;
640+ write_rollout_with_timestamp ( & rollout, "2026-03-04T00:00:00.000Z" ) ;
641+
642+ let project_dir = main. parent ( ) . expect ( "project dir" ) ;
643+ let user_prompt_file = main. strip_prefix ( workdir) . expect ( "rel" ) ;
644+
645+ // First iteration window: 3 rounds, then stop.
646+ write_potter_rollout (
647+ project_dir,
648+ user_prompt_file,
649+ 3 ,
650+ 10 ,
651+ Path :: new ( "rollout.jsonl" ) ,
652+ PotterRoundOutcome :: Interrupted ,
653+ None ,
654+ ) ;
655+
656+ // Resumed window: rounds reset to 1, succeeds in 2 rounds.
657+ let potter_rollout_path =
658+ project_dir. join ( crate :: workflow:: rollout:: POTTER_ROLLOUT_FILENAME ) ;
659+ let thread_id =
660+ codex_protocol:: ThreadId :: from_string ( "019ca423-63d9-7641-ae83-db060ad3c000" )
661+ . expect ( "thread id" ) ;
662+
663+ crate :: workflow:: rollout:: append_line (
664+ & potter_rollout_path,
665+ & crate :: workflow:: rollout:: PotterRolloutLine :: RoundStarted {
666+ current : 1 ,
667+ total : 10 ,
668+ } ,
669+ )
670+ . expect ( "append round_started" ) ;
671+ crate :: workflow:: rollout:: append_line (
672+ & potter_rollout_path,
673+ & crate :: workflow:: rollout:: PotterRolloutLine :: RoundConfigured {
674+ thread_id,
675+ rollout_path : PathBuf :: from ( "rollout.jsonl" ) ,
676+ service_tier : None ,
677+ rollout_path_raw : None ,
678+ rollout_base_dir : None ,
679+ } ,
680+ )
681+ . expect ( "append round_configured" ) ;
682+ crate :: workflow:: rollout:: append_line (
683+ & potter_rollout_path,
684+ & crate :: workflow:: rollout:: PotterRolloutLine :: RoundFinished {
685+ outcome : PotterRoundOutcome :: Completed ,
686+ } ,
687+ )
688+ . expect ( "append round_finished" ) ;
689+
690+ crate :: workflow:: rollout:: append_line (
691+ & potter_rollout_path,
692+ & crate :: workflow:: rollout:: PotterRolloutLine :: RoundStarted {
693+ current : 2 ,
694+ total : 10 ,
695+ } ,
696+ )
697+ . expect ( "append round_started" ) ;
698+ crate :: workflow:: rollout:: append_line (
699+ & potter_rollout_path,
700+ & crate :: workflow:: rollout:: PotterRolloutLine :: RoundConfigured {
701+ thread_id,
702+ rollout_path : PathBuf :: from ( "rollout.jsonl" ) ,
703+ service_tier : None ,
704+ rollout_path_raw : None ,
705+ rollout_base_dir : None ,
706+ } ,
707+ )
708+ . expect ( "append round_configured" ) ;
709+ crate :: workflow:: rollout:: append_line (
710+ & potter_rollout_path,
711+ & crate :: workflow:: rollout:: PotterRolloutLine :: ProjectSucceeded {
712+ rounds : 2 ,
713+ duration_secs : 1 ,
714+ user_prompt_file : user_prompt_file. to_path_buf ( ) ,
715+ git_commit_start : "" . to_string ( ) ,
716+ git_commit_end : "" . to_string ( ) ,
717+ } ,
718+ )
719+ . expect ( "append project_succeeded" ) ;
720+ crate :: workflow:: rollout:: append_line (
721+ & potter_rollout_path,
722+ & crate :: workflow:: rollout:: PotterRolloutLine :: RoundFinished {
723+ outcome : PotterRoundOutcome :: Completed ,
724+ } ,
725+ )
726+ . expect ( "append round_finished" ) ;
727+
728+ let rows = discover_projects_for_overlay ( workdir) . expect ( "discover" ) ;
729+ assert_eq ! ( rows. len( ) , 1 ) ;
730+ assert_eq ! ( rows[ 0 ] . status, PotterProjectListStatus :: Succeeded ) ;
731+ assert_eq ! ( rows[ 0 ] . rounds, 5 ) ;
732+ }
733+
595734 #[ test]
596735 fn discover_resumable_projects_filters_missing_rollout_files ( ) {
597736 let temp = tempfile:: tempdir ( ) . expect ( "tempdir" ) ;
0 commit comments