Skip to content

Update lucide monorepo to v1.21.0 (#2049) #475

Update lucide monorepo to v1.21.0 (#2049)

Update lucide monorepo to v1.21.0 (#2049) #475

name: Hotfix R-counter
# Track if a hotfix commit produces further hotfix commits within a
# short window. When R > 1 (a hotfix begets another hotfix on the same
# parent chain) the root cause was not actually fixed; surface that on
# the original feature PR for human attention.
#
# Borrowed-from: epidemiology. R = secondary cases / primary case.
# Applied to hotfix chains: a feature merge produces a hotfix; that
# hotfix should produce zero further hotfixes. If it does, the original
# feature PR is the right place to investigate, not the latest patch.
#
# State is persisted under .sdd/runtime/ which is gitignored; the
# workflow only uses the GitHub API for cross-run state.
on:
push:
branches: [main]
concurrency:
group: hotfix-r-tracker-${{ github.sha }}
cancel-in-progress: false
permissions: {}
jobs:
track:
name: Detect hotfix-begets-hotfix
runs-on: ubuntu-latest
timeout-minutes: 5
# Only run on commits that look like CI hotfixes. Conventional
# commit prefix `fix(ci):` matches the established pattern used by
# bernstein-ci-fix and auto-heal.
if: contains(github.event.head_commit.message, 'fix(ci):')
permissions:
contents: read
issues: write
pull-requests: write
steps:
- name: Harden runner (audit mode)
uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4
with:
egress-policy: audit
- name: Checkout repo for benign-drift classifier
# EDGE-4: the classifier script lives in the repo; checkout
# shallow (depth=1) so the workflow can read it without paying
# for full history.
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 # zizmor: ignore[artipacked]
with:
ref: ${{ github.sha }}
fetch-depth: 1
persist-credentials: false
- name: Check parent commit for prior hotfix
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
HEAD_SHA: ${{ github.sha }}
HEAD_MSG: ${{ github.event.head_commit.message }}
run: |
set -euo pipefail
# Walk back up to 6 parents looking for prior hotfix commits
# within a 24h window. 6 is enough to span a typical
# main-red --> bisect --> patch --> merge cycle without
# collapsing into noise on a calm day.
MAX_DEPTH=6
WINDOW_HOURS=24
NOW_EPOCH=$(date -u +%s)
CHAIN_LEN=1
CHAIN_DETAILS=""
ROOT_SHA=""
ROOT_MSG=""
# One batched API call returns the head commit followed by
# up to per_page-1 ancestors along the first-parent chain,
# replacing the previous N sequential .parents[0].sha walks.
# per_page is set to MAX_DEPTH+4 so we still have a few extra
# entries in case the window-age check trims the tail.
PER_PAGE=$((MAX_DEPTH + 4))
COMMITS_JSON=$(gh api \
"/repos/${REPO}/commits?sha=${HEAD_SHA}&per_page=${PER_PAGE}" \
2>/dev/null || echo '[]')
# Slice off the head commit itself (.[1:]) so we iterate only
# ancestors; everything we need for each ancestor (sha, first
# message line, author date) is already in the payload.
ANCESTORS=$(echo "${COMMITS_JSON}" \
| jq -c '.[1:][] | {sha: .sha, msg: (.commit.message | split("\n")[0]), date: .commit.author.date}' \
2>/dev/null || echo "")
if [ -z "${ANCESTORS}" ]; then
echo "No ancestor commits returned; skipping walk."
fi
DEPTH=0
while IFS= read -r ENTRY; do
[ -z "${ENTRY}" ] && continue
DEPTH=$((DEPTH + 1))
if [ "${DEPTH}" -gt "${MAX_DEPTH}" ]; then
break
fi
PARENT=$(echo "${ENTRY}" | jq -r '.sha // ""')
PARENT_MSG=$(echo "${ENTRY}" | jq -r '.msg // ""')
PARENT_DATE=$(echo "${ENTRY}" | jq -r '.date // ""')
if [ -z "${PARENT}" ] || [ -z "${PARENT_DATE}" ]; then
break
fi
PARENT_EPOCH=$(date -u -d "${PARENT_DATE}" +%s 2>/dev/null \
|| date -u -j -f "%Y-%m-%dT%H:%M:%SZ" "${PARENT_DATE}" +%s 2>/dev/null \
|| echo "0")
AGE_HOURS=$(( (NOW_EPOCH - PARENT_EPOCH) / 3600 ))
if [ "${AGE_HOURS}" -gt "${WINDOW_HOURS}" ]; then
echo "Walk stopped: parent ${PARENT:0:7} is ${AGE_HOURS}h old (window ${WINDOW_HOURS}h)."
break
fi
case "${PARENT_MSG}" in
"fix(ci):"*|*"fix(ci):"*)
CHAIN_LEN=$((CHAIN_LEN + 1))
CHAIN_DETAILS="${CHAIN_DETAILS}- \`${PARENT:0:7}\`: ${PARENT_MSG}"$'\n'
ROOT_SHA="${PARENT}"
continue
;;
*)
# First non-hotfix parent within window: that is the
# feature merge to blame. Capture it as the root cause
# signal.
ROOT_SHA="${PARENT}"
ROOT_MSG="${PARENT_MSG}"
break
;;
esac
done <<< "${ANCESTORS}"
echo "Hotfix chain length R = ${CHAIN_LEN}"
if [ "${CHAIN_LEN}" -le 1 ]; then
echo "R = 1 (single hotfix). No action needed."
exit 0
fi
# EDGE-4 hardening: classify the chain against the benign-drift
# allow-list before deciding to comment. The agents-md regen +
# ruff-format drift sequence is mechanical post-feature cleanup,
# not a regression; alerting on it trains operators to ignore
# the signal entirely.
#
# Collect every chain subject including HEAD into a single
# buffer and pipe to the classifier.
CHAIN_SUBJECTS_FILE=$(mktemp)
printf '%s\n' "${HEAD_MSG}" | head -1 > "${CHAIN_SUBJECTS_FILE}"
# Append each parent's subject (first line). CHAIN_DETAILS lines
# look like "- `sha7`: subject", strip the prefix back to subject.
printf '%s' "${CHAIN_DETAILS}" \
| sed -E 's/^- `[0-9a-f]+`: //' \
>> "${CHAIN_SUBJECTS_FILE}"
# ubuntu-latest ships python3 by default; we do not need an
# explicit setup-python step for this stdlib-only script. If
# the classifier exits 2 (missing allow-list, malformed regex)
# we fall open to "investigate" so the legacy alert path runs.
VERDICT=$(python3 scripts/r_counter_classify.py \
< "${CHAIN_SUBJECTS_FILE}" 2>/dev/null || echo "investigate")
if [ "${VERDICT}" = "benign" ]; then
{
echo "## R-counter (benign drift, no PR comment)"
echo ""
echo "Chain length R=${CHAIN_LEN}, but every commit matches"
echo ".github/r-counter-allowlist.txt patterns. This is the"
echo "standard agents-md+ruff drift mop-up sequence after a"
echo "feature merge, not a regression. Suppressing PR comment."
echo ""
echo "### Chain (newest first)"
echo ""
printf -- '- `%s`: %s\n' "${HEAD_SHA:0:7}" "${HEAD_MSG}"
printf '%s' "${CHAIN_DETAILS}"
} >> "${GITHUB_STEP_SUMMARY}"
exit 0
fi
echo "::warning::R-counter triggered: chain length ${CHAIN_LEN}"
# Identify the feature PR (if any) that introduced the root.
# The merge-commit message conventionally ends with `(#NNNN)`.
PR_NUMBER=""
if [ -n "${ROOT_SHA:-}" ]; then
ROOT_FULL_MSG=$(gh api "repos/${REPO}/commits/${ROOT_SHA}" \
--jq '.commit.message' 2>/dev/null || echo "")
PR_NUMBER=$(printf '%s' "${ROOT_FULL_MSG}" \
| grep -oE '#[0-9]+' \
| head -1 \
| tr -d '#' \
|| true)
fi
BODY_FILE=$(mktemp)
{
printf '## R-counter triggered (hotfix begets hotfix)\n\n'
printf 'The CI hotfix on `%s` is the **%sth** consecutive `fix(ci):` commit on main within the last 24h. R = %s > 1 indicates the previous hotfix did not actually contain the regression.\n\n' \
"${HEAD_SHA:0:7}" "${CHAIN_LEN}" "${CHAIN_LEN}"
printf '### Chain (newest first)\n\n'
printf -- '- `%s`: %s\n' "${HEAD_SHA:0:7}" "${HEAD_MSG}"
printf '%s' "${CHAIN_DETAILS}"
printf '\n### Suggested next step\n\n'
printf 'Investigate the change that introduced the regression rather than landing another patch on top. Borrowed-from: epidemiology -- if R > 1, isolate the source, do not treat downstream cases in isolation.\n\n'
printf '_Posted automatically by `hotfix-r-tracker.yml`._\n'
} > "${BODY_FILE}"
if [ -n "${PR_NUMBER}" ]; then
echo "Commenting on root feature PR #${PR_NUMBER}"
gh pr comment "${PR_NUMBER}" --repo "${REPO}" --body-file "${BODY_FILE}" \
|| echo "::warning::failed to comment on PR #${PR_NUMBER}"
else
# No PR to attach to: open an issue so the signal still lands.
TITLE="R-counter: hotfix chain R=${CHAIN_LEN} on main"
EXISTING=$(gh issue list --repo "${REPO}" --state open \
--search "in:title \"${TITLE}\"" \
--json number,title \
--jq ".[] | select(.title == \"${TITLE}\") | .number" \
| head -1)
if [ -n "${EXISTING}" ]; then
gh issue comment "${EXISTING}" --repo "${REPO}" --body-file "${BODY_FILE}"
else
gh issue create --repo "${REPO}" \
--title "${TITLE}" \
--body-file "${BODY_FILE}" \
--label "ci,reliability" \
|| gh issue create --repo "${REPO}" \
--title "${TITLE}" \
--body-file "${BODY_FILE}"
fi
fi