Skip to content

fix(tool_parser): coerce XML tool-call args by declared schema type (qwen_xml, glm4_moe) #12365

fix(tool_parser): coerce XML tool-call args by declared schema type (qwen_xml, glm4_moe)

fix(tool_parser): coerce XML tool-call args by declared schema type (qwen_xml, glm4_moe) #12365

name: Claude Code Review
on:
pull_request:
types: [opened, synchronize, reopened]
paths-ignore:
- '*.md'
- 'docs/**'
- '*.lock'
- 'mkdocs.yml'
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
permissions:
contents: read
pull-requests: write
issues: write
id-token: write
jobs:
# Auto-review on PR open/push
review:
name: Claude PR Review
if: >-
github.event_name == 'pull_request'
&& github.repository == 'lightseekorg/smg'
&& github.actor != 'dependabot[bot]'
&& !github.event.pull_request.head.repo.fork
runs-on: k8s-runner-cpu
timeout-minutes: 30
concurrency:
group: claude-review-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Export API key from pod env
run: |
echo "::add-mask::${ANTHROPIC_API_KEY}"
echo "ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}" >> "$GITHUB_ENV"
- name: Install gh CLI
run: |
if ! command -v gh &>/dev/null; then
mkdir -p "$HOME/.local/bin"
GH_VERSION="2.74.0"
curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" \
| tar xz --strip-components=2 -C "$HOME/.local/bin" "gh_${GH_VERSION}_linux_amd64/bin/gh"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
fi
- name: Record run start time
id: run-start
run: echo "timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
- uses: anthropics/claude-code-action@v1
id: claude-review
continue-on-error: true
with:
anthropic_api_key: ${{ env.ANTHROPIC_API_KEY }}
prompt: |
REPO: ${{ github.repository }}
PR NUMBER: ${{ github.event.pull_request.number }}
EVENT: ${{ github.event.action }}
Review this pull request.
IMPORTANT — Incremental vs full review:
If EVENT is "synchronize" (new push to existing PR), this is a
follow-up review. Previous review comments are your memory.
1. Run `git diff ${{ github.event.before }}..HEAD` to see only what this push changed.
If this fails (e.g., force-push rewrote history), fall back to
`git diff origin/main...HEAD` for a full review instead.
2. Use the comments from step 3 to see what was already flagged.
3. Focus on NEW or CHANGED code in this push. Skip re-reviewing
unchanged code that was already covered.
4. If a previous comment is now resolved by the new push, do not
re-post it.
If EVENT is "opened" or "reopened", do a full review using
`git diff origin/main...HEAD`.
IMPORTANT — Output early:
Always post inline comments BEFORE doing further exploration.
An incomplete review is better than no review because you ran
out of turns.
For every issue found, call mcp__github_inline_comment__create_inline_comment
with path, line, and a severity-prefixed body per REVIEW.md format.
IMPORTANT — Deduplication:
Use the comments fetched in step 3 to deduplicate.
Self-dedup (strict): If claude[bot] already commented on the same
file and line (±3 lines), SKIP — do not post regardless of wording.
Cross-bot awareness (soft): If another bot already commented on the
same file and line (±3 lines), READ their comment. Only post if you
have a genuinely different concern not covered by the existing comment.
Human replies: If a human replied to a bot comment (e.g., "fixed",
"won't fix", "disagree"), respect their response. Do not re-flag
issues that the author has acknowledged or intentionally declined.
IMPORTANT — PR description template check (run this FIRST,
before the code review steps below, on every event):
The PR body must follow .github/PULL_REQUEST_TEMPLATE.md.
Required headers (literal lines, anywhere in body, any order):
## Description
### Problem
### Solution
## Changes
## Test Plan
The trailing <details>Checklist</details> block is template
boilerplate, not content — strip it before computing section
content (see pre-processing below).
Pre-processing: before computing section content, strip from
BODY any <details>...</details> block whose <summary> line,
after trimming, is exactly "Checklist". This is the trailing
block from PULL_REQUEST_TEMPLATE.md and must not count as
content for any section — otherwise an empty Test Plan would
be falsely treated as filled, because there is no later ##
header so the checklist sits inside the Test Plan section.
"Section content" = text between a header and the NEXT ## or ###
header line (or end of body) in the pre-processed BODY. After
stripping all HTML comments (<!-- ... -->, including multi-line)
and trimming whitespace, content must be non-empty. For
"## Description", the Problem and Solution subsections count
as content — Description is filled if both subsections are
filled, even if no prose sits directly under it.
To check:
a. Fetch the body:
BODY=$(gh pr view ${{ github.event.pull_request.number }} \
--repo ${{ github.repository }} --json body --jq .body)
b. Verify each required header appears in BODY.
c. For each of Problem, Solution, Changes, Test Plan, verify the
section is filled per the definition above.
d. Build a bulleted gap list, e.g.
- Missing header: `## Test Plan`
- Empty section: `### Problem` (only contains HTML comments)
Dedup by a hidden marker on a claude[bot] top-level (issue) comment:
EXISTING=$(gh api \
repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \
--paginate \
--jq '.[] | select(.user.login == "claude[bot]" and (.body | startswith("<!-- claude-pr-template-check -->"))) | .id' \
| head -1)
Cases:
gaps + no EXISTING → post a new comment:
gh pr comment ${{ github.event.pull_request.number }} \
--repo ${{ github.repository }} --body "$MSG"
gaps + EXISTING → update in place (never post a second one):
gh api repos/${{ github.repository }}/issues/comments/$EXISTING \
-X PATCH -f body="$MSG"
no gaps + EXISTING → delete on green:
gh api repos/${{ github.repository }}/issues/comments/$EXISTING -X DELETE
no gaps + no EXISTING → do nothing.
$MSG body (verbatim, with the gap list from step d substituted in):
<!-- claude-pr-template-check -->
👋 The PR description doesn't fully follow
[PULL_REQUEST_TEMPLATE.md](https://github.com/${{ github.repository }}/blob/main/.github/PULL_REQUEST_TEMPLATE.md):
<bulleted gap list from step d>
Please update the PR description so reviewers have the context they need.
Finish this check (post/update/delete) before moving on, so a
turn-limit cutoff during code review doesn't lose the template check.
Steps (do steps 1-3 in parallel):
1. Run the appropriate git diff (see incremental review rules above).
2. Read REVIEW.md for severity format, focus areas, and domain knowledge.
3. Fetch existing review comments (bot + human replies) for dedup and memory:
gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments \
--paginate --jq '.[] | {id, path, line, body, user: .user.login, in_reply_to_id}' \
| jq -s '.[-50:]'
4. Read changed files for context.
5. Post inline comments for issues found.
6. If EVENT is "opened" or "reopened" (full review) AND no 🔴
Important issues were found, submit an APPROVE review:
gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews \
-f event=APPROVE
Do NOT approve on "synchronize" (incremental) reviews — only
full reviews have enough context to approve.
claude_args: |
--model claude-opus-4-6
--max-turns 50
--allowedTools "Read,Glob,Grep,Bash,Agent,Skill,ToolSearch,TaskCreate,TaskUpdate,TaskGet,mcp__github_inline_comment__create_inline_comment"
show_full_output: true
plugins: "pr-review-toolkit@claude-plugins-official"
plugin_marketplaces: |
https://github.com/anthropics/claude-plugins-official.git
https://github.com/lightseekorg/smg-dev-guide.git
- name: Annotate on failure
if: steps.claude-review.outcome == 'failure'
run: echo "::warning::Claude review did not complete — may have hit turn limit or encountered an error."
- name: Review summary
if: always()
env:
GH_TOKEN: ${{ github.token }}
run: |
LOG="${RUNNER_TEMP}/claude-execution-output.json"
# Extract usage stats from execution output
# The file may be a JSON array of events — the result entry is last
if [ -f "$LOG" ]; then
RESULT=$(jq 'if type == "array" then .[-1] else . end' "$LOG")
COST=$(echo "$RESULT" | jq -r '.total_cost_usd // 0')
TURNS=$(echo "$RESULT" | jq -r '.num_turns // 0')
DURATION=$(echo "$RESULT" | jq -r '(.duration_ms // 0) / 1000 | floor')
INPUT=$(echo "$RESULT" | jq -r '.usage.input_tokens // 0')
OUTPUT=$(echo "$RESULT" | jq -r '.usage.output_tokens // 0')
CACHE_READ=$(echo "$RESULT" | jq -r '.usage.cache_read_input_tokens // 0')
CACHE_CREATE=$(echo "$RESULT" | jq -r '.usage.cache_creation_input_tokens // 0')
STATUS=$(echo "$RESULT" | jq -r '.terminal_reason // "unknown"')
else
COST=0; TURNS=0; DURATION=0; INPUT=0; OUTPUT=0
CACHE_READ=0; CACHE_CREATE=0; STATUS="no output"
fi
# Fetch inline comments posted by claude in THIS run only
RUN_START="${{ steps.run-start.outputs.timestamp }}"
FETCH_OK=true
if [ -z "$RUN_START" ]; then
FETCH_OK=false
COMMENTS="[]"
else
if COMMENTS=$(set -o pipefail; gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments \
--paginate --jq ".[] | select((.user.login == \"claude[bot]\" or .user.login == \"github-actions[bot]\") and .created_at >= \"${RUN_START}\") | {path, line, body}" \
| jq -s '.' 2>/dev/null); then
: # fetch succeeded
else
FETCH_OK=false
COMMENTS="[]"
fi
fi
IMPORTANT=$(echo "$COMMENTS" | jq '[.[] | select(.body | test("🔴"))] | length')
NIT=$(echo "$COMMENTS" | jq '[.[] | select(.body | test("🟡"))] | length')
PREEXISTING=$(echo "$COMMENTS" | jq '[.[] | select(.body | test("🟣"))] | length')
TOTAL=$(echo "$COMMENTS" | jq 'length')
# Build one-line summaries per finding
FINDINGS=$(echo "$COMMENTS" | jq -r '.[] | "| `\(.path):\(.line)` | \(.body | split("\n")[0] | gsub("\\|"; "∣")) |"')
# Write step summary
{
echo "## Claude PR Review Summary"
echo ""
if [ "$STATUS" = "completed" ]; then
echo "✅ **Status:** Completed in ${TURNS} turns (${DURATION}s)"
else
echo "⚠️ **Status:** ${STATUS} after ${TURNS} turns (${DURATION}s)"
fi
echo ""
echo "### Findings"
echo ""
if [ "$FETCH_OK" = "false" ]; then
echo "⚠️ Summary unavailable — comment fetch failed or run-start timestamp missing."
elif [ "$TOTAL" -eq 0 ]; then
echo "No issues found."
else
echo "| Severity | Count |"
echo "|----------|-------|"
echo "| 🔴 Important | ${IMPORTANT} |"
echo "| 🟡 Nit | ${NIT} |"
echo "| 🟣 Pre-existing | ${PREEXISTING} |"
echo "| **Total** | **${TOTAL}** |"
echo ""
echo "| Location | Finding |"
echo "|----------|---------|"
echo "$FINDINGS"
fi
echo ""
echo "### Usage"
echo ""
echo "| Metric | Value |"
echo "|--------|-------|"
echo "| Cost | \$${COST} |"
echo "| Input tokens | ${INPUT} |"
echo "| Output tokens | ${OUTPUT} |"
echo "| Cache read | ${CACHE_READ} |"
echo "| Cache creation | ${CACHE_CREATE} |"
} >> "$GITHUB_STEP_SUMMARY"
# Respond to @claude mentions in PR/issue comments
respond:
name: Claude Respond
if: >-
(github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment')
&& contains(github.event.comment.body, '@claude')
&& github.repository == 'lightseekorg/smg'
runs-on: k8s-runner-cpu
timeout-minutes: 30
concurrency:
group: claude-respond-${{ github.event.issue.number || github.event.pull_request.number }}
cancel-in-progress: false
steps:
- name: Checkout
uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Export API key from pod env
run: |
echo "::add-mask::${ANTHROPIC_API_KEY}"
echo "ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}" >> "$GITHUB_ENV"
- name: Install gh CLI
run: |
if ! command -v gh &>/dev/null; then
mkdir -p "$HOME/.local/bin"
GH_VERSION="2.74.0"
curl -fsSL "https://github.com/cli/cli/releases/download/v${GH_VERSION}/gh_${GH_VERSION}_linux_amd64.tar.gz" \
| tar xz --strip-components=2 -C "$HOME/.local/bin" "gh_${GH_VERSION}_linux_amd64/bin/gh"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
fi
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ env.ANTHROPIC_API_KEY }}
claude_args: |
--model claude-opus-4-6
--max-turns 30
--allowedTools "Read,Glob,Grep,Bash,Agent,Skill,ToolSearch,TaskCreate,TaskUpdate,TaskGet,mcp__github_inline_comment__create_inline_comment"
plugins: "pr-review-toolkit@claude-plugins-official"
plugin_marketplaces: |
https://github.com/anthropics/claude-plugins-official.git
https://github.com/lightseekorg/smg-dev-guide.git