[UT] Add SQLTester cases for FILES() path pattern styles #4036
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |