ci: harden develop write-path against races & make PR Guardian advisory

Root cause of the recurring README-sync failure: 05.1 modified README.md in
earlier steps then ran `git pull`, which aborts on a dirty tree whenever a
concurrent bot commit advanced develop first. Rewrote its commit step as a
fetch → reset --hard → regenerate → commit → push retry loop (README is a pure
function of inventory, so a hard reset never loses data and eliminates the
merge-conflict / --ours/--theirs class entirely).

Also:
- 03.1/03.2/03.3: wrap the single-shot pull --rebase && push in a 5x retry loop.
- 07.1 PR Guardian: continue-on-error (advisory; keeps the comment, drops the
  blocking red  on AI false positives) + PR-scoped concurrency.
- 07.2 Markdown Linter: PR/branch-scoped concurrency with cancel-in-progress.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Nubenetes Bot
2026-06-19 19:06:10 +02:00
parent f60d071e40
commit b52f793ea5
7 changed files with 66 additions and 34 deletions

View File

@@ -66,8 +66,12 @@ jobs:
echo "No changes in inventory to commit."
else
git commit -m "chore: update inventory stars and licenses [skip ci]"
git pull origin develop --rebase
git push origin develop
# Retry rebase+push to survive concurrent bot pushes to develop.
for i in {1..5}; do
git pull origin develop --rebase && git push origin develop && exit 0
echo "Push attempt $i/5 rejected — retrying..."; sleep $((i * 3))
done
echo "::error::Inventory metadata push failed after 5 attempts."; exit 1
fi
- name: Persist Incremental Inventory to Cache

View File

@@ -80,8 +80,12 @@ jobs:
echo "No changes in AI analysis to commit."
else
git commit -m "chore: update inventory AI analysis"
git pull origin develop --rebase
git push origin develop
# Retry rebase+push to survive concurrent bot pushes to develop.
for i in {1..5}; do
git pull origin develop --rebase && git push origin develop && exit 0
echo "Push attempt $i/5 rejected — retrying..."; sleep $((i * 3))
done
echo "::error::Inventory AI-analysis push failed after 5 attempts."; exit 1
fi
- name: Persist Incremental Inventory to Cache

View File

@@ -90,8 +90,12 @@ jobs:
echo "No automated changes to commit."
else
git commit -m "docs: update Video Hub with high-fidelity metadata"
git pull origin develop --rebase
git push origin develop
# Retry rebase+push to survive concurrent bot pushes to develop.
for i in {1..5}; do
git pull origin develop --rebase && git push origin develop && exit 0
echo "Push attempt $i/5 rejected — retrying..."; sleep $((i * 3))
done
echo "::error::Video Hub push failed after 5 attempts."; exit 1
fi
- name: Persist Incremental Inventory Cache

View File

@@ -59,38 +59,33 @@ jobs:
git config --global user.name "Nubenetes Bot"
git config --global user.email "bot@nubenetes.com"
export PYTHONPATH=$PYTHONPATH:.
# Pull latest before committing to shrink the conflict window
git pull --no-rebase origin develop
# Re-run updater so metrics reflect the latest remote state
python src/readme_updater.py
python src/safety_readme.py
git add README.md
if git diff --staged --quiet; then
echo "No changes in README.md to commit."
exit 0
fi
git commit -m "docs: automated README metric synchronization [skip ci]"
# Push-first loop: try push, then merge-pull on rejection (not rebase)
# Using merge avoids --ours/--theirs confusion that plagued the rebase approach
# README.md is a *pure function* of the inventory, so we never need to
# preserve a local README state: each attempt hard-resets to the freshest
# remote tip, regenerates, commits and pushes. This eliminates the two
# failure classes that previously broke this job:
# 1. Dirty-tree abort — the earlier steps modify README.md, then
# `git pull` aborted with "local changes would be overwritten by merge".
# 2. Merge/rebase --ours/--theirs confusion under concurrent bot pushes.
# A push can now only be rejected if the remote advanced between reset and
# push; we simply retry from the new tip. No conflict resolution required.
for i in {1..5}; do
echo "Push attempt $i/5..."
echo "README sync attempt $i/5..."
git fetch origin develop
git reset --hard origin/develop
python src/readme_updater.py
python src/safety_readme.py
git add README.md
if git diff --staged --quiet; then
echo "README already in sync with inventory. Nothing to commit."
exit 0
fi
git commit -m "docs: automated README metric synchronization [skip ci]"
if git push origin develop; then
echo "Push succeeded on attempt $i."
exit 0
fi
echo "Attempt $i rejected — integrating remote changes via merge..."
git fetch origin develop
if ! git merge --no-edit -X ours origin/develop; then
# Merge could not auto-resolve; keep bot's README and continue
git checkout --ours README.md
git add README.md
GIT_EDITOR=true git merge --continue
fi
# Re-compute metrics on the merged state (idempotent)
python src/readme_updater.py
python src/safety_readme.py
git add README.md
git diff --staged --quiet || git commit --amend --no-edit
echo "Attempt $i rejected — remote advanced, retrying from fresh tip..."
sleep $((i * 3))
done
git push origin develop
echo "::error::README sync failed after 5 attempts (persistent write contention on develop)."
exit 1

View File

@@ -10,6 +10,11 @@ on:
required: false
type: string
# Cancel superseded Guardian runs when a PR is updated (saves CI minutes + Gemini calls).
concurrency:
group: pr-guardian-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
agentic-presubmit:
runs-on: ubuntu-latest
@@ -35,6 +40,11 @@ jobs:
pip install --no-cache-dir pydantic PyGithub httpx fake-useragent pytz python-dotenv pyyaml tenacity
- name: Run PR Guardian Analysis
# Advisory gate: the Guardian is an AI heuristic that can misattribute
# pre-existing lines or mischaracterize a PR. It always posts its findings
# as a PR comment, but a non-zero exit must NOT show the PR as a blocking
# red ❌. continue-on-error keeps the check green while preserving the comment.
continue-on-error: true
env:
GEMINI_API_KEY_1: ${{ secrets.GEMINI_API_KEY_1 }}
GEMINI_API_KEY_2: ${{ secrets.GEMINI_API_KEY_2 }}

View File

@@ -10,6 +10,11 @@ on:
paths:
- '**/*.md'
# Supersede in-flight lint runs for the same PR/branch on new commits.
concurrency:
group: md-linter-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest