From 5b569af383cc76eb7a94b55ac14f649bd130b2d9 Mon Sep 17 00:00:00 2001 From: Timmy Time Date: Sun, 22 Mar 2026 01:38:07 +0000 Subject: [PATCH] [loop-cycle] fix: consume cycle_result.json after reading (#897) (#898) --- scripts/cycle_retro.py | 2 ++ tests/loop/test_cycle_retro.py | 49 ++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/scripts/cycle_retro.py b/scripts/cycle_retro.py index ddfc4c56..87b6273f 100644 --- a/scripts/cycle_retro.py +++ b/scripts/cycle_retro.py @@ -277,6 +277,8 @@ def main() -> None: args.tests_passed = int(cr["tests_passed"]) if not args.notes and cr.get("notes"): args.notes = cr["notes"] + # Consume-once: delete after reading so stale results don't poison future cycles + CYCLE_RESULT_FILE.unlink(missing_ok=True) # Auto-detect issue from branch when not explicitly provided if args.issue is None: diff --git a/tests/loop/test_cycle_retro.py b/tests/loop/test_cycle_retro.py index 53fc19e4..d393e1f3 100644 --- a/tests/loop/test_cycle_retro.py +++ b/tests/loop/test_cycle_retro.py @@ -58,6 +58,55 @@ class TestDetectIssueFromBranch: assert mod.detect_issue_from_branch() is None +class TestConsumeOnce: + """cycle_result.json must be deleted after reading.""" + + def test_cycle_result_deleted_after_read(self, mod, tmp_path): + """After _load_cycle_result() data is consumed in main(), the file is deleted.""" + result_file = tmp_path / "cycle_result.json" + result_file.write_text('{"issue": 42, "type": "bug"}') + + with ( + patch.object(mod, "CYCLE_RESULT_FILE", result_file), + patch.object(mod, "RETRO_FILE", tmp_path / "retro" / "cycles.jsonl"), + patch.object(mod, "SUMMARY_FILE", tmp_path / "retro" / "summary.json"), + patch.object(mod, "EPOCH_COUNTER_FILE", tmp_path / "retro" / ".epoch_counter"), + patch( + "sys.argv", + ["cycle_retro", "--cycle", "1", "--success", "--main-green", "--duration", "60"], + ), + ): + mod.main() + + assert not result_file.exists(), "cycle_result.json should be deleted after consumption" + + def test_cycle_result_not_deleted_when_empty(self, mod, tmp_path): + """If cycle_result.json doesn't exist, no error occurs.""" + result_file = tmp_path / "nonexistent_result.json" + + with ( + patch.object(mod, "CYCLE_RESULT_FILE", result_file), + patch.object(mod, "RETRO_FILE", tmp_path / "retro" / "cycles.jsonl"), + patch.object(mod, "SUMMARY_FILE", tmp_path / "retro" / "summary.json"), + patch.object(mod, "EPOCH_COUNTER_FILE", tmp_path / "retro" / ".epoch_counter"), + patch( + "sys.argv", + [ + "cycle_retro", + "--cycle", + "1", + "--success", + "--main-green", + "--duration", + "60", + "--issue", + "10", + ], + ), + ): + mod.main() # Should not raise + + class TestBackfillExtractIssueNumber: """Tests for backfill_retro.extract_issue_number PR-number filtering."""