train_run: a run that already has a gate verdict is not trained again without --force

The exit code 2 of a failed gate made the cycle script retry and train
2026-10-01_v4-recipeA twice.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
jungfrauandClaude Fable 5.1 authored and jungfrau committed 2026-10-02 13:25:45 +02:00
1 parent a67e6b8e8e
commit 591f7b0b31
1 file changed
+6
+6
View File
@@ -174,12 +174,18 @@ def main(argv=None) -> int:
ap.add_argument("--baseline", default=None, help="gold report to gate against (default: the active model's)")
ap.add_argument("--no-promote", action="store_true", help="gate, but leave production.json alone")
ap.add_argument("--skip-train", action="store_true", help="reuse runs/<id>/ultralytics/train/weights/best.pt")
ap.add_argument("--force", action="store_true", help="train again a run that already has a gate verdict")
ap.add_argument("--set", nargs="*", default=[], metavar="KEY=VALUE",
help="override any Ultralytics train argument, e.g. fliplr=0 mask_ratio=1 overlap_mask=true; recorded in the ledger")
args = ap.parse_args(argv)
run = ledger.load(args.run_id)
run_dir = ledger.run_path(args.run_id).parent
# A gate verdict is an outcome, not a crash: the exit code 2 of a failed gate must not make a
# wrapper's retry train the whole run a second time (cycle 4 lost two hours that way).
if run.get("status") in ("gate_failed", "gate_passed", "promoted", "deployed") and not (args.skip_train or args.force):
print(f"{args.run_id} is already {run['status']}: not training it again (--force to retrain, --skip-train to re-measure)")
return 0
ds = dataset_dir(run)
data_yaml = resolved_yaml(run_dir, ds)
names = [v for _, v in sorted(json.loads((ds / "source.json").read_text())["classes"].items(), key=lambda kv: int(kv[0]))] \