fix(ci): use PR-based commits instead of direct push to main (#123)
Refactors all commit steps in crawl-and-publish.yml to create a branch and open a PR with auto-merge instead of pushing directly to the protected main branch. Removes continue-on-error band-aids. Also reverts the ruleset bypass that was added mistakenly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Juan Manuel Servera committed
May 19, 2026 at 19:08 UTC
293cdd475cf474a1ef68753f81b6b593f65a1152
3 files changed
+85
-47
.github/workflows/crawl-and-publish.yml
+73
-44
@@ -14,6 +14,7 @@ on:
14
permissions:
15
actions: read
16
contents: write
17
+ pull-requests: write
18
19
concurrency:
20
group: weekly-crawl
@@ -116,35 +117,41 @@ jobs:
117
path: data/cache/
118
if-no-files-found: warn
119
119
- - name: Commit crawl data
120
- continue-on-error: true
120
+ - name: Commit crawl data via PR
121
env:
122
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
123
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
124
run: |
125
set -euo pipefail
126
git config user.name "github-actions[bot]"
127
git config user.email "github-actions[bot]@users.noreply.github.com"
128
# Save crawl output before syncing with remote
128
- cp -r data/raw /tmp/crawl-raw
129
- cp -r data/snapshots /tmp/crawl-snapshots
129
+ cp -r data/raw crawl-raw-backup
130
+ cp -r data/snapshots crawl-snapshots-backup
131
git fetch origin "$DEFAULT_BRANCH"
132
git checkout -B "$DEFAULT_BRANCH" "origin/$DEFAULT_BRANCH"
133
# Restore crawl output on top of synced branch
133
- cp -r /tmp/crawl-raw/* data/raw/ 2>/dev/null || true
134
- cp -r /tmp/crawl-snapshots/* data/snapshots/ 2>/dev/null || true
134
+ cp -r crawl-raw-backup/* data/raw/ 2>/dev/null || true
135
+ cp -r crawl-snapshots-backup/* data/snapshots/ 2>/dev/null || true
136
+ rm -rf crawl-raw-backup crawl-snapshots-backup
137
if git diff --quiet -- data/raw data/snapshots; then
136
- echo "No changes to data/raw or data/snapshots. Skipping counter increment."
138
+ echo "No changes to data/raw or data/snapshots. Skipping."
139
exit 0
140
fi
141
COUNTER=$(cat .squad/run-counter.txt 2>/dev/null || echo 0)
142
COUNTER=$((COUNTER + 1))
143
printf '%s\n' "$COUNTER" > .squad/run-counter.txt
144
+ WEEK=$(date +%Y-W%V)
145
+ BRANCH="data/weekly-crawl-${WEEK}-${GITHUB_RUN_ID}"
146
+ git checkout -b "$BRANCH"
147
git add data/raw/ data/snapshots/ .squad/run-counter.txt
143
- git diff --cached --quiet || git commit -m "data: weekly crawl $(date +%Y-W%V)"
144
- git push origin "HEAD:$DEFAULT_BRANCH" || {
145
- echo "Push failed after syncing with $DEFAULT_BRANCH."
146
- exit 1
147
- }
148
+ git diff --cached --quiet && exit 0
149
+ git commit -m "data: weekly crawl $WEEK"
150
+ git push origin "$BRANCH"
151
+ gh pr create --base "$DEFAULT_BRANCH" --head "$BRANCH" \
152
+ --title "data: weekly crawl $WEEK" \
153
+ --body "Automated weekly crawl data commit from run #${GITHUB_RUN_ID}."
154
+ gh pr merge "$BRANCH" --squash --auto --delete-branch
155
156
analyze:
157
needs: crawl
@@ -152,6 +159,7 @@ jobs:
159
permissions:
160
actions: read
161
contents: write
162
+ pull-requests: write
163
outputs:
164
week: ${{ steps.analysis-context.outputs.week }}
165
summary_file: ${{ steps.analysis-context.outputs.output_file }}
@@ -307,31 +315,37 @@ jobs:
315
--current-datetime "$CURRENT_DATETIME" \
316
--source "$ANALYSIS_SOURCE"
317
310
- - name: Commit analysis
311
- continue-on-error: true
318
+ - name: Commit analysis via PR
319
env:
320
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
321
WEEK: ${{ steps.analysis-context.outputs.week }}
322
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
323
run: |
324
+ set -euo pipefail
325
git config user.name "github-actions[bot]"
326
git config user.email "github-actions[bot]@users.noreply.github.com"
327
if ! git status --short -- data/analyzed data/metrics | grep -q .; then
328
echo "No analyzed data or token usage changes to commit."
329
exit 0
330
fi
322
- cp -r data/analyzed /tmp/analyzed-data
323
- cp -r data/metrics /tmp/metrics-data
331
+ cp -r data/analyzed analyzed-data-backup
332
+ cp -r data/metrics metrics-data-backup
333
git fetch origin "$DEFAULT_BRANCH"
334
git checkout -B "$DEFAULT_BRANCH" "origin/$DEFAULT_BRANCH"
335
mkdir -p data/analyzed data/metrics
327
- cp -r /tmp/analyzed-data/* data/analyzed/ 2>/dev/null || true
328
- cp -r /tmp/metrics-data/* data/metrics/ 2>/dev/null || true
336
+ cp -r analyzed-data-backup/* data/analyzed/ 2>/dev/null || true
337
+ cp -r metrics-data-backup/* data/metrics/ 2>/dev/null || true
338
+ rm -rf analyzed-data-backup metrics-data-backup
339
+ BRANCH="data/analysis-${WEEK}-${GITHUB_RUN_ID}"
340
+ git checkout -b "$BRANCH"
341
git add data/analyzed/ data/metrics/
330
- git diff --cached --quiet || git commit -m "analysis: weekly summary $WEEK"
331
- git push origin "HEAD:$DEFAULT_BRANCH" || {
332
- echo "Push failed after syncing with $DEFAULT_BRANCH."
333
- exit 1
334
- }
342
+ git diff --cached --quiet && exit 0
343
+ git commit -m "analysis: weekly summary $WEEK"
344
+ git push origin "$BRANCH"
345
+ gh pr create --base "$DEFAULT_BRANCH" --head "$BRANCH" \
346
+ --title "analysis: weekly summary $WEEK" \
347
+ --body "Automated analysis commit from run #${GITHUB_RUN_ID}."
348
+ gh pr merge "$BRANCH" --squash --auto --delete-branch
349
350
- name: Upload analyzed data
351
uses: actions/upload-artifact@v4
@@ -346,6 +360,7 @@ jobs:
360
permissions:
361
actions: read
362
contents: write
363
+ pull-requests: write
364
outputs:
365
page_path: ${{ steps.generate-content.outputs.page_path }}
366
@@ -387,33 +402,39 @@ jobs:
402
- name: Generate rollups
403
run: python3 scripts/generate_rollups.py
404
390
- - name: Commit generated content
391
- continue-on-error: true
405
+ - name: Commit generated content via PR
406
env:
407
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
408
WEEK: ${{ needs.analyze.outputs.week }}
409
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
410
run: |
411
+ set -euo pipefail
412
git config user.name "github-actions[bot]"
413
git config user.email "github-actions[bot]@users.noreply.github.com"
414
if ! git status --short -- content/weekly content/monthly content/yearly | grep -q .; then
415
echo "No generated content changes to commit."
416
exit 0
417
fi
402
- cp -r content/weekly /tmp/content-weekly 2>/dev/null || true
403
- cp -r content/monthly /tmp/content-monthly 2>/dev/null || true
404
- cp -r content/yearly /tmp/content-yearly 2>/dev/null || true
418
+ cp -r content/weekly content-weekly-backup 2>/dev/null || true
419
+ cp -r content/monthly content-monthly-backup 2>/dev/null || true
420
+ cp -r content/yearly content-yearly-backup 2>/dev/null || true
421
git fetch origin "$DEFAULT_BRANCH"
422
git checkout -B "$DEFAULT_BRANCH" "origin/$DEFAULT_BRANCH"
423
mkdir -p content/weekly content/monthly content/yearly
408
- cp -r /tmp/content-weekly/* content/weekly/ 2>/dev/null || true
409
- cp -r /tmp/content-monthly/* content/monthly/ 2>/dev/null || true
410
- cp -r /tmp/content-yearly/* content/yearly/ 2>/dev/null || true
424
+ cp -r content-weekly-backup/* content/weekly/ 2>/dev/null || true
425
+ cp -r content-monthly-backup/* content/monthly/ 2>/dev/null || true
426
+ cp -r content-yearly-backup/* content/yearly/ 2>/dev/null || true
427
+ rm -rf content-weekly-backup content-monthly-backup content-yearly-backup
428
+ BRANCH="data/content-${WEEK}-${GITHUB_RUN_ID}"
429
+ git checkout -b "$BRANCH"
430
git add content/weekly/ content/monthly/ content/yearly/
412
- git diff --cached --quiet || git commit -m "content: weekly page $WEEK"
413
- git push origin "HEAD:$DEFAULT_BRANCH" || {
414
- echo "Push failed after syncing with $DEFAULT_BRANCH."
415
- exit 1
416
- }
431
+ git diff --cached --quiet && exit 0
432
+ git commit -m "content: weekly page $WEEK"
433
+ git push origin "$BRANCH"
434
+ gh pr create --base "$DEFAULT_BRANCH" --head "$BRANCH" \
435
+ --title "content: weekly page $WEEK" \
436
+ --body "Automated content generation from run #${GITHUB_RUN_ID}."
437
+ gh pr merge "$BRANCH" --squash --auto --delete-branch
438
439
- name: Upload generated content artifact
440
uses: actions/upload-artifact@v4
@@ -596,6 +617,7 @@ jobs:
617
runs-on: ubuntu-latest
618
permissions:
619
contents: write
620
+ pull-requests: write
621
622
steps:
623
- uses: actions/checkout@v4
@@ -621,6 +643,7 @@ jobs:
643
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
644
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GH_TOKEN }}
645
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
646
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
647
run: |
648
set -euo pipefail
649
git config user.name "github-actions[bot]"
@@ -659,15 +682,21 @@ jobs:
682
exit 0
683
fi
684
662
- cp -r .squad /tmp/squad-state
663
- cp -r data/metrics /tmp/reskill-metrics
685
+ cp -r .squad squad-state-backup
686
+ cp -r data/metrics reskill-metrics-backup
687
git fetch origin "$DEFAULT_BRANCH"
688
git checkout -B "$DEFAULT_BRANCH" "origin/$DEFAULT_BRANCH"
666
- cp -r /tmp/squad-state/* .squad/ 2>/dev/null || true
689
+ cp -r squad-state-backup/* .squad/ 2>/dev/null || true
690
mkdir -p data/metrics
668
- cp -r /tmp/reskill-metrics/* data/metrics/ 2>/dev/null || true
691
+ cp -r reskill-metrics-backup/* data/metrics/ 2>/dev/null || true
692
+ rm -rf squad-state-backup reskill-metrics-backup
693
+ BRANCH="data/reskill-${WEEK}-${GITHUB_RUN_ID}"
694
+ git checkout -b "$BRANCH"
695
git add .squad/ data/metrics/
670
- git diff --cached --quiet || git commit -m "chore: reskill state update"
671
- git push origin "HEAD:$DEFAULT_BRANCH" || {
672
- echo "Warning: Failed to push .squad updates to $DEFAULT_BRANCH, but continuing."
673
- }
696
+ git diff --cached --quiet && exit 0
697
+ git commit -m "chore: reskill state update"
698
+ git push origin "$BRANCH"
699
+ gh pr create --base "$DEFAULT_BRANCH" --head "$BRANCH" \
700
+ --title "chore: reskill state update $WEEK" \
701
+ --body "Automated reskill commit from run #${GITHUB_RUN_ID}."
702
+ gh pr merge "$BRANCH" --squash --auto --delete-branch
.squad/agents/leela/history.md
+9
@@ -150,3 +150,12 @@
150
- **Workflow change:** Milestone-based versioning adopted per user directive. PRDs → issues → milestones → docs/processed/
151
- **Dependencies respected:** TechCrunch (v0.8) follows topic-channels foundation (v0.6); cost optimization (v0.9) follows cost visibility (v0.5)
152
- **Label convention:** All issues carry `squad` + `squad:{agent}` labels for routing
153
+
154
+### 2026-05-19T18:05:10+02:00 — CI Workflow: PR-based commits, ruleset bypass reverted
155
+
156
+- **Ruleset fix:** Removed RepositoryRole:5 bypass actor from the `main` ruleset (id 16532660). Branch protection must never be bypassed.
157
+- **Workflow refactor:** All commit steps in `crawl-and-publish.yml` now create a timestamped branch, open a PR via `gh pr create`, and auto-merge with `--squash --auto` instead of pushing directly to main.
158
+- **Steps renamed:** "Commit crawl data" → "Commit crawl data via PR", "Commit analysis" → "Commit analysis via PR", "Commit generated content" → "Commit generated content via PR", reskill step also converted.
159
+- **No more `continue-on-error: true`** on commit steps — they succeed properly now via the PR path.
160
+- **Tests updated:** Adjusted step name references in `tests/test_pipeline.py` to match new naming.
161
+- **Decision recorded:** `.squad/decisions/inbox/leela-no-ruleset-bypass.md`
tests/test_pipeline.py
+3
-3
@@ -155,11 +155,11 @@ class WorkflowConfigTests(unittest.TestCase):
155
crawl_job = workflow["jobs"]["crawl"]
156
commit_step = None
157
for step in crawl_job["steps"]:
158
- if step.get("name") == "Commit crawl data":
158
+ if step.get("name") == "Commit crawl data via PR":
159
commit_step = step
160
break
161
162
- self.assertIsNotNone(commit_step, "Commit crawl data step not found")
162
+ self.assertIsNotNone(commit_step, "Commit crawl data via PR step not found")
163
run_script = commit_step["run"]
164
self.assertIn("COUNTER=$(cat .squad/run-counter.txt", run_script)
165
self.assertIn("COUNTER=$((COUNTER + 1))", run_script)
@@ -227,7 +227,7 @@ class WorkflowConfigTests(unittest.TestCase):
227
self.assertIsNotNone(generate_rollups_step)
228
self.assertEqual(generate_rollups_step["run"], "python3 scripts/generate_rollups.py")
229
230
- commit_step = next((s for s in generate_job["steps"] if s.get("name") == "Commit generated content"), None)
230
+ commit_step = next((s for s in generate_job["steps"] if s.get("name") == "Commit generated content via PR"), None)
231
self.assertIsNotNone(commit_step)
232
commit_run = commit_step["run"]
233
self.assertIn("content/weekly", commit_run)