Skip to content

Fix GitHub Actions validation errors from emojis in commit status contexts #6002

Fix GitHub Actions validation errors from emojis in commit status contexts

Fix GitHub Actions validation errors from emojis in commit status contexts #6002

Workflow file for this run

name: Safety Guard
on:
pull_request:
branches: [ '**' ]
types: [opened, synchronize, reopened, ready_for_review]
paths-ignore:
- ".cursorrules"
push:
branches: [ '**' ]
paths-ignore:
- ".cursorrules"
permissions:
contents: read
pull-requests: write
concurrency:
group: safety-guard-${{ github.ref }}
cancel-in-progress: true
jobs:
safety-guard:
name: "🧯 Dangerous deletes guard"
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: 📥 Checkout Code
uses: actions/checkout@v4
- name: ✅ Ensure .cursorrules exists
run: |
if [ ! -f .cursorrules ]; then
echo "::error::Missing .cursorrules file at repo root" && exit 1
fi
- name: 🔎 Scan and build report
id: scan
shell: bash
run: |
set -euo pipefail
pattern='(shutil\\.rmtree|os\\.remove\(|Path\\.unlink\(|rm -rf|rimraf)'
grep -R -nE "$pattern" . --exclude-dir=.git > matches_all.txt || true
excluded_re='^(\./)?docs/|README|Dockerfile|get-pip\\.py|Makefile|build_docs\\.sh'
grep -E "$excluded_re" matches_all.txt > matches_excluded.txt || true
: > flagged.txt
py_files=$(git ls-files '*.py' || true)
if [ -n "${py_files:-}" ]; then
while IFS= read -r f; do
[[ "$f" == docs/* || "$f" == 'get-pip.py' ]] && continue
grep -nE '(shutil\\.rmtree|os\\.remove\(|Path\\.unlink\()' "$f" || true
done < <(printf '%s\n' $py_files) >> flagged.txt
fi
sh_files=$(git ls-files '*.sh' || true)
if [ -n "${sh_files:-}" ]; then
while IFS= read -r f; do
[[ "$f" == docs/* || "$f" == *Makefile* || "$f" == 'build_docs.sh' ]] && continue
grep -nE '(rm -rf|rimraf)' "$f" || true
done < <(printf '%s\n' $sh_files) >> flagged.txt
fi
# Truncate very long lines to keep PR comment readable (e.g., *.map JSON lines)
MAX_COLS=240
truncate_lines() { # $1=in $2=out
local in_file="$1" out_file="$2"
if [ -s "$in_file" ]; then
awk -v maxc="$MAX_COLS" '{
if (length($0) > maxc) {
print substr($0, 1, maxc) " … [truncated]";
} else { print $0 }
}' "$in_file" > "$out_file"
else
: > "$out_file"
fi
}
truncate_lines matches_excluded.txt matches_excluded_short.txt
truncate_lines flagged.txt flagged_short.txt
num_flagged=$(grep -c ':' flagged.txt 2>/dev/null || echo 0)
num_excluded=$(grep -c ':' matches_excluded.txt 2>/dev/null || echo 0)
num_all=$(grep -c ':' matches_all.txt 2>/dev/null || echo 0)
{
echo "## 🧯 Dangerous deletes guard report"
echo
echo "Policy: see .cursorrules — dangerous deletions are blocked unless wrapped safely."
echo
echo "Summary:"
echo "- Flagged findings (blocking): ${num_flagged}"
echo "- Excluded matches (not blocking): ${num_excluded}"
echo "- Total matches (all files): ${num_all}"
echo
echo "Flagged findings (file:line:snippet):"
if [ -s flagged_short.txt ]; then
echo '```'
cat flagged_short.txt
echo '```'
else
echo "(none)"
fi
echo
echo "<details><summary>Excluded matches (by path pattern)</summary>"
echo
echo '```'
cat matches_excluded_short.txt 2>/dev/null || true
echo '```'
echo "</details>"
} > scan_report.md
if [ "$num_flagged" -gt 0 ]; then
echo "has_violations=true" >> "$GITHUB_OUTPUT"
else
echo "has_violations=false" >> "$GITHUB_OUTPUT"
fi
- name: 💬 Post PR report comment
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const marker = '<!-- safety-guard-report -->';
let body = '';
try { body = fs.readFileSync('scan_report.md', 'utf8'); } catch (e) { body = 'Report missing.' }
// Truncate to stay under GitHub's 65,536 chars issue comment limit
const MAX = 65000;
const suffix = '\n\n[...truncated, see workflow logs for full report]';
let safeBody = body;
if (typeof body === 'string' && body.length > MAX) {
safeBody = body.slice(0, MAX - suffix.length) + suffix;
}
const full = `${marker}\n${safeBody}`;
const pr = context.payload.pull_request?.number;
if (!pr) { core.info('Not a PR event; skip comment'); return; }
const { owner, repo } = context.repo;
const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number: pr, per_page: 100 });
const existing = comments.find(c => (c.user?.type === 'Bot' || (c.user?.login || '').includes('[bot]')) && (c.body || '').includes(marker));
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body: full });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number: pr, body: full });
}
- name: ❌ Fail if violations found
if: steps.scan.outputs.has_violations == 'true'
run: |
echo "Blocking merge: flagged dangerous deletion calls found. See report above." >&2
exit 1