deprecate: Dataset without name parameter #3176
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: PR Bots | |
| on: | |
| pull_request_target: | |
| types: [opened, synchronize, labeled] | |
| concurrency: | |
| group: pr-bots-${{ github.event.pull_request.number }} | |
| cancel-in-progress: ${{ github.event.action == 'synchronize' }} | |
| jobs: | |
| size-label: | |
| name: Size Label | |
| if: github.event.action != 'labeled' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Calculate PR size and set label | |
| run: | | |
| # Fetch file changes for this PR | |
| FILES=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files --paginate | jq -s 'add') | |
| # Calculate lines by category (excluding uv.lock and cassettes) | |
| CODE=$(echo "$FILES" | jq '[.[] | select( | |
| (.filename | (startswith("tests/") or startswith("docs/") or endswith(".md")) | not) and | |
| (.filename != "uv.lock") and | |
| (.filename | contains("/cassettes/") | not) | |
| ) | .additions + .deletions] | add // 0') | |
| DOCS=$(echo "$FILES" | jq '[.[] | select( | |
| (.filename | (startswith("docs/") or endswith(".md"))) and | |
| (.filename != "uv.lock") and | |
| (.filename | contains("/cassettes/") | not) | |
| ) | .additions + .deletions] | add // 0') | |
| TESTS=$(echo "$FILES" | jq '[.[] | select( | |
| (.filename | startswith("tests/")) and | |
| (.filename | contains("/cassettes/") | not) and | |
| (.filename | endswith(".md") | not) | |
| ) | .additions + .deletions] | add // 0') | |
| # Calculate weighted score: code + 50% docs + 50% tests | |
| SCORE=$((CODE + DOCS / 2 + TESTS / 2)) | |
| echo "Code: $CODE, Docs: $DOCS, Tests: $TESTS" | |
| echo "Weighted score: $SCORE" | |
| # Determine size label based on cutoffs | |
| if [ $SCORE -le 100 ]; then | |
| SIZE="size: S" | |
| elif [ $SCORE -le 500 ]; then | |
| SIZE="size: M" | |
| elif [ $SCORE -le 1500 ]; then | |
| SIZE="size: L" | |
| else | |
| SIZE="size: XL" | |
| fi | |
| echo "Size: $SIZE" | |
| # Remove any existing size labels (except the one we're setting) via API | |
| for label in "size: S" "size: M" "size: L" "size: XL"; do | |
| if [ "$label" != "$SIZE" ]; then | |
| gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels/${label}" --method DELETE 2>/dev/null || true | |
| fi | |
| done | |
| # Add the new size label via API | |
| gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels" --method POST -f "labels[]=$SIZE" | |
| echo "Set label: $SIZE (score: $SCORE)" | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| # Security: The classify job runs the LLM with READ-ONLY permissions and no label API access. | |
| # The LLM's output is validated against an allowlist before the apply job takes any write action. | |
| # This prevents prompt injection from adding arbitrary labels (e.g. 'auto-review' to trigger the review job). | |
| category-classify: | |
| name: Category Classify | |
| if: github.event.action != 'labeled' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| category: ${{ steps.extract.outputs.category }} | |
| skip: ${{ steps.check-label.outputs.has_label }} | |
| steps: | |
| - name: Check for modified config files | |
| if: github.event.pull_request.head.repo.fork | |
| run: | | |
| CHANGED=$(gh pr diff ${{ github.event.pull_request.number }} --name-only --repo ${{ github.repository }}) | |
| if echo "$CHANGED" | grep -qiE '(^|/)AGENTS\.md$|(^|/)CLAUDE\.md$|(^|/)\.claude/'; then | |
| echo "::error::PR modifies agent config files (AGENTS.md, CLAUDE.md, or .claude/). Skipping auto-labeling for security." | |
| exit 1 | |
| fi | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| - name: Check if category label already exists | |
| id: check-label | |
| run: | | |
| LABELS=$(gh pr view ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --json labels --jq '.labels[].name') | |
| CATEGORY_LABELS=("bug" "feature" "docs" "chore" "dependency") | |
| for label in "${CATEGORY_LABELS[@]}"; do | |
| if echo "$LABELS" | grep -q "^${label}$"; then | |
| echo "has_label=true" >> $GITHUB_OUTPUT | |
| echo "PR already has category label: $label" | |
| exit 0 | |
| fi | |
| done | |
| echo "has_label=false" >> $GITHUB_OUTPUT | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| - name: Checkout repository | |
| if: steps.check-label.outputs.has_label == 'false' | |
| uses: actions/checkout@v6 | |
| - name: Classify PR with Claude Code | |
| if: steps.check-label.outputs.has_label == 'false' | |
| id: classify | |
| uses: anthropics/claude-code-action@v1 | |
| env: | |
| ANTHROPIC_BASE_URL: ${{ secrets.CLAUDE_CODE_BASE_URL }} | |
| with: | |
| anthropic_api_key: ${{ secrets.CLAUDE_CODE_API_KEY || secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ github.token }} | |
| allowed_non_write_users: "*" | |
| claude_args: | | |
| --allowedTools "Bash(gh pr view:*),Bash(gh pr diff:*)" | |
| --json-schema '{"type":"object","properties":{"category":{"type":"string"}},"required":["category"]}' | |
| prompt: | | |
| Classify PR #${{ github.event.pull_request.number }} in ${{ github.repository }}. | |
| Run `gh pr view ${{ github.event.pull_request.number }} --repo ${{ github.repository }}` for the title and description. | |
| Run `gh pr diff ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --name-only` for the changed files. | |
| Categories: | |
| - bug: Fixes broken behavior | |
| - feature: New functionality | |
| - docs: Documentation-only (no code changes) | |
| - chore: CI, refactoring, dev dependencies, tests-only | |
| - dependency: Production dependency updates | |
| When both code and docs change, prefer `feature` or `bug` over `docs`. | |
| If pyproject.toml files changed, run `gh pr diff ${{ github.event.pull_request.number }} --repo ${{ github.repository }}` to see the full diff and distinguish production deps (`dependency`) from dev-only deps in `[dependency-groups]` (`chore`). | |
| Return your classification as JSON with a "category" field set to one of: bug, feature, docs, chore, dependency. | |
| - name: Extract and validate category | |
| if: steps.check-label.outputs.has_label == 'false' | |
| id: extract | |
| run: | | |
| CATEGORY=$(echo "$STRUCTURED_OUTPUT" | jq -r .category | tr -d '[:space:]') | |
| ALLOWED="bug feature docs chore dependency" | |
| if ! echo "$ALLOWED" | grep -Fqw "$CATEGORY"; then | |
| echo "::error::Invalid category '$CATEGORY' from Claude — must be one of: $ALLOWED" | |
| exit 1 | |
| fi | |
| echo "category=$CATEGORY" >> $GITHUB_OUTPUT | |
| echo "Classified as: $CATEGORY" | |
| env: | |
| STRUCTURED_OUTPUT: ${{ steps.classify.outputs.structured_output }} | |
| category-apply: | |
| name: Category Apply | |
| needs: category-classify | |
| if: needs.category-classify.outputs.skip != 'true' && needs.category-classify.outputs.category != '' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Validate and apply category label | |
| run: | | |
| CATEGORY="${{ needs.category-classify.outputs.category }}" | |
| ALLOWED="bug feature docs chore dependency" | |
| if ! echo "$ALLOWED" | grep -Fqw "$CATEGORY"; then | |
| echo "::error::Invalid category '$CATEGORY' — must be one of: $ALLOWED" | |
| exit 1 | |
| fi | |
| gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels" \ | |
| --method POST -f "labels[]=$CATEGORY" | |
| echo "Applied label: $CATEGORY" | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| review: | |
| name: Review | |
| needs: [size-label, category-apply] | |
| if: >- | |
| !failure() && !cancelled() && | |
| github.event.action == 'labeled' && github.event.label.name == 'auto-review' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| actions: read | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| repository: ${{ github.event.pull_request.head.repo.full_name }} | |
| ref: ${{ github.event.pull_request.head.ref }} | |
| fetch-depth: 0 | |
| - name: Check for modified config files | |
| if: github.event.pull_request.head.repo.fork | |
| run: | | |
| CHANGED=$(gh pr diff ${{ github.event.pull_request.number }} --name-only --repo ${{ github.repository }}) | |
| if echo "$CHANGED" | grep -qiE '(^|/)AGENTS\.md$|(^|/)CLAUDE\.md$|(^|/)\.claude/'; then | |
| echo "::error::PR modifies agent config files (AGENTS.md, CLAUDE.md, or .claude/). Skipping auto-review for security." | |
| exit 1 | |
| fi | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Select review model | |
| id: model | |
| run: | | |
| LABELS=$(gh pr view ${{ github.event.pull_request.number }} --repo ${{ github.repository }} --json labels --jq '.labels[].name') | |
| echo "Labels: $LABELS" | |
| CATEGORY=$(echo "$LABELS" | grep -E '^(bug|feature|docs|chore|dependency)$' | head -1) | |
| SIZE=$(echo "$LABELS" | grep -E '^size: ' | head -1) | |
| # Default to Opus for large/complex PRs | |
| MODEL="claude-opus-4-6[1m]" | |
| # Disabled for now because Sonnet reviews have been disappointing | |
| # # Use Sonnet for docs, dependency, chore, or small/medium PRs | |
| # if [ "$CATEGORY" = "docs" ] || [ "$CATEGORY" = "dependency" ] || [ "$CATEGORY" = "chore" ] || [ "$SIZE" = "size: S" ] || [ "$SIZE" = "size: M" ]; then | |
| # MODEL="claude-sonnet-4-5[1m]" | |
| # fi | |
| echo "model=$MODEL" >> $GITHUB_OUTPUT | |
| echo "Selected model: $MODEL (category: $CATEGORY, size: $SIZE)" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Gather PR context | |
| run: | | |
| # Use the script from the base repo (main branch), not the fork | |
| gh api "repos/${REPO}/contents/scripts/gather-review-context.sh?ref=${BASE_REF}" --jq .content | base64 -d > /tmp/gather-review-context.sh | |
| bash /tmp/gather-review-context.sh "$PR_NUMBER" "$REPO" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| REPO: ${{ github.repository }} | |
| BASE_REF: ${{ github.event.pull_request.base.ref }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| - uses: anthropics/claude-code-action@v1 | |
| env: | |
| ANTHROPIC_BASE_URL: ${{ secrets.CLAUDE_CODE_BASE_URL }} | |
| with: | |
| anthropic_api_key: ${{ secrets.CLAUDE_CODE_API_KEY || secrets.ANTHROPIC_API_KEY }} | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| allowed_non_write_users: "*" | |
| display_report: 'true' | |
| additional_permissions: | | |
| actions: read | |
| claude_args: | | |
| --model ${{ steps.model.outputs.model }} | |
| --allowedTools "mcp__github_inline_comment__create_inline_comment,Bash(gh pr comment:*),Bash(gh pr diff:*),Bash(gh pr view:*),Bash(gh pr checks:*),Bash(gh pr list:*),Bash(gh issue view:*),Bash(gh issue list:*),Bash(gh run view:*),Bash(gh run list:*),Bash(gh api repos/${{ github.repository }}/pulls/comments/:*),Bash(gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments:*),Bash(gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews:*),Bash(gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments:*),Bash(git log:*),Bash(git diff:*),Bash(git grep:*),Bash(git show:*),Bash(git status:*),Bash(jq:*),Bash(cat:*),Bash(rg:*),Bash(ls:*),Bash(tree:*),Bash(grep:*),WebSearch,WebFetch" | |
| prompt: | | |
| REPO: ${{ github.repository }} | |
| PR NUMBER: ${{ github.event.pull_request.number }} | |
| PR AUTHOR: ${{ github.event.pull_request.user.login }} (${{ github.event.pull_request.author_association }}) | |
| Review this pull request. The PR branch is already checked out in the current working directory and the `CLAUDE.md` at the root (symlinked to `AGENTS.md`) is already loaded in your system prompt. | |
| # What to look for | |
| - If the PR should not have been created yet (e.g. no issue, insufficiently defined scope, not ready for implementation, duplicate of existing open PR), just leave a comment informing the user and maintainer of this and don't bother doing a thorough review. | |
| - Any change that does not align with the project's standards, philosophy, or requirements for every contribution as stated in `AGENTS.md` (symlinked from `CLAUDE.md`). | |
| - Any change that does not match maintainer guidance in the issue or earlier PR comments on what an acceptable solution would look like. | |
| - Any change or design decision or tradeoff (in both behavior and API) that needs explicit consideration, discussion, or maintainer awareness and approval. | |
| - Any line of code that violates the concrete guidelines/rules laid out in the relevant `AGENTS.md` file(s): the top-level guidelines apply to all changes, while directory-specific guidelines affect only the changes in that directory. | |
| - Anything else that the responsibilities you are assigned in `AGENTS.md` suggest that you should be calling out: use your best judgment. | |
| Generally, the priority in terms of "crucial to get right" and "what to focus on first in a new PR" is public API > concepts and behavior > documentation > tests > code style. | |
| If the PR has high level problems that will likely require significant changes at lower levels, hold off on looking for or commenting on lower level problems until the higher level problems are addressed, so that the PR author (and your context window) don't get overwhelmed. | |
| Note that while another agent (Devin) is responsible for thoroughly reviewing the implementation for bugs, security issues, and edge cases, | |
| _you_ are responsible for catching every violation of the repository's standards and guidelines listed in the `AGENTS.md` and `agent_docs/*.md` files. | |
| Do not focus exclusively on high-level concerns: by the time the author has addressed every comment you've left over multiple rounds of review, the PR should be ready to merge. | |
| # Gathering context | |
| Before doing anything else, read ALL of the following pre-gathered context files in a single parallel tool call: | |
| - `.github/.review-context/pr-details.json` — PR title, body, author, branch info, labels, state, review decision, timestamps | |
| - `.github/.review-context/pr-comments.txt` — existing top-level PR comments | |
| - `.github/.review-context/review-comments.txt` — existing inline review comments (resolved+outdated threads and threads predating the last auto-review are collapsed to one-liners with comment IDs so you can fetch full details if needed) | |
| - `.github/.review-context/related-issues.txt` — linked issues and their comments | |
| - `.github/.review-context/changed-files.txt` — changed files with per-file addition/deletion counts (tab-separated; non-generated files include a third column with the path to their per-file diff) | |
| - `.github/.review-context/agents-md.txt` — directory-specific `AGENTS.md` files for changed directories | |
| - `agent_docs/index.md` - repo-wide coding guidelines | |
| The diff is split into per-file diffs under `.github/.review-context/diff/` (excluding `uv.lock` and cassettes), that you can read on demand and in parallel. | |
| The diffs include function-level context (`git diff -W`), so you can see the full function/method being modified without needing to read the source file separately. | |
| Each commentable line in the diff is prefixed with its source line number: `NL:<number>` for new or context lines, `OL:<number>` for deleted lines. | |
| For newly added files, the diff contains the complete file contents — do not re-read these from disk. | |
| The diff file paths are listed in the third column of `changed-files.txt`. | |
| The pre-gathered diffs are the source of truth for what this PR changes. Do not re-fetch diffs or file lists using `gh pr diff` or `gh api`. | |
| When you need code context beyond what the diffs provide, use the `Read` tool on the checked-out source files. | |
| Use the `gh` CLI only when you need additional information not already in these files (e.g. to read other referenced PRs or issues, check CI status, or read files excluded from the gathered diff). | |
| Use specific `gh` subcommands (`gh pr view`, `gh issue view`, `gh run view`, etc.) rather than `gh api` for most queries. | |
| `gh api` is scoped to comment and review endpoints on this PR only: | |
| - `gh api repos/${{ github.repository }}/pulls/comments/<id>` — individual review comment by ID | |
| - `gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments` — list review comments | |
| - `gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews` — list reviews | |
| - `gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments` — list issue comments | |
| Be careful about loading large diffs if you're unlikely to need them yet, like massive test files when there's plenty of more interesting code to comment on first, as you don't want to blow your context window too early. | |
| You will usually want to read all the "core implementation" and docs diffs in one go, though, so you have the full context of the PR as you identify problems, instead of going file by file. | |
| # Posting comments | |
| While gathering context and learning about the PR, keep track of problems/points of discussion as you find them, and wait to post comments until the end, | |
| as the comments you write will be better, less duplicative and more focused on the changes that really matter if you have the full set of problems as context. | |
| For each identified issue that is determined to be worth a new comment, use `mcp__github_inline_comment__create_inline_comment` to attach the feedback to a specific line of code. | |
| - Only lines with an `NL:` or `OL:` prefix in the diff are commentable. For OL lines, use `side: LEFT`. | |
| - Include the reasoning, but don't quote specific rules from the `AGENTS.md` files. | |
| - Include a concrete suggestion if appropriate (but to not use ` ```suggestion ` blocks as they can render incorrectly when the line numbers are off) | |
| - Include a ping to the maintainer (`@DouweM`) on any change that requires maintainer input before the PR author can move forward. | |
| - If the same issue shows up in multiple places, post a comment on each instance but have later comments refer to the first comment using a link. | |
| - Use `gh pr comment` only for important feedback that doesn't relate to a specific line or file, not for a summary of feedback you've already posted inline. | |
| Your comments should be: | |
| - actionable: they should request a change, flag a concern that needs discussion, and/or suggest an improvement; don't comment on positive aspects of the PR like "excellent design choices". | |
| - concise and to the point: don't use unnecessary emojis, lists, or subheadings, but do link to code if appropriate; 1 to 3 paragraphs are pretty much always enough. | |
| - friendly without being sycophantic: use the tone and language of a helpful and encouraging project maintainer, but no need to compliment the author on positive aspects of the PR or point out changes that are good. | |
| - non-repetitive: don't repeat things pointed out in earlier review comments, unless it looks like they'll be forgotten if you don't point them out; e.g. when they're marked as resolved/outdated but the problem persists without a satisfactory resolution (like a maintainer comment saying the comment does not need to be addressed). | |
| You are meant to be helpful to the contributor and the maintainer, so your comments should never add noise to the conversation: | |
| - Do not post a final summary comment; inline comments are sufficient. | |
| - Do not comment on lines that do not need improvement, maintainer awareness, or discussion; comments pointing out a good choice are just noise. | |
| - Do not post multiple comments for the same exact issue unless it shows up in different places. | |
| It bears repeating that you are the first line of defense against low-quality contributions and maintainer headaches, and you have a big role in ensuring that every contribution to this project meets or exceeds the high standards that the Pydantic brand is known and loved for. | |
| - name: Remove auto-review label | |
| if: always() | |
| run: gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/labels/auto-review" --method DELETE 2>/dev/null || true | |
| env: | |
| GH_TOKEN: ${{ github.token }} |