From 80c919362a15b513514d9c8fc1068b9f22d80ded Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 04:29:23 +0000 Subject: [PATCH 1/3] Initial plan From ed9421bb39135207309c3e710e14298eb9201026 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 04:31:41 +0000 Subject: [PATCH 2/3] Fix autoloop pre-step starvation: clone repo-memory before scheduler + deterministic tie-break Agent-Logs-Url: https://github.com/githubnext/tsessebe/sessions/a98d4d22-8daa-4733-ab25-6cd7025a0ddc Co-authored-by: mrjf <180956+mrjf@users.noreply.github.com> --- .github/workflows/autoloop.md | 41 ++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/.github/workflows/autoloop.md b/.github/workflows/autoloop.md index d59202c6..eda1b80a 100644 --- a/.github/workflows/autoloop.md +++ b/.github/workflows/autoloop.md @@ -84,6 +84,30 @@ imports: - shared/reporting.md steps: + - name: Clone repo-memory for scheduler + # The "Check which programs are due" step below reads program state from + # /tmp/gh-aw/repo-memory/autoloop/, but gh-aw's built-in repo-memory clone + # runs *after* this pre-step (and clones to a different directory). Without + # this step, every program looks like a "first run" and the tiebreaker + # starves programs that lose the original-insertion-order tiebreak. + # See issue: "Autoloop pre-step can't read state files". + env: + GITHUB_TOKEN: ${{ github.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + run: | + mkdir -p /tmp/gh-aw/repo-memory + if [ -d /tmp/gh-aw/repo-memory/autoloop/.git ]; then + echo "repo-memory/autoloop already cloned; skipping" + else + git clone --depth=1 --branch memory/autoloop \ + "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \ + /tmp/gh-aw/repo-memory/autoloop \ + || { + echo "memory/autoloop branch not found (first run); creating empty dir" + mkdir -p /tmp/gh-aw/repo-memory/autoloop + } + fi + - name: Check which programs are due env: GITHUB_TOKEN: ${{ github.token }} @@ -377,7 +401,8 @@ steps: "next_due": (last_run + schedule_delta).isoformat()}) continue - due.append({"name": name, "last_run": lr, "file": pf, "target_metric": target_metric}) + due.append({"name": name, "last_run": lr, "file": pf, "target_metric": target_metric, + "schedule_seconds": schedule_delta.total_seconds() if schedule_delta else None}) # Pick the program to run selected = None @@ -422,8 +447,18 @@ steps: pass print(f"FORCED: running program '{forced_program}' (manual dispatch)") elif due: - # Normal scheduling: pick the single most-overdue program - due.sort(key=lambda p: p["last_run"] or "") # None/empty sorts first (never run) + # Normal scheduling: pick the single most-overdue program. + # Tiebreaker rationale: programs that have never run (no last_run) take + # priority over ever-run programs; among never-run programs, prefer the + # shortest schedule (so "every 30m" beats "every 6h"), then alphabetical + # by name. This avoids permanent starvation when state is missing — see + # issue: "Autoloop pre-step can't read state files". + def _due_sort_key(p): + if p["last_run"]: + return (1, p["last_run"], p["name"]) + sched = p.get("schedule_seconds") + return (0, sched if sched is not None else float("inf"), p["name"]) + due.sort(key=_due_sort_key) selected = due[0]["name"] selected_file = due[0]["file"] selected_target_metric = due[0].get("target_metric") From 4930b2eff93d5e86b3051dba79a89e90f15a0c5a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 21 Apr 2026 04:32:45 +0000 Subject: [PATCH 3/3] Address review: avoid token-in-URL via http.extraHeader; document inf fallback Agent-Logs-Url: https://github.com/githubnext/tsessebe/sessions/a98d4d22-8daa-4733-ab25-6cd7025a0ddc Co-authored-by: mrjf <180956+mrjf@users.noreply.github.com> --- .github/workflows/autoloop.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/autoloop.md b/.github/workflows/autoloop.md index eda1b80a..69d6057c 100644 --- a/.github/workflows/autoloop.md +++ b/.github/workflows/autoloop.md @@ -99,8 +99,11 @@ steps: if [ -d /tmp/gh-aw/repo-memory/autoloop/.git ]; then echo "repo-memory/autoloop already cloned; skipping" else - git clone --depth=1 --branch memory/autoloop \ - "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \ + # Pass the token via an http.extraHeader rather than embedding it in the + # URL — keeps it out of process listings and any logged remote URLs. + AUTH_HEADER="Authorization: Basic $(printf 'x-access-token:%s' "${GITHUB_TOKEN}" | base64 -w0)" + git -c "http.extraHeader=${AUTH_HEADER}" clone --depth=1 --branch memory/autoloop \ + "https://github.com/${GITHUB_REPOSITORY}.git" \ /tmp/gh-aw/repo-memory/autoloop \ || { echo "memory/autoloop branch not found (first run); creating empty dir" @@ -451,8 +454,9 @@ steps: # Tiebreaker rationale: programs that have never run (no last_run) take # priority over ever-run programs; among never-run programs, prefer the # shortest schedule (so "every 30m" beats "every 6h"), then alphabetical - # by name. This avoids permanent starvation when state is missing — see - # issue: "Autoloop pre-step can't read state files". + # by name. Programs with no parseable schedule sort last among never-run + # programs (float('inf')). This avoids permanent starvation when state + # is missing — see issue: "Autoloop pre-step can't read state files". def _due_sort_key(p): if p["last_run"]: return (1, p["last_run"], p["name"])