From 196eb90be65b2216ec1a77fd9d8d820ffd035ad4 Mon Sep 17 00:00:00 2001 From: Rongsong Shen Date: Tue, 29 Sep 2026 07:26:16 +0000 Subject: [PATCH] fix(gitea-sync): filter milestone issues with milestones=, adopt phase issues left by an interrupted run Gitea ignores the singular milestone= query parameter and returns the newest issues repo-wide, so list_issues() never filtered by milestone: phase-state updates only saw issues on the first page. Use milestones=. sync_spec() adopted an existing milestone but always created its three phase issues, so a run killed between creating an issue and writing spec.json created duplicates on the next run. Reuse an issue already in the milestone with the same title. Co-Authored-By: Claude Opus 5.5 (1M context) --- scripts/gitea-sync.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/scripts/gitea-sync.py b/scripts/gitea-sync.py index 187b7517..a383870b 100755 --- a/scripts/gitea-sync.py +++ b/scripts/gitea-sync.py @@ -296,9 +296,11 @@ class GiteaAPI: def list_issues(self, milestone_id=None, state="all"): """List issues, optionally filtered by milestone.""" - params = [f"state={state}"] + params = [f"state={state}", "type=issues"] if milestone_id is not None: - params.append(f"milestone={milestone_id}") + # Gitea filters on `milestones` (names or ids); it silently + # ignores `milestone` and returns the newest issues repo-wide. + params.append(f"milestones={milestone_id}") url = "?" + "&".join(params) _, issues = request("GET", f"/issues{url}") return issues or [] @@ -682,15 +684,27 @@ def sync_spec(api, spec_name, dry_run=False, quiet=False): warn(f" could not find or create milestone for '{spec_name}'") return - # Create phase issues + # Create phase issues, adopting any a run interrupted before its + # spec.json write-back already created in this milestone. milestone_id = milestone["id"] phase_issues = {} + existing_by_title = { + i["title"]: i["number"] + for i in api.list_issues(milestone_id=milestone_id, state="all") + } for phase_def in PHASE_DEFS: title = f"[{spec_name}] {phase_def['title_suffix']}" state = _spec_desired_states(spec_data)[0][phase_def["key"]] + if title in existing_by_title: + phase_issues[phase_def["key"]] = existing_by_title[title] + if not quiet: + print(f" adopting existing issue " + f"#{existing_by_title[title]}: {title}") + continue + # Build body if phase_def.get("body_fn_extra"): body = phase_def["body_fn_extra"](spec_name, spec_data)