@@ -630,15 +630,28 @@ def finish(
630630 except Exception :
631631 pass
632632 _run_async (self ._job .update_progress (100 )) # type: ignore[union-attr]
633- _run_async (
634- self ._job .update_job_data_fields ( # type: ignore[union-attr]
635- {
636- "completion_status" : "success" ,
637- "completion_details" : message ,
638- "end_time" : time .strftime ("%Y-%m-%d %H:%M:%S" , time .gmtime ()),
639- }
640- )
641- )
633+
634+ # Resolve score before the first write so it's included atomically with the
635+ # other completion fields. This prevents a window where completion_status is
636+ # "success" but score is still null (observable by caches or concurrent readers).
637+ resolved_score : Dict [str , Any ] = {}
638+ if isinstance (score , dict ):
639+ resolved_score .update (score )
640+ resolved_score .setdefault ("discard" , False )
641+
642+ completion_fields : Dict [str , Any ] = {
643+ "completion_status" : "success" ,
644+ "completion_details" : message ,
645+ "end_time" : time .strftime ("%Y-%m-%d %H:%M:%S" , time .gmtime ()),
646+ "score" : resolved_score ,
647+ }
648+ if additional_output_path is not None and additional_output_path .strip () != "" :
649+ completion_fields ["additional_output_path" ] = additional_output_path
650+ if plot_data_path is not None and plot_data_path .strip () != "" :
651+ completion_fields ["plot_data_path" ] = plot_data_path
652+
653+ _run_async (self ._job .update_job_data_fields (completion_fields )) # type: ignore[union-attr]
654+
642655 # Best-effort Trackio integration: finish our own run if we created one,
643656 # then capture the active Trackio DB (managed or user-created) into job artifacts.
644657 try :
@@ -662,15 +675,6 @@ def finish(
662675 except Exception :
663676 # Never let optional Trackio integration break finish()
664677 logger .debug ("Trackio integration failed during finish()" , exc_info = True )
665- resolved_score : Dict [str , Any ] = {}
666- if isinstance (score , dict ):
667- resolved_score .update (score )
668- resolved_score .setdefault ("discard" , False )
669- _run_async (self ._job .update_job_data_field ("score" , resolved_score )) # type: ignore[union-attr]
670- if additional_output_path is not None and additional_output_path .strip () != "" :
671- _run_async (self ._job .update_job_data_field ("additional_output_path" , additional_output_path )) # type: ignore[union-attr]
672- if plot_data_path is not None and plot_data_path .strip () != "" :
673- _run_async (self ._job .update_job_data_field ("plot_data_path" , plot_data_path )) # type: ignore[union-attr]
674678
675679 # Important: update cached_jobs only when all completion fields are already written.
676680 _run_async (self ._job .update_status (JobStatus .COMPLETE )) # type: ignore[union-attr]
0 commit comments