From 402c501645d3fe298b808f859a9c132443bab776 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 04:38:32 +0000 Subject: [PATCH 1/3] Initial plan From 8526222ee35eb1ef3d07447c38d9be2bf259b68e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 04:45:13 +0000 Subject: [PATCH 2/3] Add repo-memory clone step before scheduling pre-step The repo-memory clone step (from the tools: section) was placed after the "Check which programs are due" pre-step by the gh-aw compiler, so the scheduling script could not read persisted state from previous runs. This caused incorrect selection/skip behavior. Fix: add an explicit shallow clone of the memory/autoloop branch as a new pre-step that runs before the scheduling step, ensuring state files are available when scheduling decisions are made. Also adds tests to verify the step ordering invariant. Agent-Logs-Url: https://github.com/githubnext/autoloop/sessions/3013e45a-953d-4f06-bdab-ed720379b412 Co-authored-by: mrjf <180956+mrjf@users.noreply.github.com> --- tests/test_scheduling.py | 58 ++++++++++++++++++++++++++++++++++++++++ workflows/autoloop.md | 22 +++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/tests/test_scheduling.py b/tests/test_scheduling.py index a78d757..3c8728b 100644 --- a/tests/test_scheduling.py +++ b/tests/test_scheduling.py @@ -680,3 +680,61 @@ def test_get_program_name_extracted(self): def test_read_program_state_extracted(self): # read_program_state exists in the workflow but depends on file I/O assert "read_program_state" in _funcs + + +# --------------------------------------------------------------------------- +# Workflow step ordering — repo-memory must be available before scheduling +# --------------------------------------------------------------------------- + +class TestWorkflowStepOrdering: + """Verify that the repo-memory clone step appears before the scheduling step. + + The scheduling pre-step reads persisted state from repo-memory. If the + clone happens after scheduling, the script cannot see previous-run state, + causing incorrect selection/skip behaviour. + """ + + def _load_steps(self): + """Return the list of pre-step names from workflows/autoloop.md.""" + import os, re, yaml + wf_path = os.path.join(os.path.dirname(__file__), "..", "workflows", "autoloop.md") + with open(wf_path) as f: + content = f.read() + # The YAML frontmatter (before `---` + markdown body) contains the steps. + # Extract the frontmatter block (first --- ... --- block including 'steps:'). + # The source file has the structure: ---\n\n---\nsteps:\n - ...\n\nsource: ... + # We need everything from start to the line "source: ..." or "engine: ..." + # Actually, the whole pre-body section is YAML-ish; let's just find step names. + step_names = [] + for m in re.finditer(r'^\s*-\s*name:\s*(.+)$', content, re.MULTILINE): + step_names.append(m.group(1).strip()) + return step_names + + def test_clone_step_exists(self): + """A step that clones repo-memory for scheduling must exist.""" + steps = self._load_steps() + clone_steps = [s for s in steps if "repo-memory" in s.lower() and "schedul" in s.lower()] + assert len(clone_steps) >= 1, ( + "Expected a repo-memory clone step for scheduling, found none. " + f"Steps: {steps}" + ) + + def test_clone_before_scheduling(self): + """The repo-memory clone step must come before 'Check which programs are due'.""" + steps = self._load_steps() + clone_idx = None + sched_idx = None + for i, name in enumerate(steps): + if "repo-memory" in name.lower() and "schedul" in name.lower(): + clone_idx = i + break + for i, name in enumerate(steps): + if name == "Check which programs are due": + sched_idx = i + break + assert clone_idx is not None, f"Clone step not found. Steps: {steps}" + assert sched_idx is not None, f"Scheduling step not found. Steps: {steps}" + assert clone_idx < sched_idx, ( + f"Clone step (index {clone_idx}) must come before scheduling step " + f"(index {sched_idx}). Steps: {steps}" + ) diff --git a/workflows/autoloop.md b/workflows/autoloop.md index 32ddde3..de29c07 100644 --- a/workflows/autoloop.md +++ b/workflows/autoloop.md @@ -81,6 +81,28 @@ imports: - shared/reporting.md steps: + - name: Clone repo-memory for scheduling + env: + GH_TOKEN: ${{ github.token }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} + run: | + # Clone the repo-memory branch so the scheduling step can read persisted state + # from previous runs. The framework-managed repo-memory clone happens after + # pre-steps, so we perform an early shallow clone here. + MEMORY_DIR="/tmp/gh-aw/repo-memory/autoloop" + BRANCH="memory/autoloop" + mkdir -p "$(dirname "$MEMORY_DIR")" + REPO_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git" + AUTH_URL="$(echo "$REPO_URL" | sed "s|https://|https://x-access-token:${GH_TOKEN}@|")" + if git ls-remote --exit-code --heads "$AUTH_URL" "$BRANCH" > /dev/null 2>&1; then + git clone --single-branch --branch "$BRANCH" --depth 1 "$AUTH_URL" "$MEMORY_DIR" 2>&1 + echo "Cloned repo-memory branch to $MEMORY_DIR" + else + mkdir -p "$MEMORY_DIR" + echo "No repo-memory branch found yet (first run). Created empty directory." + fi + - name: Check which programs are due env: GITHUB_TOKEN: ${{ github.token }} From d1dfa9a4ca8697dad81e2ed524ecfdc4159d9524 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 4 Apr 2026 04:46:19 +0000 Subject: [PATCH 3/3] Address code review: fix imports and use exact step name matching in tests Agent-Logs-Url: https://github.com/githubnext/autoloop/sessions/3013e45a-953d-4f06-bdab-ed720379b412 Co-authored-by: mrjf <180956+mrjf@users.noreply.github.com> --- tests/test_scheduling.py | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/tests/test_scheduling.py b/tests/test_scheduling.py index 3c8728b..7cfa7d9 100644 --- a/tests/test_scheduling.py +++ b/tests/test_scheduling.py @@ -694,17 +694,17 @@ class TestWorkflowStepOrdering: causing incorrect selection/skip behaviour. """ + CLONE_STEP = "Clone repo-memory for scheduling" + SCHED_STEP = "Check which programs are due" + def _load_steps(self): """Return the list of pre-step names from workflows/autoloop.md.""" - import os, re, yaml + import os + import re + wf_path = os.path.join(os.path.dirname(__file__), "..", "workflows", "autoloop.md") with open(wf_path) as f: content = f.read() - # The YAML frontmatter (before `---` + markdown body) contains the steps. - # Extract the frontmatter block (first --- ... --- block including 'steps:'). - # The source file has the structure: ---\n\n---\nsteps:\n - ...\n\nsource: ... - # We need everything from start to the line "source: ..." or "engine: ..." - # Actually, the whole pre-body section is YAML-ish; let's just find step names. step_names = [] for m in re.finditer(r'^\s*-\s*name:\s*(.+)$', content, re.MULTILINE): step_names.append(m.group(1).strip()) @@ -713,28 +713,16 @@ def _load_steps(self): def test_clone_step_exists(self): """A step that clones repo-memory for scheduling must exist.""" steps = self._load_steps() - clone_steps = [s for s in steps if "repo-memory" in s.lower() and "schedul" in s.lower()] - assert len(clone_steps) >= 1, ( - "Expected a repo-memory clone step for scheduling, found none. " - f"Steps: {steps}" + assert self.CLONE_STEP in steps, ( + f"Expected step '{self.CLONE_STEP}' not found. Steps: {steps}" ) def test_clone_before_scheduling(self): """The repo-memory clone step must come before 'Check which programs are due'.""" steps = self._load_steps() - clone_idx = None - sched_idx = None - for i, name in enumerate(steps): - if "repo-memory" in name.lower() and "schedul" in name.lower(): - clone_idx = i - break - for i, name in enumerate(steps): - if name == "Check which programs are due": - sched_idx = i - break - assert clone_idx is not None, f"Clone step not found. Steps: {steps}" - assert sched_idx is not None, f"Scheduling step not found. Steps: {steps}" + clone_idx = steps.index(self.CLONE_STEP) + sched_idx = steps.index(self.SCHED_STEP) assert clone_idx < sched_idx, ( - f"Clone step (index {clone_idx}) must come before scheduling step " - f"(index {sched_idx}). Steps: {steps}" + f"'{self.CLONE_STEP}' (index {clone_idx}) must come before " + f"'{self.SCHED_STEP}' (index {sched_idx}). Steps: {steps}" )