Skip to content

Commit 67f433e

Browse files
committed
fix: [AI] prevent bot commit if human edit
1 parent 07e44cb commit 67f433e

File tree

2 files changed

+96
-33
lines changed

2 files changed

+96
-33
lines changed

.github/actions/validate-requirements/check.sh

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
set -euo pipefail
33
IFS=$'\n\t'
44

5+
# Mode: "validate" (default, exits non-zero on human edit) or "detect" (outputs to GITHUB_OUTPUT)
6+
MODE="${MODE:-validate}"
57
ALLOWED_BOTS="${ALLOWED_BOTS:-github-actions[bot],dependabot[bot]}"
68

79
# Determine the comparison range
@@ -24,49 +26,69 @@ else
2426
COMPARE_RANGE="HEAD~1..HEAD"
2527
fi
2628

27-
# If requirements.txt changed in comparison range, ensure latest change's commit was authored by an allowed bot, or the latest commit message exactly matches the canonical bot commit message, or fallback to any bot-authored commit
28-
if git diff --name-only $COMPARE_RANGE | grep -q "^requirements.txt$"; then
29-
# Get commits touching requirements.txt in the range
30-
commits=$(git log --pretty=format:'%H' $COMPARE_RANGE -- requirements.txt || true)
31-
32-
if [ -z "$commits" ]; then
33-
echo "::error::No commits found touching requirements.txt in range $COMPARE_RANGE"
34-
exit 1
29+
# Check if requirements.txt changed
30+
if ! git diff --name-only $COMPARE_RANGE | grep -q "^requirements.txt$"; then
31+
echo "'requirements.txt' unchanged"
32+
if [ "$MODE" = "detect" ]; then
33+
echo "human_edit=false" >> "${GITHUB_OUTPUT:-/dev/stdout}"
3534
fi
35+
exit 0
36+
fi
3637

37-
# Build a grep-friendly regex from comma-separated allowed bots
38-
allowed_regex=$(echo "$ALLOWED_BOTS" | sed 's/,/\\|/g')
38+
# Get latest commit that touched requirements.txt
39+
latest_sha=$(git log -1 --pretty=format:'%H' $COMPARE_RANGE -- requirements.txt || true)
3940

40-
# Read canonical commit message first line if available
41-
canonical_msg=""
42-
if [ -n "${COMMIT_MSG_FILE:-}" ] && [ -f "$COMMIT_MSG_FILE" ]; then
43-
canonical_msg=$(sed -n '1p' "$COMMIT_MSG_FILE" | tr -d '\r')
41+
if [ -z "$latest_sha" ]; then
42+
echo "::error::No commits found touching requirements.txt in range $COMPARE_RANGE"
43+
if [ "$MODE" = "detect" ]; then
44+
echo "human_edit=false" >> "${GITHUB_OUTPUT:-/dev/stdout}"
4445
fi
46+
exit 1
47+
fi
48+
49+
latest_author=$(git show -s --format='%an' "$latest_sha")
50+
latest_committer=$(git show -s --format='%cn' "$latest_sha")
51+
latest_message=$(git show -s --format='%B' "$latest_sha")
52+
latest_subject=$(echo "$latest_message" | head -n1 | sed -e 's/[[:space:]]*$//')
4553

46-
# Short check: report any commit touching requirements.txt in the range that is not authored by an allowed bot and does not exactly match the canonical bot commit message (first line)
47-
offending=$(git log $COMPARE_RANGE --pretty=format:'%H|%an|%s' -- requirements.txt |
48-
while IFS='|' read -r sha author subject; do
49-
if echo "$author" | grep -qE "^($allowed_regex)$"; then
50-
continue
51-
fi
52-
if [ -n "$canonical_msg" ] && [ "$subject" = "$canonical_msg" ]; then
53-
continue
54-
fi
55-
printf '%s|%s|%s\n' "$sha" "$author" "$subject"
56-
break
57-
done)
54+
# Build a grep-friendly regex from comma-separated allowed bots
55+
allowed_regex=$(echo "$ALLOWED_BOTS" | sed 's/,/\\|/g')
5856

59-
if [ -z "$offending" ]; then
60-
echo "All commits touching requirements.txt in the range are from allowed bots or canonical bot messages: OK"
57+
# Check 1: author or committer is allowed bot
58+
if echo "$latest_author" | grep -qE "^($allowed_regex)$" || echo "$latest_committer" | grep -qE "^($allowed_regex)$"; then
59+
echo "Latest change by allowed bot: OK"
60+
if [ "$MODE" = "detect" ]; then
61+
echo "human_edit=false" >> "${GITHUB_OUTPUT:-/dev/stdout}"
62+
fi
63+
exit 0
64+
fi
65+
66+
# Check 2: commit message exactly matches canonical message
67+
if [ -n "${COMMIT_MSG_FILE:-}" ] && [ -f "$COMMIT_MSG_FILE" ]; then
68+
canonical_msg=$(sed -n '1p' "$COMMIT_MSG_FILE" | tr -d '\r')
69+
if [ "$latest_subject" = "$canonical_msg" ]; then
70+
echo "Latest commit message exactly matches canonical bot message: OK"
71+
if [ "$MODE" = "detect" ]; then
72+
echo "human_edit=false" >> "${GITHUB_OUTPUT:-/dev/stdout}"
73+
fi
6174
exit 0
6275
fi
76+
fi
6377

78+
# Human edit detected
79+
if [ "$MODE" = "detect" ]; then
80+
echo "human_edit=true" >> "${GITHUB_OUTPUT:-/dev/stdout}"
81+
echo "offender_author=$latest_author" >> "${GITHUB_OUTPUT:-/dev/stdout}"
82+
echo "offender_subject=$latest_subject" >> "${GITHUB_OUTPUT:-/dev/stdout}"
83+
echo "Human edit detected"
84+
exit 0
85+
else
6486
echo "::error::You may NOT edit 'requirements.txt'"
6587
echo "::warning::Undo your changes to requirements.txt, so robot can maintain it."
6688
echo "::notice::To pin dependencies, use 'poetry add <package-name>'."
67-
echo "Offending commit(s):"
68-
echo "$offending" | sed 's/^/ /'
89+
echo "Latest commit: $latest_sha"
90+
echo "Latest author: $latest_author"
91+
echo "Latest committer: $latest_committer"
92+
echo "Latest message: $latest_subject"
6993
exit 1
7094
fi
71-
72-
echo "'requirements.txt' unchanged (or latest change by allowed bot/marker)"

.github/workflows/requirments-sync.yml

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,51 @@ jobs:
4949
echo "has_change=true" >> $GITHUB_OUTPUT
5050
fi
5151
52-
commit-requirements-delta:
52+
prevent-bot-commit-if-human-edit:
5353
runs-on: ubuntu-latest
5454
needs: detect-requirements-delta
5555
if: needs.detect-requirements-delta.outputs.has_change == 'true'
56+
outputs:
57+
is_human_edit: ${{ steps.check.outputs.is_human_edit }}
58+
offender_author: ${{ steps.check.outputs.offender_author }}
59+
offender_subject: ${{ steps.check.outputs.offender_subject }}
60+
env:
61+
ALLOWED_BOTS: 'github-actions[bot],dependabot[bot]'
62+
COMMIT_MSG_FILE: .github/commit-messages/requirements_update.txt
63+
64+
steps:
65+
- name: Checkout code
66+
uses: actions/checkout@v4
67+
with:
68+
ref: ${{ github.head_ref || github.ref_name }}
69+
fetch-depth: 0
70+
71+
- name: Check for human edits
72+
id: check
73+
env:
74+
MODE: detect
75+
run: bash ./.github/actions/validate-requirements/check.sh
76+
77+
- name: Comment on PR about human edit
78+
if: steps.check.outputs.is_human_edit == 'true'
79+
uses: actions/github-script@v6
80+
with:
81+
github-token: ${{ secrets.GITHUB_TOKEN }}
82+
script: |
83+
const author = `${{ steps.check.outputs.offender_author }}`
84+
const subject = `${{ steps.check.outputs.offender_subject }}`
85+
const body = `Undo edits to \`requirements.txt\` in this PR. That file is maintained by a bot; human edits should be reverted so the bot can manage it. Offending commit by ${author}: "${subject}". If you intended to pin a dependency, use \`poetry add <package>\`.`
86+
await github.rest.issues.createComment({
87+
owner: context.repo.owner,
88+
repo: context.repo.repo,
89+
issue_number: context.issue.number,
90+
body
91+
})
92+
93+
commit-requirements-delta:
94+
runs-on: ubuntu-latest
95+
needs: [detect-requirements-delta, prevent-bot-commit-if-human-edit]
96+
if: needs.detect-requirements-delta.outputs.has_change == 'true' && needs.prevent-bot-commit-if-human-edit.outputs.is_human_edit == 'false'
5697
env:
5798
COMMIT_MSG_FILE: .github/commit-messages/requirements_update.txt
5899

0 commit comments

Comments
 (0)