Fix generate step failure and issue creation (#221)
* docs(squad): log Ralph round 3 — PR fixes & merges #217 #216 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix generate step failure and issue creation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: add actions:read permission to notify-failure, use set -euo pipefail - Add actions:read so gh run view can read job conclusions - Replace $GITHUB_RUN_ID with ${{ github.run_id }} for consistency - Add set -euo pipefail to catch masked errors - Update tests to match Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Juan Manuel Servera committed
Jun 1, 2026 at 13:40 UTC
f12a08a98f3cff6dfd6e50e65a4506534c620dd2
7 files changed
+135
-1
.github/workflows/crawl-and-publish.yml
+56
-1
@@ -609,7 +609,19 @@ jobs:
609
git checkout -f -B "$DATA_BRANCH" "origin/$DEFAULT_BRANCH"
610
fi
611
mkdir -p content/weekly content/monthly content/yearly
612
- # Required: use exact page_path from generate-content step; hard-fail if missing
612
+ # generate_content.py returns an absolute path; normalize it before restoring
613
+ # the freshly generated page onto the publish branch checkout.
614
+ case "$PAGE_PATH" in
615
+ "$GITHUB_WORKSPACE"/*) PAGE_PATH="${PAGE_PATH#"$GITHUB_WORKSPACE"/}" ;;
616
+ esac
617
+ PAGE_PATH="${PAGE_PATH#/}"
618
+ case "$PAGE_PATH" in
619
+ content/weekly/*) ;;
620
+ *)
621
+ echo "::error::Expected PAGE_PATH under content/weekly/, got: $PAGE_PATH"
622
+ exit 1
623
+ ;;
624
+ esac
625
RELATIVE_FROM_WEEKLY="${PAGE_PATH#content/weekly/}"
626
mkdir -p "$(dirname "$PAGE_PATH")"
627
cp "content-weekly-backup/${RELATIVE_FROM_WEEKLY}" "$PAGE_PATH"
@@ -782,6 +794,49 @@ jobs:
794
-H "Content-Type: application/json" \
795
-d "$PAYLOAD" || echo "Webhook post failed (non-critical)"
796
797
+ notify-failure:
798
+ needs: [crawl, analyze, generate, deploy, notify]
799
+ if: ${{ always() && contains(needs.*.result, 'failure') }}
800
+ runs-on: ubuntu-latest
801
+ permissions:
802
+ actions: read
803
+ issues: write
804
+
805
+ steps:
806
+ - name: Create or update failure issue
807
+ env:
808
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
809
+ GH_REPO: ${{ github.repository }}
810
+ run: |
811
+ set -euo pipefail
812
+ RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
813
+ FAILED_JOBS=$(gh run view "${{ github.run_id }}" --json jobs --jq '[.jobs[] | select(.conclusion=="failure") | .name] | join(", ")')
814
+ if [ -z "$FAILED_JOBS" ]; then
815
+ FAILED_JOBS="unknown"
816
+ fi
817
+
818
+ EXISTING=$(gh issue list --state open --search 'in:title "Crawl and publish pipeline failed"' --json number --jq length)
819
+ if [ "$EXISTING" -gt 0 ]; then
820
+ ISSUE_NUM=$(gh issue list --state open --search 'in:title "Crawl and publish pipeline failed"' --json number --jq '.[0].number')
821
+ COMMENT_BODY=$(printf '%s\n\n%s' \
822
+ "Pipeline failed again: ${RUN_URL}" \
823
+ "Failed jobs: ${FAILED_JOBS}")
824
+ gh issue comment "$ISSUE_NUM" --body "$COMMENT_BODY"
825
+ echo "Updated existing issue #$ISSUE_NUM"
826
+ else
827
+ BODY=$(printf '%s\n\n%s\n%s\n\n%s\n%s\n%s' \
828
+ "The Crawl and publish weekly data workflow failed." \
829
+ "**Run:** ${RUN_URL}" \
830
+ "**Failed jobs:** ${FAILED_JOBS}" \
831
+ "Please triage:" \
832
+ "- If transient (network/rate limit), close with context" \
833
+ "- If real bug, assign to the right squad member")
834
+ gh issue create \
835
+ --title "🔴 Crawl and publish pipeline failed (run ${{ github.run_id }})" \
836
+ --label bug \
837
+ --label squad \
838
+ --body "$BODY"
839
+ fi
840
841
reskill-check:
842
needs: [crawl]
.squad/agents/amy/history.md
+9
@@ -29,3 +29,12 @@
29
- PR #219 opened with clean Hugo build
30
- Mobile UX improved for topics navigation
31
- Responsive design verified on small screens
32
+
33
+## Round 2026-06-01T12:41
34
+
35
+### PR #219 Merged
36
+- Review comments resolved: front matter `.Description` used, h2→h3 for card titles
37
+- Accessibility improvement: hidden topic chips on screens ≤768px
38
+- Commit e39720c
39
+- Merged (squash)
40
+- Issue #216 closed
.squad/agents/fry/history.md
+11
@@ -9,6 +9,7 @@
9
- End-to-end checks matter more than isolated unit confidence when artifacts move across crawl, analyze, and publish stages.
10
- Raw crawl output can be publishable with curation, but trend filters still need skepticism about exploit noise and weak momentum data.
11
- 2026-06-01: Issue #217 came from the analysis stage: Copilot retries could fail the tightened gate with generic week-title output or missing files, so the prompt now injects concrete week/year values, the workflow falls back to GitHub Models after Copilot gate failures, and `scripts/analyze_fallback.py` now works when invoked exactly as CI runs it.
12
+- 2026-06-01: Issue #220 came from the generate stage: `generate_content.py` emits an absolute `page_path`, but the publish-branch commit step treated it as `content/weekly/...`, so the restore `cp` doubled the path and failed. The crawl workflow also had no failure-issue job, so generate/deploy failures were silent unless someone checked Actions manually.
13
14
## Round 2026-06-01T12:19
15
@@ -16,3 +17,13 @@
17
- PR #218 opened with all tests passing
18
- Fallback mechanism now includes GitHub Models support
19
- Copilot prompt validation hardened
20
+
21
+## Round 2026-06-01T12:41
22
+
23
+### PR #218 Merged
24
+- Review comment resolved: removed outer quotes from YAML title hint
25
+- Added regression test
26
+- 542 tests passing
27
+- Commit 9d15b18
28
+- Merged (squash)
29
+- Issue #217 closed
.squad/agents/ralph/history.md
+10
@@ -6,3 +6,13 @@
6
## Learnings
7
- Project memory is most useful when repeated guidance is compressed into stable summaries instead of copied into every agent file.
8
- Reskill cycles are the right time to turn shared context into reusable skills.
9
+
10
+## Round 2026-06-01T12:41
11
+
12
+### Coordination & Merge Cycle
13
+- Resolved all review conversations via GraphQL API
14
+- Merged PR #218 (squash): Fry's test & prompt fix
15
+- Resolved merge conflict on PR #218 branch
16
+- Merged PR #219 (squash): Amy's accessibility & UX fix
17
+- Issues #216, #217 now closed
18
+- Decision inbox consolidated: Fry's quality-gate fallback decision merged into decisions.md
.squad/decisions.md
+15
@@ -6,6 +6,21 @@ Applies to future weekly summaries and any generator work that consumes `data/an
6
7
---
8
9
+# Fry: quality gate fallback hardening
10
+
11
+Date: 2026-06-01
12
+
13
+## Context
14
+Issue #217 showed the weekly analysis job can fail even when crawl data is healthy because Copilot sometimes returns a generic placeholder title or no output file at all after retries.
15
+
16
+## Decision
17
+Keep Copilot CLI as the primary analysis generator, but if its output still fails the quality gate after retries, immediately fall back to `scripts/analyze_fallback.py` via GitHub Models. Also render the prompt with concrete `week`, `year`, and title guidance so the model is less likely to echo placeholder frontmatter.
18
+
19
+## Rationale
20
+This keeps the higher-quality primary path, but removes CI flakiness from transient Copilot failures and from prompt placeholders leaking into the final markdown.
21
+
22
+---
23
+
24
# Directive: Prevent Recrawl on Previous-Week Rebuilds
25
26
**Date:** 2026-05-25T15:55:00+02:00
.squad/decisions/inbox/fry-generate-step.md
new
+12
@@ -0,0 +1,12 @@
1
+# Fry — generate-step failure handling
2
+
3
+Date: 2026-06-01
4
+
5
+## Context
6
+Issue #220 showed the crawl-and-publish workflow could finish crawl and analysis successfully, then fail in the generate handoff because the generated weekly page path was absolute while the publish-branch restore logic assumed a repository-relative path. The same workflow also lacked a failure-to-issue bridge, so repeated pipeline failures did not automatically open or update a GitHub issue.
7
+
8
+## Decision
9
+Normalize `page_path` to a repo-relative `content/weekly/...` path inside the generate commit step before copying weekly output onto the publish branch. Add a dedicated `notify-failure` job that always evaluates after the pipeline jobs and creates or updates a GitHub issue whenever any crawl/analyze/generate/deploy/notify job fails.
10
+
11
+## Rationale
12
+The path normalization fixes the actual handoff bug without changing `scripts/generate_content.py`, which already returns an absolute file path used elsewhere in tests. A separate failure notifier makes regressions visible even when later jobs are skipped, which is the exact reliability gap that hid the recent failures.
tests/test_pipeline.py
+22
@@ -260,6 +260,9 @@ class WorkflowConfigTests(unittest.TestCase):
260
self.assertIn("git add content/weekly/", commit_run)
261
self.assertIn("content/monthly/", commit_run)
262
self.assertIn("content/yearly/", commit_run)
263
+ self.assertIn("GITHUB_WORKSPACE", commit_run)
264
+ self.assertIn('case "$PAGE_PATH" in', commit_run)
265
+ self.assertIn("Expected PAGE_PATH under content/weekly/", commit_run)
266
267
upload_step = next((s for s in generate_job["steps"] if s.get("name") == "Upload generated content artifact"), None)
268
self.assertIsNotNone(upload_step)
@@ -290,6 +293,25 @@ class WorkflowConfigTests(unittest.TestCase):
293
self.assertIn("📊 **SquadScope Week", webhook_run)
294
self.assertIn("Webhook post failed (non-critical)", webhook_run)
295
296
+ def test_notify_failure_job_creates_or_updates_issue(self) -> None:
297
+ workflow_path = Path(".github/workflows/crawl-and-publish.yml")
298
+ workflow = yaml.safe_load(workflow_path.read_text(encoding="utf-8"))
299
+
300
+ notify_failure_job = workflow["jobs"]["notify-failure"]
301
+ self.assertEqual(notify_failure_job["needs"], ["crawl", "analyze", "generate", "deploy", "notify"])
302
+ self.assertEqual(notify_failure_job["if"], "${{ always() && contains(needs.*.result, 'failure') }}")
303
+ self.assertEqual(notify_failure_job["permissions"], {"actions": "read", "issues": "write"})
304
+
305
+ create_issue_step = next((s for s in notify_failure_job["steps"] if s.get("name") == "Create or update failure issue"), None)
306
+ self.assertIsNotNone(create_issue_step)
307
+ self.assertEqual(create_issue_step["env"]["GITHUB_TOKEN"], "${{ secrets.GITHUB_TOKEN }}")
308
+ create_issue_run = create_issue_step["run"]
309
+ self.assertIn('gh run view "${{ github.run_id }}" --json jobs', create_issue_run)
310
+ self.assertIn('gh issue list --state open --search', create_issue_run)
311
+ self.assertIn('gh issue comment "$ISSUE_NUM"', create_issue_run)
312
+ self.assertIn('gh issue create', create_issue_run)
313
+ self.assertIn('Crawl and publish pipeline failed', create_issue_run)
314
+
315
316
class PipelineIntegrationTests(unittest.TestCase):
317
def test_crawl_script_produces_valid_json_output_schema(self) -> None: