Skip to content

[UT] Add SQLTester cases for FILES() path pattern styles #4036

[UT] Add SQLTester cases for FILES() path pattern styles

[UT] Add SQLTester cases for FILES() path pattern styles #4036

Workflow file for this run

name: AI SR SKILLS
on:
pull_request_target:
branches:
- main
types:
- opened
- reopened
- synchronize # re-brief on every push so the comment tracks the live diff
- ready_for_review # brief when a draft is marked ready
permissions:
pull-requests: write
jobs:
assign-reviewer:
runs-on: [self-hosted, normal]
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
if: >
github.event.action == 'opened' &&
!startsWith(github.head_ref, 'mergify/') &&
!contains(github.head_ref, '-sync-pr-')
steps:
- name: Recommend Reviewer (AI)
run: |
echo "=== DEBUG ==="
echo "PR: ${{ github.event.number }}"
echo "ACTION: ${{ github.event.action }}"
echo "BASE_REF: ${{ github.base_ref }}"
echo "HEAD_REF: ${{ github.head_ref }}"
echo "CLAUDE_CODE_OAUTH_TOKEN length: ${#CLAUDE_CODE_OAUTH_TOKEN}"
echo "GITHUB_TOKEN length: ${#GITHUB_TOKEN}"
echo "=== RUN ==="
# Runs inside claude-cli docker; produces only "Recommended reviewer: <login>"
# output. The host-side "Apply Reviewer" step below actually attaches it
# (gh is not available inside the container).
# tee captures the skill's stdout for the downstream steps to parse.
docker exec -i -u claudeuser \
-e GITHUB_TOKEN="${GITHUB_TOKEN}" \
-e CLAUDE_CODE_OAUTH_TOKEN="${CLAUDE_CODE_OAUTH_TOKEN}" \
claude-cli claude --dangerously-skip-permissions \
-p "/starrocks-assign-pr-reviewer https://github.com/${{ github.repository }}/pull/${{ github.event.number }}" \
2>&1 | tee "${RUNNER_TEMP}/ai_output.txt" || true
- name: Apply Reviewer
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.number }}
REPO: ${{ github.repository }}
run: |
# The skill runs inside claude-cli docker where `gh` is unavailable,
# so it can only print recommendation lines and asks for manual
# assignment. Host runner has `gh` + GITHUB_TOKEN, so attach the
# reviewers here instead. Idempotent: gh ignores duplicates.
#
# The skill (starrocks-skills/starrocks-assign-pr-reviewer) emits up
# to three lines (see SKILL.md "Output Template"):
# Recommended reviewer: <owner login | none>
# Affinity reviewer: <login | none>
# Global reviewer: <team slug | none>
# Assign every non-"none" output. The first two are individual logins;
# "Global reviewer" is a team and `gh` requires the org/team form.
ai_output="${RUNNER_TEMP}/ai_output.txt"
if [ ! -f "$ai_output" ]; then
echo "no ai_output, skip"
exit 0
fi
# $1 is the literal field label (without trailing colon). LLM may
# wrap the line or value in **bold**; strip leading * and trailing
# markdown, then keep only the relevant slug chars. Two variants:
# extract_user -- GitHub login: [A-Za-z0-9-]
# extract_team -- team ref: [A-Za-z0-9-] plus '/' so a qualified
# `org/team-slug` is preserved intact. Using the
# user regex on a qualified team would truncate
# at the first '/', losing the team segment.
extract_user() {
local key="$1"
grep -iE "^\**${key}:" "$ai_output" | tail -1 \
| sed -E "s|^\**[^:]*:\**[[:space:]]*||" \
| grep -oE '[A-Za-z0-9][A-Za-z0-9-]*' | head -1
}
extract_team() {
local key="$1"
grep -iE "^\**${key}:" "$ai_output" | tail -1 \
| sed -E "s|^\**[^:]*:\**[[:space:]]*||" \
| grep -oE '[A-Za-z0-9][A-Za-z0-9/-]*' | head -1
}
owner=$(extract_user "Recommended reviewer")
affinity=$(extract_user "Affinity reviewer")
global=$(extract_team "Global reviewer")
author=$(gh pr view "$PR" -R "$REPO" --json author -q '.author.login' 2>/dev/null || true)
assign_user() {
local r="$1"
if [ -z "$r" ] || [ "$r" = "none" ]; then
return
fi
# Self-review guard: GitHub rejects request-review from the author.
if [ -n "$author" ] && [ "$r" = "$author" ]; then
echo "skip: $r is the PR author"
return
fi
echo "Assigning user: $r"
gh pr edit "$PR" -R "$REPO" --add-reviewer "$r" \
|| echo "WARN: add-reviewer $r failed (permissions, already a reviewer, or transient API error)"
}
# Route team request through a label instead of calling
# gh pr edit --add-reviewer directly. label-action.yml has a
# `global-review:` job that watches the GLOBAL-REVIEW label and
# POSTs the requested_reviewers API. Keeping this on the label
# path means manual label toggling and AI assignment funnel
# through one code path; approve-checker.yml's global-review
# gate also keys off the same label.
assign_global_team() {
local t="$1"
if [ -z "$t" ] || [ "$t" = "none" ]; then
return
fi
# Strip optional org prefix the skill may emit
case "$t" in
*/*) t="${t#*/}" ;;
esac
if [ "$t" != "global-reviewer" ]; then
echo "WARN: unsupported team slug from AI: $t (only global-reviewer is supported via GLOBAL-REVIEW label routing)"
return
fi
echo "Adding GLOBAL-REVIEW label (label-action.yml will request the team)"
gh pr edit "$PR" -R "$REPO" --add-label GLOBAL-REVIEW \
|| echo "WARN: add-label GLOBAL-REVIEW failed (permissions or transient API error)"
}
assign_user "$owner"
# De-dup: if affinity == owner, the second call would be a no-op API
# round-trip; skip it locally for clarity.
if [ -n "$affinity" ] && [ "$affinity" = "$owner" ]; then
affinity=""
fi
assign_user "$affinity"
assign_global_team "$global"
- name: Notify Slack
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
PR: ${{ github.event.number }}
REPO: ${{ github.repository }}
TITLE: ${{ github.event.pull_request.title }}
PR_URL: ${{ github.event.pull_request.html_url }}
CHANNEL_ID: C0B4GU36HJN
run: |
set -e
rm -rf ./ci-tool && cp -rf /var/lib/ci-tool ./ci-tool
cd ci-tool && git pull
# Parse owner + affinity reviewers from the skill output. The skill
# (starrocks-skills/starrocks-assign-pr-reviewer/SKILL.md "Output
# Template") emits:
# Recommended reviewer: <owner login | none>
# Affinity reviewer: <login | none>
# Global reviewer: <team slug | none>
# Slack-mention both individuals. "Global reviewer" is intentionally
# skipped here -- it is a team, and member_slack.json is keyed by
# individual login, so we cannot resolve a Slack user-group ID.
# The LLM may wrap the line/value in markdown (e.g.
# **Recommended reviewer: kevincai**, Recommended reviewer:
# **kevincai**); tolerate optional leading * and extract only valid
# GitHub login chars to drop @, **, backticks, quotes.
ai_output="${RUNNER_TEMP}/ai_output.txt"
if [ ! -f "$ai_output" ]; then
echo "no ai_output, skip Slack notification"
exit 0
fi
extract_user() {
local key="$1"
grep -iE "^\**${key}:" "$ai_output" | tail -1 \
| sed -E "s|^\**[^:]*:\**[[:space:]]*||" \
| grep -oE '[A-Za-z0-9][A-Za-z0-9-]*' | head -1
}
owner=$(extract_user "Recommended reviewer")
affinity=$(extract_user "Affinity reviewer")
# De-dup + drop empty/none, preserve owner-before-affinity order.
reviewers=$(printf '%s\n%s\n' "$owner" "$affinity" \
| awk 'NF && $0!="none" && !seen[$0]++')
if [ -z "$reviewers" ]; then
echo "Could not extract AI-assigned reviewers from output, skip Slack notification"
exit 0
fi
echo "AI assigned: $(echo "$reviewers" | tr '\n' ' ')"
# GitHub login -> Slack ID; unmapped users fall back to plain @login
mentions=""
while IFS= read -r login; do
[ -z "$login" ] && continue
slack_id=$(jq -r --arg k "$login" '.[$k] // empty' conf/member_slack.json)
if [ -n "$slack_id" ]; then
mentions="${mentions}<@${slack_id}> "
else
mentions="${mentions}@${login} "
fi
done <<< "$reviewers"
# Include `${REPO}` so the link text shows e.g. `StarRocks/starrocks#74725 ...`
# — this file is synced to both StarRocks/starrocks and
# CelerData/celerdata-enterprise, both notify the same Slack channel,
# so the repo prefix lets reviewers tell which repo the PR is in.
text="${mentions}You were assigned as reviewer for <${PR_URL}|${REPO}#${PR} ${TITLE}>"
python3 lib/slack_notify.py \
--mode channel \
--token "$SLACK_BOT_TOKEN" \
--channel "$CHANNEL_ID" \
--text "$text"
# Module-risk briefing: a read-only, history-backed review focus for the PR's changed modules,
# mined from merged-bugfix pathology cards in code_kb, posted as a sticky PR comment. Sibling to
# assign-reviewer (same PR-open trigger / container / mergify+sync exclusion). Read-only: it never
# writes code_kb, so per-PR concurrency (cancel-in-progress) is fine — no origin-write race here.
# PREREQUISITES (container, one-time): the `github-pr-module-risk` plugin installed + the
# `context-base` MCP with `code_kb` USAGE (read). gh is absent in the container → the skill uses
# the GitHub REST API (GITHUB_TOKEN injected). --comment applies a strict confidence gate and emits
# either a sticky-marked briefing or the sentinel "MODULE-RISK: none" (then we post nothing).
#
# TRIGGER: every human-authored PR event (opened/reopened/synchronize/ready_for_review), minus
# mergify/ and *-sync-pr-* automation branches and Bot-typed authors. It intentionally does NOT
# restrict to org members / collaborators — external contributors are briefed too (deliberate; see
# the SECURITY note below for the accepted risk).
#
# Why no trusted-author gate: the prior `author_association in [OWNER,MEMBER,COLLABORATOR]` check
# silently skipped EVERY PR. In the pull_request_target payload `author_association` reflects only
# PUBLICLY-visible org membership, and StarRocks committers keep membership private, so they arrive
# as CONTRIBUTOR and the gate matched nobody (module-risk never ran once since it was added). The
# replacement `github.event.pull_request.user.type != 'Bot'` keeps automation (mergify[bot],
# dependabot, ...) out while briefing all human PRs.
#
# SECURITY (pull_request_target): the briefing runs attacker-influenceable PR body/diff through
# `claude --dangerously-skip-permissions` with secrets in env, so a malicious PR can prompt-inject
# the model to run bash and try to exfiltrate whatever the container holds. To bound the blast
# radius the work is SPLIT into two jobs:
# * module-risk (runs the model): its GITHUB_TOKEN is scoped READ-only (job `permissions:` below),
# so an injected run cannot write to the repo or steal a write-capable token. It never posts —
# it only hands the briefing text to the next job via a job output.
# * module-risk-comment (posts the comment): holds the pull-requests:write token, never runs the
# model, and receives the untrusted body through env (never spliced into a command via ${{ }}),
# so an injected body cannot inject shell. The body crosses the job boundary base64-encoded, so
# it also cannot forge the job-output framing.
# Residual risk: CLAUDE_CODE_OAUTH_TOKEN still lives in the model container and a determined
# injection could exfiltrate it — that is a Claude billing/abuse exposure (rotatable), NOT repo
# write or supply-chain. Closing it fully needs container network-egress allowlisting
# (anthropic / github / context-base only); tracked separately. (assign-reviewer stays safe for any
# author because its host step extracts only login-shaped tokens — a strict whitelist; module-risk's
# output is free-form.)
module-risk:
runs-on: [self-hosted, normal]
# Dedup rapid pushes for THIS job only (a new event cancels the in-flight briefing run).
# Job-scoped — NOT workflow-wide — so a synchronize run never cancels the opened-only
# assign-reviewer job (which would otherwise miss reviewer assignment + Slack).
concurrency:
group: ai-sr-skills-module-risk-${{ github.event.number }}
cancel-in-progress: true
continue-on-error: true
# READ-only token: this job runs the model on untrusted PR content, so it must NOT hold a
# write-capable token (see SECURITY above). The sibling module-risk-comment job does the posting.
permissions:
contents: read
pull-requests: read
if: >
(github.event.action == 'opened' || github.event.action == 'reopened' ||
github.event.action == 'synchronize' || github.event.action == 'ready_for_review') &&
!startsWith(github.head_ref, 'mergify/') &&
!contains(github.head_ref, '-sync-pr-') &&
github.event.pull_request.user.type != 'Bot'
outputs:
# base64 of the marker-to-EOF briefing (empty when there is nothing to post). base64 keeps the
# untrusted body to a single [A-Za-z0-9+/=] line so it can neither forge the job-output framing
# nor inject anything when the comment job consumes it.
body: ${{ steps.brief.outputs.body }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
steps:
- name: Module-risk briefing (AI, read-only)
id: brief
run: |
# Namespaced plugin command (a bare /review would NOT resolve in headless -p — it would be
# treated as prompt text and silently no-op). tee captures stdout for extraction below.
docker exec -i -u claudeuser \
-e GITHUB_TOKEN="${GITHUB_TOKEN}" \
-e CLAUDE_CODE_OAUTH_TOKEN="${CLAUDE_CODE_OAUTH_TOKEN}" \
claude-cli claude --dangerously-skip-permissions \
-p "/github-pr-module-risk:review ${PR_URL} --comment" 2>&1 | tee "${RUNNER_TEMP}/module_risk.txt" || true
# The skill emits the briefing starting at the marker line, or "MODULE-RISK: none" (no
# marker) when nothing clears the confidence gate. Take from the marker to EOF as the body
# (empty for no marker / sentinel / error => the comment job posts nothing); sed from the
# marker also strips any claude preamble before it. Hand it to the comment job base64-encoded
# on a single line (see the `outputs:` note above).
out="${RUNNER_TEMP}/module_risk.txt"
marker="<!-- module-risk-briefing -->"
b64=""
[ -f "$out" ] && b64="$(sed -n "/$marker/,\$p" "$out" 2>/dev/null | base64 -w0 || true)"
echo "body=${b64}" >> "$GITHUB_OUTPUT"
module-risk-comment:
needs: module-risk
runs-on: [self-hosted, normal]
continue-on-error: true
# Holds the write token and does the posting; it never runs the model, so untrusted PR content
# never meets a write-capable token. Skipped when the brief produced no briefing (empty output),
# and (via `needs`) when the brief job itself was gated out.
permissions:
pull-requests: write
if: ${{ needs.module-risk.outputs.body != '' }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR: ${{ github.event.number }}
REPO: ${{ github.repository }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
# Untrusted (model-produced) body, base64. Passed via env, never spliced into a command via
# ${{ }}, so a prompt-injected body cannot inject shell.
BODY_B64: ${{ needs.module-risk.outputs.body }}
steps:
- name: Upsert sticky comment
run: |
marker="<!-- module-risk-briefing -->"
body="$(printf '%s' "$BODY_B64" | base64 -d 2>/dev/null || true)"
if [ -z "$body" ]; then
echo "no high-confidence, diff-aligned module risk — not commenting"
exit 0
fi
# Defense-in-depth against an "assessment could not run" body. The skill's --comment
# contract makes an access/environment failure (no code_kb context-base MCP connection, or
# USAGE denied) emit the markerless `MODULE-RISK: unavailable` sentinel, so it normally
# arrives here empty and is dropped above. But the model is non-deterministic — if it
# regresses and emits a marker'd "cannot assess" body, that failure recurs identically on
# every PR, so posting it per-PR is pure noise. Drop it here too. We KEEP a real briefing
# (carries the fixed "High-confidence risks" section) and the genuine per-PR "coverage
# incomplete" caveat (PR-too-large); we drop only a body that is neither AND carries a
# context-base/code_kb access-failure signature (tool names / contextbase name never
# appear in a real StarRocks bug briefing).
if ! printf '%s' "$body" | grep -qiE 'High-confidence risks|coverage incomplete' \
&& printf '%s' "$body" | grep -qiE 'context-base MCP|list_collections|context_get|code_kb|MODULE-RISK: unavailable'; then
echo "module-risk not assessed — no code_kb MCP connection / access; environment failure, not commenting"
exit 0
fi
# Stamp the PR head SHA as line 2 (right after the marker, which the brief job's sed
# guaranteed is line 1) so reviewers can detect a briefing that predates the latest push.
# awk avoids sed escaping pitfalls; the SHA is hex so it is injection-safe.
sha_line="<!-- module-risk-head-sha: ${PR_HEAD_SHA} -->"
printf '%s\n' "$body" \
| awk -v s="$sha_line" 'NR==1{print; print s; next} {print}' \
> "${RUNNER_TEMP}/mr_body.md"
# Sticky: edit our existing marked comment if present, else create one. Idempotent across
# reopen / workflow rerun (the marker identifies our comment). PATCH body is JSON-built with
# jq so markdown is escaped correctly.
existing="$(gh api --paginate "repos/$REPO/issues/$PR/comments" \
--jq ".[] | select(.body | contains(\"$marker\")) | .id" 2>/dev/null | head -1 || true)"
if [ -n "$existing" ]; then
jq -n --rawfile b "${RUNNER_TEMP}/mr_body.md" '{body:$b}' \
| gh api -X PATCH "repos/$REPO/issues/comments/$existing" --input - >/dev/null \
&& echo "updated sticky comment $existing" \
|| echo "WARN: patch comment $existing failed (permissions or transient API error)"
else
gh pr comment "$PR" -R "$REPO" --body-file "${RUNNER_TEMP}/mr_body.md" \
&& echo "created sticky comment" \
|| echo "WARN: create comment failed (permissions or transient API error)"
fi