chore: import upstream snapshot with attribution
Harness Compat / harness compat (push) Failing after 0s
CI / test on 3.12 (standard) (push) Has been cancelled
CI / test on 3.13 (standard) (push) Has been cancelled
CI / test on 3.14 (standard) (push) Has been cancelled
CI / test on 3.10 (all-extras) (push) Has been cancelled
CI / test on 3.11 (all-extras) (push) Has been cancelled
CI / test on 3.12 (all-extras) (push) Has been cancelled
CI / test on 3.14 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.10 (pydantic-evals) (push) Has been cancelled
CI / test on 3.11 (pydantic-evals) (push) Has been cancelled
CI / test on 3.12 (pydantic-evals) (push) Has been cancelled
CI / deploy-docs-preview (push) Has been cancelled
CI / build release artifacts (push) Has been cancelled
CI / publish to PyPI (push) Has been cancelled
CI / Send tweet (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / mypy (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / test on 3.10 (standard) (push) Has been cancelled
CI / test on 3.11 (standard) (push) Has been cancelled
CI / test on 3.13 (all-extras) (push) Has been cancelled
CI / test on 3.14 (all-extras) (push) Has been cancelled
CI / test on 3.10 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.11 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.12 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.13 (pydantic-ai-slim) (push) Has been cancelled
CI / test on 3.13 (pydantic-evals) (push) Has been cancelled
CI / test on 3.14 (pydantic-evals) (push) Has been cancelled
CI / test on 3.10 (lowest-versions) (push) Has been cancelled
CI / test on 3.11 (lowest-versions) (push) Has been cancelled
CI / test on 3.12 (lowest-versions) (push) Has been cancelled
CI / test on 3.13 (lowest-versions) (push) Has been cancelled
CI / test on 3.14 (lowest-versions) (push) Has been cancelled
CI / test examples on 3.11 (push) Has been cancelled
CI / test examples on 3.12 (push) Has been cancelled
CI / test examples on 3.13 (push) Has been cancelled
CI / test examples on 3.14 (push) Has been cancelled
CI / coverage (push) Has been cancelled
CI / check (push) Has been cancelled
CI / deploy-docs (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:27:52 +08:00
commit 9201ef759e
2096 changed files with 1232387 additions and 0 deletions
@@ -0,0 +1,59 @@
---
# Shared adversarial validity gate for bug/behavior-filing gh-aw sweeps.
# gh-aw imports this file; the markdown below (after the closing ---) is
# appended to the agent's task prompt at runtime via {{#runtime-import}}.
#
# Encodes the concrete failure modes that made past sweep issues get closed as
# not-a-bug / by-design / duplicate. Every create-issue must clear this gate.
---
## Adversarial validity gate — mandatory before `create_issue`
Put the finding through this validity gate before filing, reviewing it as a
skeptical maintainer would — treat **"this is NOT a bug"** as the default and
file **only** if it survives every check below; otherwise call
`mcp__safeoutputs__noop`. Most runs should noop — a false or by-design report
costs more maintainer time than a missed one.
Record the results as an **`## Adversarial review`** section in the issue body.
An issue that omits it is incomplete — noop instead.
1. **Reproduced on current `main`, for real.** You must have *executed* code this
run — a source-level snippet via `uv run python -c …`, a tiny script, or a
single `uv run pytest -k …` — and **observed** the failure. Paste the exact
command and its actual output. A claim you only reasoned about (e.g. "this
*would* fail") is not a bug and is the most common reason past reports were
rejected as a false premise.
2. **Existing tests don't already bless the behavior.** Grep the suite for the
symbol / code path and **read** the nearest tests. If a passing test already
asserts the current behavior, the behavior is intentional → noop. (Past
reports proposed one-line "fixes" that broke a dozen adapter/serialization
tests asserting the opposite on purpose.) A fix that would merely require
*updating* tests is not, by itself, proof of intent — real fixes often update
a stale test — but it raises the bar: read every test the fix would touch and
noop if any of them asserts the current behavior deliberately (an explicit
comment/docstring, or the same expectation repeated across several tests).
3. **Ruled out "by design."** Check for: a nearby comment/docstring explaining
the choice, the provider profile, a maintainer decision in a linked issue/PR,
and whether other providers/adapters deliberately do the same thing.
Programmatic-only fields (`metadata`, `conversation_id`) excluded from wire /
UI protocols, and request-only parts absent from a *response* union (or vice
versa), are intentional — not bugs.
4. **No cross-provider false equivalence** *(provider-specific findings only)*.
If the finding concerns a provider's request/response payload or SDK shape,
verify the real type for **that** provider from its own types or docs — never
infer a bug by analogy to a different provider. For provider-agnostic findings
(core serialization/round-trip, streaming lifecycle, message plumbing), this
check does not apply — skip it.
5. **Not already tracked.** Re-confirm the dedup above — label-filtered where
this sweep has a dedicated label, otherwise the full open-issue scan —
returned nothing covering this exact finding.
If any *applicable* check fails or is genuinely inconclusive,
`mcp__safeoutputs__noop`. A check that doesn't apply (e.g. the provider check for
a core finding) is not a failure — skip it. One issue that clears every
applicable check beats five that don't.
+6
View File
@@ -0,0 +1,6 @@
---
# Full git history for all pydantic-ai gh-aw workflows.
# Needed for git log / git diff in the agent container.
checkout:
fetch-depth: 0
---
@@ -0,0 +1,36 @@
---
# Shared runtime + engine config for the Pydantic AI gh-aw shim (MiniMax backend).
#
# Registers as the built-in `claude` engine and only overrides `command`, so
# gh-aw runs its full Claude proxy + credential-injection machinery.
#
# ANTHROPIC_BASE_URL MUST be a compile-time literal (not a ${{ vars.* }}
# expression): gh-aw derives the api-proxy target host AND the
# `--anthropic-api-base-path` from its parsed URL path at compile time. With a
# vars expression the path can't be parsed, so the proxy drops the `/anthropic`
# prefix and the upstream returns 404. Only ANTHROPIC_API_KEY stays a secret
# (injected by the AWF api-proxy, excluded from the agent container).
# MiniMax exposes an Anthropic-compatible API at https://api.minimax.io/anthropic.
#
# The checked-out workspace is mounted no-exec in the AWF sandbox, so a
# pre-step stages a launcher in gh-aw's exec-able /tmp/gh-aw/bin that runs
# `uv run --script` against the workspace harness.
#
# Required repo variable:
# GH_AW_MODEL — model name forwarded as `--model <name>` to the harness.
# Required secret:
# MINIMAX_API_KEY — API key injected by the AWF api-proxy.
#
# Usage:
# imports:
# - shared/engine-minimax.md
runtimes:
uv: {}
engine:
id: claude
model: ${{ vars.GH_AW_MODEL }}
command: /tmp/gh-aw/bin/pydantic-ai-runner-launch
env:
ANTHROPIC_BASE_URL: https://api.minimax.io/anthropic
ANTHROPIC_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
---
@@ -0,0 +1,118 @@
---
# Shared network allowlist for provider SDK/API hosts and vendor docs.
#
# This list is intended for research/review workflows that may need to consult:
# - provider API endpoints
# - provider SDK/docs/console pages
# - provider model catalogs and related reference docs
#
# Sources:
# - Existing allowlist from pydantic-ai-pr-review.md
# - Domains referenced under docs/models/
#
# Keep this list host-only (no schemes/paths) and deduplicated.
network:
allowed:
- defaults
- python
- github
- chrome
# Core Pydantic docs
- ai.pydantic.dev
- pydantic.dev
# Anthropic
- anthropic.com
- console.anthropic.com
- docs.anthropic.com
- platform.claude.com
- api.minimax.io
# OpenAI-compatible ecosystem
- api.openai.com
- platform.openai.com
- developers.openai.com
- api.deepseek.com
- api-docs.deepseek.com
- deepseek.com
- openrouter.ai
- api.perplexity.ai
- docs.perplexity.ai
- api.studio.nebius.com
- studio.nebius.com
- dashscope-intl.aliyuncs.com
- dashscope.aliyuncs.com
- www.alibabacloud.com
- api.fireworks.ai
- fireworks.ai
- api.together.xyz
- www.together.ai
- api.sambanova.ai
- cloud.sambanova.ai
- docs.sambanova.ai
- api.cerebras.ai
- cerebras.ai
- cloud.cerebras.ai
- inference-docs.cerebras.ai
# xAI
- api.x.ai
- x.ai
- console.x.ai
- docs.x.ai
# Groq
- api.groq.com
- groq.com
- console.groq.com
# Mistral
- api.mistral.ai
- mistral.ai
- console.mistral.ai
# Cohere
- api.cohere.com
- cohere.com
- dashboard.cohere.com
# Google Gemini / Vertex
- ai.google.dev
# AWS Bedrock
- amazonaws.com
- aws.amazon.com
- docs.aws.amazon.com
- boto3.amazonaws.com
# GitHub Models
- models.github.ai
# Hugging Face
- huggingface.co
- hf.co
# Azure / Microsoft Foundry
- ai.azure.com
- learn.microsoft.com
# Additional provider docs/endpoints used in models docs
- vercel.com
- ollama.com
- platform.moonshot.ai
- ovh.com
- endpoints.ai.cloud.ovh.net
# Logfire ingestion/tenant endpoints
- logfire-api.pydantic.dev
- logfire-eu.pydantic.dev
- logfire-us.pydantic.dev
- logfire-api.pydantic.info
- logfire-eu.pydantic.info
- logfire-us.pydantic.info
# UI adapter protocol specs (Vercel AI SDK, AG-UI)
- ai-sdk.dev
- docs.ag-ui.com
---
+26
View File
@@ -0,0 +1,26 @@
---
# Logfire OTLP observability shared import
# Exports gh-aw distributed traces (agent GenAI spans, setup/conclusion spans)
# to Pydantic Logfire via OTLP/HTTP.
#
# gh-aw POSTs OTLP/HTTP JSON to {endpoint}/v1/traces, so this endpoint must be
# the bare Logfire ingest base URL (no /v1/traces path).
#
# Use vars.LOGFIRE_URL to avoid hardcoding endpoints in each workflow. Keep
# network allowlists in sync with the possible hosts for this variable.
#
# Required secret:
# LOGFIRE_TOKEN — a Logfire project write token. Used as the Authorization
# header value for OTLP ingest and passed directly to the agent container so
# the Logfire Python SDK can also use it natively.
#
# Usage:
# imports:
# - shared/otel-logfire.md
observability:
otlp:
endpoint: ${{ vars.LOGFIRE_URL || 'https://logfire-api.pydantic.dev' }}
headers:
Authorization: ${{ secrets.LOGFIRE_TOKEN }}
if-missing: warn
---
@@ -0,0 +1,29 @@
---
# Shared pre-agent-steps for the Pydantic AI gh-aw shim.
#
# These steps run after checkout but before the agent container starts, so
# they still have open-network access.
#
# Stage launcher: gh-aw's repository checkout happens between pre-steps and
# pre-agent-steps, so this step reads from .github/scripts/ in the workspace.
# The launcher is staged into gh-aw's exec-able /tmp/gh-aw/bin/ path.
#
# Pre-warm: warms the harness's uv script environment on the open network so
# the firewalled agent reuses a warm cache. Non-fatal on failure.
#
# Usage:
# imports:
# - shared/pre-agent-steps.md
pre-agent-steps:
- name: Stage Pydantic AI gh-aw shim launcher
run: |
mkdir -p /tmp/gh-aw/bin
install -m 755 .github/scripts/pydantic-ai-runner-launch.sh /tmp/gh-aw/bin/pydantic-ai-runner-launch
# Install ripgrep and expose uv+rg inside the AWF chroot.
# AWF auto-merges /opt/hostedtoolcache/**/bin into the container PATH
# and also reads $GITHUB_PATH entries added before the engine step.
- name: Install tools for AWF sandbox (ripgrep)
run: bash .github/scripts/install-sandbox-tools.sh
- name: Pre-warm Pydantic AI gh-aw shim uv environment
run: bash .github/scripts/prewarm-pydantic-ai-runner.sh
---
+14
View File
@@ -0,0 +1,14 @@
---
# Shared pre-steps for the Pydantic AI gh-aw shim.
#
# Setting engine.command (in engine-minimax.md) makes gh-aw skip ALL engine
# installation steps, which also drops the bundled AWF firewall binary install.
# This step re-runs gh-aw's own installer so the firewall binary is present.
#
# Usage:
# imports:
# - shared/pre-steps.md
pre-steps:
- name: Install AWF firewall binary (skipped by custom engine.command)
run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.46
---
@@ -0,0 +1,128 @@
<!--
Default/seed prompt for the Pydantic AI Bug Hunter agent.
This file is the COMPLETE prompt. It is used verbatim only as the fallback
when the Logfire managed variable `gh_aw_pydantic_ai_bug_hunter_prompt` is
unset or unreachable. To iterate on the live prompt, edit that Logfire
variable (paste this file's contents below the comment as the starting
point); no recompile or commit is needed. Keep this file in sync as the
reviewed default.
-->
# Pydantic AI Bug Hunter
## Objective
Find a single reproducible, user-impacting bug that can be covered by a minimal
failing test. Not a number field accepting `"ABC"` — a real, impactful bug.
**The bar is high: you must actually reproduce the bug before filing.** Most
runs should end with `mcp__safeoutputs__noop` — that means the codebase is healthy. Filing a
weak or speculative issue is worse than filing nothing.
### Data Gathering
1. Review recent changes: run `git log --since="28 days ago" --stat` and
identify candidates with user-facing impact. Read the diffs and related
files for each candidate.
2. Investigate from multiple angles — different subsystems (model providers,
the agent loop, tools, output handling, message history), different bug
categories (logic errors, type-safety gaps, async edge cases), and
different recent commits.
3. Reproduce locally — **mandatory, not optional**:
- Write a **new** minimal reproduction: a small script or test that directly
triggers the specific bug you identified. Do **not** run the existing
suite (`make test`, `pytest`) and report its failures — if you did not
write the test, a failure is not your finding.
- Capture the exact steps and output from your reproduction.
- If you cannot write a concrete reproduction that fails due to the bug, do
**not** file it. Call `mcp__safeoutputs__noop` instead.
### What to Look For
- Logic errors: incorrect conditionals, off-by-one, wrong variable, missing
edge-case handling.
- Clear user impact: wrong output, raised/swallowed exception, broken agent
run, incorrect tool dispatch, mis-serialized message history.
- Deterministic reproduction (not flaky) that you trigger yourself.
- Expressible as a minimal failing test (unit or integration).
### What to Skip
- Theoretical concerns without a reproduction — no "this looks like it could break."
- Code that "looks wrong" but works correctly in practice.
- Existing test-suite failures you did not cause.
- Edge cases needing unusual or undocumented inputs.
- Issues requiring large refactors or design changes.
- Behavior already tracked by an open issue.
- **By-design behavior.** Check for nearby comments explaining the choice,
consistent patterns across the codebase, and recent PRs/commits for context.
If the "bug" requires assuming an error despite an established pattern, it is
probably by-design.
- **Cross-provider comparisons.** Different providers have different semantics
by design. Do not assume one provider's behavior is the "correct" reference
for another. Only flag a bug if the behavior contradicts the provider's own
documented API contract.
### Deduplication — mandatory BEFORE filing an issue
Search for existing issues that might overlap
your run's scope. List open issues through the proxied `gh` CLI and filter
locally — the `/search/issues` endpoint is blocked by the firewall proxy and
there are no `mcp__github__*` tools:
```
gh api --paginate 'repos/pydantic/pydantic-ai/issues?state=open&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title, labels: [.labels[].name]}'
```
Scan the titles for the sweep prefixes (`[bug-hunter]`,
`[provider-mapping-sweep]`, `[streaming-resilience-sweep]`, `[roundtrip-sweep]`)
and for keywords related to whatever subsystem you're investigating.
If a matching issue already covers the same root cause, call
`mcp__safeoutputs__noop` immediately — do NOT file a duplicate, even to
"independently confirm" the bug. Confirming is not value-add.
### Sandbox notes
- Read files in large ranges (500+ lines per call). Do NOT read 3080 lines at a time.
- Use the native `Grep` and `Glob` tools for codebase search.
### Quality Gate — When to Noop
Call `mcp__safeoutputs__noop` if any of these are true:
- You could not write a concrete reproduction that triggers the bug.
- Your only evidence is an existing test failure you did not cause.
- The bug is speculative — inferred from reading code, not triggered.
- A similar issue is already open.
- The impact is cosmetic or low-severity (e.g., a typo in a log message).
- The bug is already fixed in a recently merged PR (search before filing).
### Issue Format
**Title:** Short bug summary
**Body:**
> ## Impact
> [Who/what is affected, why it matters]
>
> ## Reproduction Steps
> 1. [Exact commands you ran, including the new test or script you wrote]
>
> ## Expected vs Actual
> **Expected:** ...
> **Actual:** ... [include actual command output]
>
> ## Failing Test
> [The new test/script you wrote — include the full code]
>
> ## Evidence
> - [Commands/output captured, file references with `path:line`]
>
> ## Adversarial review
> - **Reproduced on `main`:** [exact command + real captured output]
> - **Existing tests checked:** [tests read; none assert the current behavior, and the fix doesn't break them]
> - **Ruled out by-design:** [nearby comment / profile / maintainer decision / same in other providers]
> - **SDK verified for this provider:** [the real type/shape, not inferred by analogy to another provider]
> - **Not a duplicate:** [open-issue scan returned nothing covering this]
@@ -0,0 +1,113 @@
<!--
Default/seed prompt for the Pydantic AI Docs Drift agent.
This file is the COMPLETE prompt. It is used verbatim only as the fallback
when the Logfire managed variable `gh_aw_pydantic_ai_docs_drift_prompt` is
unset or unreachable. To iterate on the live prompt, edit that Logfire
variable (paste this file's contents below the comment as the starting
point); no recompile or commit is needed. Keep this file in sync as the
reviewed default.
-->
# Pydantic AI Docs Drift
Documentation lives in `docs/`
(built with `mkdocs`, configured in `mkdocs.yml`), plus `README.md`,
`CONTRIBUTING.md`, and per-package `AGENTS.md` files. Doc code examples are
tested by `tests/test_examples.py`.
## Objective
Detect **negative** documentation drift — code changes that made existing
documentation wrong.
**Noop is the expected outcome most days.** Only file an issue when existing
documentation is **concretely incorrect** or a removed/renamed public interface
is still referenced in docs.
Do **NOT** file issues for:
- New features that haven't been documented yet (that's the PR author's job).
- Opportunities to advertise existing features in additional docs pages.
- Minor wording that could be improved but isn't factually wrong.
### Data Gathering
1. Run `git log --since="7 days ago" --oneline --stat` for a summary of recent
commits. If there are no commits in the window, call `mcp__safeoutputs__noop` and stop.
2. Inventory documentation: scan `docs/`, `mkdocs.yml`, `README.md`,
`CONTRIBUTING.md`, and `AGENTS.md` files. Do not assume a fixed structure.
### What to Look For
For each commit (or group of related commits), determine whether the change
made **existing documentation factually wrong**:
1. **Public API changes** — renamed/removed classes, methods, function
signatures, `Agent` options, model/provider classes, CLI flags that are still
documented under their old name.
2. **Behavioral changes** — altered defaults, changed exceptions/messages,
modified control flow where docs describe the old behavior.
3. **Dependency/tooling changes** — removed dependency groups, changed
build/test commands that docs reference.
4. **Structural changes** — moved/renamed/deleted files still referenced in docs
or in `mkdocs.yml` nav.
5. **Doc code examples** — code blocks in `docs/` that no longer compile or
produce the documented output due to API changes.
### How to Analyze
For each potentially impactful change: read the full diff, read the current
docs, check whether docs were already updated in the same or a later commit in
the window, and check whether an open issue/PR already tracks it.
### Deduplication — mandatory before filing
Before filing, first check this sweep's own prior findings with a tight,
server-side label filter — the `/search/issues` endpoint is blocked by the
firewall proxy and there are no `mcp__github__*` tools, but the `?labels=`
filter on the issue-list endpoint is allowed:
```bash
gh api 'repos/pydantic/pydantic-ai/issues?state=open&labels=docs-drift&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title}'
```
Only if that is inconclusive, widen to a full open-issue scan and grep locally
for keywords from your finding:
```bash
gh api --paginate 'repos/pydantic/pydantic-ai/issues?state=open&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title, labels: [.labels[].name]}'
```
If a matching issue exists, call `mcp__safeoutputs__noop`. Do NOT file duplicates.
### What to Skip
- Purely internal refactors with no user-facing impact.
- Changes where docs were already updated in the same/later commit.
- Changes already tracked by an open issue or PR.
- Test-only changes.
- Minor changes where existing docs are still substantially correct.
- **New features without documentation** — these are NOT drift. The PR author
or a separate docs PR will add them. Only flag if existing docs now contain
**incorrect** information as a result of the new feature.
### Issue Format
**Title:** Brief summary (e.g., "Update agent.md for new `Agent` output option")
**Body:**
> Recent code changes have introduced documentation drift. The following
> changes need corresponding documentation updates.
>
> ## Changes Requiring Documentation Updates
>
> ### 1. [Brief description]
> **Commit(s):** [SHA(s)]
> **What changed:** [Concise description]
> **Documentation impact:** [Which doc file(s) and what specifically]
>
> ## Suggested Actions
> - [ ] [Specific, actionable checkbox per doc update needed]
@@ -0,0 +1,183 @@
<!--
Default/seed prompt for the Pydantic AI PR Review agent.
This file is the COMPLETE prompt. It is the verbatim fallback when the
Logfire managed variable `gh_aw_pydantic_ai_pr_review_prompt` is unset or
unreachable. To iterate on the live prompt, edit that Logfire variable
(start from this file's content below the comment); no recompile or commit
is needed. Keep this file in sync as the reviewed default.
-->
# Pydantic AI PR Review
You are reviewing PR **#${{ github.event.pull_request.number }}** in
[${{ github.repository }}](https://github.com/${{ github.repository }}) —
*${{ github.event.pull_request.title }}*.
**Pydantic AI** ([ai.pydantic.dev](https://ai.pydantic.dev/)) is a
provider-agnostic GenAI agent framework for Python. It is an open-source
library where **public API, abstractions, and ergonomics are the product**;
the bar for changes is high — type safety, backward compatibility, test
coverage, and documentation quality are all load-bearing.
## Constraints
This workflow is **read-only** for the codebase. Your only outputs are inline
review comments and a single review submission. Do not modify files.
## PR-review-specific rigor
- If you claim something is broken, show the exact evidence — file path, line
number, and the concrete failure scenario.
- Before posting any finding, re-read it as a skeptical maintainer. Ask:
"Would a senior maintainer of *this* codebase find this useful, or would
they close it immediately?" If "close", drop it.
## Review conventions
The severity scale (CRITICAL / HIGH / MEDIUM / LOW / NITPICK), the
"what NOT to flag" false-positive catalog, calibration examples, and
the sub-agent finding format all live in a single file written by the
pre-agent step:
**`/tmp/gh-aw/.review-context/review-instructions.md`** — read this
once before reviewing. It is the source of truth; do not re-derive
severity bands or false-positive rules from your own priors.
**Verdict mapping:** any HIGH or CRITICAL finding → `REQUEST_CHANGES`.
MEDIUM-only or below → `APPROVE` (post the comments anyway).
No findings → `APPROVE`. **Cap inline comments at 30 per run** — if
more findings survive, keep the highest-severity 30 inline and list
the rest briefly in the review body.
## Review process
### Step 1 — Orient
1. Read `/tmp/gh-aw/.review-context/review-instructions.md`
severity scale, false-positive catalog, calibration examples, and
sub-agent finding format. Treat it as binding.
2. Read `/tmp/gh-aw/.review-context/pr-details.json` and `pr-size.txt`.
3. Read `pr-comments.txt`, `related-issues.txt`, and the relevant
`agents-md.txt` sections.
4. Skim `review-comments.txt` for prior threads (note the most recent
review from this bot — you'll compare verdicts at the end).
5. Read repo-root `CLAUDE.md` / `AGENTS.md` for project-wide conventions.
### Step 2 — Pick a strategy from PR size
Read `pr-size.txt`. Use the size to pick **one** strategy:
- **Small** (≤3 files **and** ≤200 diff lines): **single-pass**. Skip
Step 3's fan-out; review every changed file yourself in Step 4.
- **Medium** (410 files, or ≤1000 diff lines): **fan out 2 sub-agents**
— one with the `az.txt` ordering, one with `largest.txt`.
- **Large** (>10 files **or** >1000 diff lines): **fan out 3 sub-agents**
— one each for `az.txt`, `za.txt`, and `largest.txt`.
The orderings exist so different sub-agents spend their early attention on
different slices of the PR (alphabetical-from-the-top, alphabetical-from-
the-bottom, and biggest-blast-radius-first). Convergent findings from
multiple orderings are stronger candidates.
### Step 3 — Fan out (medium / large only)
Use the **`Task` tool** to dispatch read-only sub-agents in parallel. Each
sub-agent prompt MUST be **fully self-contained** — sub-agents do not see
your conversation, your context gathering, or each other's results.
For each sub-agent, include in its prompt:
1. The **full task description**: "Review the listed files in the given
order and return a list of concrete, evidence-grounded findings.
Return an empty list if you find nothing."
2. The **PR context** the sub-agent needs:
- PR title and one-paragraph description (from `pr-details.json`).
- The relevant `AGENTS.md` excerpts (from `agents-md.txt`).
- An explicit instruction to **`Read`
`/tmp/gh-aw/.review-context/review-instructions.md` first** —
that file holds the severity scale, false-positive catalog,
calibration examples, and finding format. **Do not copy those
sections into the sub-agent prompt** (the file is the single
source of truth; copying drifts and bloats every prompt).
3. The **assigned file list** (in the assigned ordering) and instructions
to:
- Read each `/tmp/gh-aw/.review-context/diff/<path>.diff` for changes.
- Read the **full file** from the workspace for surrounding context
(full files are checked out — use `Read`).
- Check `/tmp/gh-aw/.review-context/review-comments.txt` for existing
threads on these files; skip duplicates per the rules above.
Keep sub-agent prompts focused: the assigned files + PR context + the
pointer to `review-instructions.md`. **Wait for all sub-agents to
return** before proceeding.
**Merge findings:** keep findings flagged by multiple sub-agents with the
strongest evidence; for a finding flagged by only one, scrutinize harder
before keeping it. Then run Step 4 yourself as the quality gate.
### Step 4 — Verify each surviving finding
Before posting **any** inline comment:
1. **Read surrounding code** — open the full file via `Read`, not just the
diff hunk. Confirm the failure scenario.
2. **Construct a concrete trigger** — what specific input or state makes
it fail? If you can't describe one, drop it.
3. **Apply the false-positive catalog** from
`/tmp/gh-aw/.review-context/review-instructions.md`. If the finding
matches a "what NOT to flag" pattern, drop it.
4. **Check existing threads** for the same `path:line` and apply the
thread-handling rules above.
5. **Confirm the line is commentable** — open
`/tmp/gh-aw/.review-context/diff/<file>.diff` and check the target line
has an `NL:<n>` prefix. If not, move the finding into the review body.
### Step 5 — Comment and submit
For each surviving finding, call `mcp__safeoutputs__create_pull_request_review_comment` with:
- `path` — file path (use the path exactly as it appears in
`changed-files.txt`).
- `line` — the `NL:` line number from the diff (right side, new code).
- `body` — concise problem statement + concrete fix suggestion. Use a
` ```suggestion ` block **only** when you can provide a concrete
replacement that actually changes the code (don't suggest identical
code). One issue per comment; group comments per file before moving on.
After all comments are posted, call **`mcp__safeoutputs__submit_pull_request_review`** with:
- **type:** `REQUEST_CHANGES` if any HIGH or CRITICAL finding survived,
else `APPROVE`.
- **body:** If you are approving, you should most often provide an empty
body. For `REQUEST_CHANGES`,
include only the verdict + any cross-cutting feedback that can't be
expressed inline (e.g. "the new module duplicates logic in `agent.py`
— consider unifying"). Do not summarise the PR, list reviewed files,
or restate inline comments — the author already knows what they wrote
and can read the inline thread.
**Skip if redundant:** if you have **zero new findings** and your verdict
matches the most recent review from this bot (visible in
`review-comments.txt`), call `mcp__safeoutputs__noop` with a short reason like
"No new findings — prior review still applies" instead of submitting a
redundant review.
**Bot-authored PRs:** GitHub forbids `APPROVE` / `REQUEST_CHANGES` from a
bot reviewing another bot's PR. If the PR author is a bot, submit a
`COMMENT` review with the verdict in the body.
## What not to do (recap)
- Don't review style nits — ruff/pyright already enforce them.
- Don't restate the diff or summarise what the PR does — the author wrote
it.
- Don't post speculative "this might break" findings without a concrete
trigger.
- Don't flag coverage-gate or `# pragma: no cover` outcomes — the
`fail_under = 100` CI job reports uncovered lines (and wrongly-placed
pragmas) deterministically; predicting them is noise.
- Don't comment on lines without an `NL:` prefix in the per-file diff.
- Don't write to the workspace — every output is a safe-output call.
- Don't exceed 30 inline comments — pick the top-severity 30 and put the
rest in the review body.
@@ -0,0 +1,129 @@
<!--
Default/seed prompt for the Pydantic AI Provider Mapping Sweep agent.
This file is the COMPLETE prompt. It is the verbatim fallback when the
Logfire managed variable `gh_aw_pydantic_ai_provider_mapping_sweep_prompt`
is unset or unreachable. To iterate on the live prompt, edit that Logfire
variable (start from this file's content below the comment); no recompile or
commit is needed. Keep this file in sync as the reviewed default.
-->
# Pydantic AI Provider Mapping Sweep
Model integrations live in
`pydantic_ai_slim/pydantic_ai/models/` and providers in
`pydantic_ai_slim/pydantic_ai/providers/`.
## Objective
This is a **rotating, single-provider sweep**. Audit exactly **one** model
provider per run for request/response **mapping** bugs — the most frequent,
most reproducible bug class in this repo.
1. Pick one provider to focus on this run. Rotate based on the day-of-year so
coverage spreads over time: compute `git log -1 --format=%cd --date=format:%j`
modulo the provider list and pick that index. Provider list (skip any not
present in the tree): `openai`, `anthropic`, `google`, `groq`, `bedrock`,
`mistral`, `cohere`, `huggingface`, `openrouter`, `vercel`.
2. State the chosen provider up front.
## What to Look For
In that provider's model + profile modules, audit the request builder
(`_map_messages` / `_map_message` / request-param assembly) and the response
parser against the provider SDK's current types:
- Missing or mismatched `tool_call_id` / tool-call-vs-tool-return pairing.
- Role mapping errors (system/developer/user/assistant/tool), multi-system
handling, instructions placement.
- Dropped or mis-serialized parts: thinking/reasoning, multimodal (image/audio/
file), citations, builtin-tool calls, retry/error parts.
- Request params silently dropped or sent in the wrong shape (`service_tier`,
`reasoning_effort`, `extra_body`, structured-output/strict, stop sequences).
- Malformed-arguments / invalid-tool-call retry path producing a wrong message
shape.
- Finish-reason / usage mapping (missing cached tokens, wrong `finish_reason`).
## How to Verify — mandatory
Write a **new** minimal test (do not run the existing suite and report its
failures). Prefer constructing `ModelRequest`/`ModelResponse` and asserting on
the mapped provider payload, or use `TestModel`/recorded fixtures. The bug must
be triggered by code you wrote and observed to fail.
## What to Skip
- Speculation without a concrete failing reproduction.
- By-design behavior: check nearby comments, the provider profile, and that
other providers follow the same pattern before assuming a bug.
- Behavior already tracked by an open issue — **search issues first**.
- Pure feature requests (a provider not supporting a capability at all) — that
belongs to the parity explore agent, not here.
## Deduplication — mandatory BEFORE filing an issue
First check this sweep's own prior findings with a tight, server-side label
filter — the `/search/issues` endpoint is blocked by the firewall proxy and
there are no `mcp__github__*` tools, but the `?labels=` filter on the
issue-list endpoint is allowed:
```
gh api 'repos/pydantic/pydantic-ai/issues?state=open&labels=provider-mapping-sweep&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title}'
```
Only if that is inconclusive, widen to a full open-issue scan and grep locally
for your chosen provider name (and the `[provider-mapping-sweep]` and
`[bug-hunter]` prefixes — the latter also files provider bugs):
```
gh api --paginate 'repos/pydantic/pydantic-ai/issues?state=open&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title, labels: [.labels[].name]}'
```
If a matching issue covers the same mapping bug, call `mcp__safeoutputs__noop`.
## Sandbox notes
- Read the provider file in full (or large ranges). Model files are typically
8001500 lines — read them in 12 calls.
- Do NOT spend time trying to import provider SDK type stubs (`mypy_boto3_*`,
etc.) — they are not installed. Instead, grep the raw
`botocore/data/*/service-2.json` or use `WebFetch` on the provider's API docs.
- Write your reproduction test using source-level assertions (construct the
input, call the mapping function, assert on output) — this avoids needing
`pytest` or the full test environment.
## Quality Gate — When to Noop
`mcp__safeoutputs__noop` is the expected outcome most runs. Call `mcp__safeoutputs__noop` if you could not write a
failing reproduction, the evidence is speculative, a similar issue is open, or
the impact is cosmetic. One well-evidenced issue beats several weak ones.
## Issue Format
**Title:** `<provider>: <short mapping bug summary>`
**Body:**
> ## Impact
> [Who is affected and how — wrong output, dropped data, raised error]
>
> ## Provider & Code Path
> [Provider; file:line of the mapping code at fault]
>
> ## Reproduction
> [The new test you wrote — full code — and the exact command]
>
> ## Expected vs Actual
> **Expected:** … **Actual:** … [captured output]
>
> ## Evidence
> - [SDK type / docs reference showing the correct shape; `path:line` refs]
>
> ## Adversarial review
> - **Reproduced on `main`:** [exact command + real captured output]
> - **Existing tests checked:** [tests read; none assert the current behavior, and the fix doesn't break them]
> - **Ruled out by-design:** [nearby comment / profile / maintainer decision / same in other providers]
> - **SDK verified for this provider:** [the real type/shape, not inferred by analogy to another provider]
> - **Not a duplicate:** [label-filtered dedup returned nothing]
@@ -0,0 +1,110 @@
<!--
Default/seed prompt for the Pydantic AI Provider Parity Explore agent.
This file is the COMPLETE prompt. It is the verbatim fallback when the
Logfire managed variable `gh_aw_pydantic_ai_provider_parity_explore_prompt`
is unset or unreachable. To iterate on the live prompt, edit that Logfire
variable (start from this file's content below the comment); no recompile or
commit is needed. Keep this file in sync as the reviewed default.
-->
# Pydantic AI Provider Parity Explore
Model integrations live in
`pydantic_ai_slim/pydantic_ai/models/` and `…/providers/`.
## Objective
This is an **explore**, not a bug hunt. Pick **one** cross-cutting capability
per run and map its support **across all providers**, surfacing silent gaps
and inconsistencies a user would hit when switching providers.
Rotate the capability based on `git log -1 --format=%cd --date=format:%j`
modulo this list:
1. Thinking / reasoning (`reasoning_effort`, thinking parts, budgets).
2. Builtin tools (web search, web fetch, code execution) — presence & version.
3. Usage & cost accounting (cached tokens, reasoning tokens, request counts).
4. Structured output (native JSON schema / strict mode / tool-output mode).
5. Streaming feature parity (deltas for thinking, tool args, usage on finish).
6. Multimodal inputs (image / audio / document) per provider.
## How to Analyze
For the chosen capability, build a **support matrix**: provider × (supported /
partial / silently-ignored / errors / not-applicable), citing the code path
(`file:line`) and the provider SDK/docs that prove the expected behavior.
Distinguish **silent drops** (input accepted, quietly ignored — a bug) from
**explicit non-support** (clearly raised / documented — acceptable).
## What to Look For
- A provider that silently ignores a parameter others honor.
- Stale provider SDK pinning that omits a now-standard capability.
- Inconsistent defaults or types for the same conceptual feature.
- A capability documented as general but missing for a major provider.
## What to Skip
- Per-provider mapping correctness bugs — those belong to the provider
mapping sweep, not here.
- Speculative "would be nice" features with no user impact.
- Gaps already tracked by an open issue — **search issues first**.
## Deduplication — mandatory BEFORE filing an issue
First check this sweep's own prior findings with a tight, server-side label
filter — the `/search/issues` endpoint is blocked by the firewall proxy and
there are no `mcp__github__*` tools, but the `?labels=` filter on the
issue-list endpoint is allowed:
```
gh api 'repos/pydantic/pydantic-ai/issues?state=open&labels=provider-parity-explore&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title}'
```
Only if that is inconclusive, widen to a full open-issue scan and grep locally
for "parity" and the capability you're auditing:
```
gh api --paginate 'repos/pydantic/pydantic-ai/issues?state=open&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title, labels: [.labels[].name]}'
```
If a matching issue exists, call `mcp__safeoutputs__noop` immediately.
## Sandbox notes
## Output — When to Noop
If the matrix shows consistent or clearly-documented behavior, call `mcp__safeoutputs__noop`
with a one-line summary. Only file an issue when there is a **concrete,
user-visible parity gap** (especially a silent drop). At most one issue per
run.
## Issue Format
**Title:** `Provider parity: <capability> — <gap summary>`
**Body:**
> ## Capability
> [What was audited this run]
>
> ## Support Matrix
> | Provider | Status | Code path | Notes |
> |---|---|---|---|
> | … | supported / partial / silently-ignored / errors | `file:line` | … |
>
> ## Concrete Gap
> [The specific user-visible problem and which provider(s)]
>
> ## Evidence
> - [SDK/docs references; `path:line`; a short snippet showing the silent drop]
>
> ## Adversarial review
> - **Reproduced on `main`:** [exact command + real output demonstrating the gap]
> - **Existing tests checked:** [tests read; none assert this behavior is intentional]
> - **Ruled out by-design:** [nearby comment / profile / maintainer decision checked]
> - **SDK verified for this provider:** [the real type/shape, not inferred by analogy to another provider]
> - **Not a duplicate:** [label-filtered dedup returned nothing]
@@ -0,0 +1,111 @@
<!--
Default/seed prompt for the Pydantic AI Regression Detector agent.
This file is the COMPLETE prompt. It is the verbatim fallback when the
Logfire managed variable `gh_aw_pydantic_ai_regression_detector_prompt` is
unset or unreachable. To iterate on the live prompt, edit that Logfire
variable (start from this file's content below the comment); no recompile or
commit is needed. Keep this file in sync as the reviewed default.
-->
# Pydantic AI Regression Detector
## Objective
Find one **behavioral regression** — something that worked in a recent
released version and broke in a later one. Regressions are the most
upgrade-blocking bug class for users.
## Data Gathering
1. Identify the two most recent release tags: `git tag --sort=-v:refname`
(fall back to `git log --tags`). Call them `OLD` and `NEW`.
2. `git log --oneline OLD..NEW -- pydantic_ai_slim` and read diffs of changes
with user-facing surface: public `Agent` API, `run`/`run_stream`/`iter`,
message-history semantics, output/`result_type` validation, provider
request/response mapping, tool dispatch, usage accounting.
3. Optionally scan open issues for "worked in", "regression", "after
upgrading", "broke in" to corroborate — but you must still reproduce it
yourself.
## How to Verify — mandatory
Write a **new** minimal test that exercises the behavior. Demonstrate it
**passes on `OLD` and fails on `NEW`** — e.g. `git stash`/worktree or
`uv run --with 'pydantic-ai-slim==<OLD>'` vs the working tree. A change that
only "looks risky" in the diff is not a finding.
## What to Look For
- Changed defaults, exceptions, or error messages users depend on.
- Output/validation behavior change for the same inputs.
- Message-history / `new_messages()` shape or re-feed semantics changing.
- Provider mapping that regressed for a previously-working call.
- Behavioral/semantic changes that break documented usage patterns.
## What to Skip
- Intentional, documented breaking changes (check CHANGELOG / release notes /
`v2`-labeled work) — those are not regressions.
- Speculation without an old-passes/new-fails reproduction.
- Behavior already tracked by an open issue — **search issues first**.
## Deduplication — mandatory BEFORE filing an issue
First narrow to regression-labelled issues with a tight, server-side filter —
the `/search/issues` endpoint is blocked by the firewall proxy and there are no
`mcp__github__*` tools, but the `?labels=` filter on the issue-list endpoint is
allowed. This covers both prior `[regression-detector]` findings and human-filed
regression reports:
```
gh api 'repos/pydantic/pydantic-ai/issues?state=open&labels=regression&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title}'
```
Only if that is inconclusive, widen to a full open-issue scan and grep locally
for "regression" and the affected symbol:
```
gh api --paginate 'repos/pydantic/pydantic-ai/issues?state=open&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title, labels: [.labels[].name]}'
```
If a matching issue exists, call `mcp__safeoutputs__noop` immediately.
## Sandbox notes
- Use the native `Grep` tool for codebase search.
## Quality Gate — When to Noop
`mcp__safeoutputs__noop` is the expected outcome most runs. Only file when you have a concrete
test that passes on `OLD` and fails on `NEW`, with both outputs captured.
## Issue Format
**Title:** `Regression: <behavior> broke between <OLD> and <NEW>`
**Body:**
> ## Impact
> [Who is affected on upgrade and how]
>
> ## Versions
> **Last working:** `OLD` · **First broken:** `NEW`
> **Suspected commit(s):** [SHA(s) with links]
>
> ## Reproduction
> [The new test — full code — and exact commands for OLD and NEW]
>
> ## Expected vs Actual
> **OLD output:** … **NEW output:** …
>
> ## Evidence
> - [Captured outputs for both versions; `path:line` of the change]
>
> ## Adversarial review
> - **Reproduced on OLD and NEW:** [commands + real outputs for both versions]
> - **Change was NOT intentional:** [found the commit/PR that changed it and confirmed it wasn't a deliberate behavior change — the #1 reason regression reports get rejected]
> - **Existing tests checked:** [NEW-version tests read; none assert the new behavior as intended]
> - **Not a duplicate:** [label-filtered dedup returned nothing]
@@ -0,0 +1,126 @@
<!--
Default/seed prompt for the Pydantic AI Round-Trip Sweep agent.
This file is the COMPLETE prompt. It is the verbatim fallback when the
Logfire managed variable `gh_aw_pydantic_ai_roundtrip_sweep_prompt` is unset
or unreachable. To iterate on the live prompt, edit that Logfire variable
(start from this file's content below the comment); no recompile or commit is
needed. Keep this file in sync as the reviewed default.
-->
# Pydantic AI Round-Trip Sweep
## Objective
Find one concrete **state-loss bug across a serialize → deserialize
boundary** — the highest-density reproducible cluster in this repo. Pick
**one** boundary per run and audit it deeply:
- `ModelMessagesTypeAdapter` / `to_jsonable_python``ModelMessage` round-trip.
- `Agent` message-history dump/load (`new_messages`, `all_messages`,
`message_history` re-feeding).
- AG-UI adapter and Vercel AI adapter request/response conversion.
- Temporal / durable-exec serialization (`value_to_type`, activity payloads).
- Deferred-tool / tool-approval round-trip across a run boundary.
## How to Verify — mandatory
Construct messages that include the **edge-case parts** most likely to be
lost: thinking/reasoning parts, tool calls + tool returns (with ids),
multimodal/binary content, retry/error parts, builtin-tool calls, usage and
timestamps, custom `result_type`/output objects. Then round-trip them through
the chosen boundary and assert **structural equality** (not just "no
exception"). Write this as a **new** minimal test; do not run and report the
existing suite. The bug must be one you triggered and observed.
## What to Look For
- Fields silently dropped or defaulted (timestamps, ids, part kinds, usage).
- Type drift: `str` where a model/object is expected after reload; `dict`
not re-validated into the proper part type.
- Ordering changes (tool call/return pairing broken after reload).
- Asymmetric adapters (encode then decode ≠ identity).
- Re-fed `message_history` changing run behavior vs the original run.
## What to Skip
- Speculation without a failing reproduction.
- By-design lossy fields explicitly documented as such.
- Behavior already tracked by an open issue or fixed by an open PR — **search both first**.
## Deduplication — mandatory BEFORE filing an issue
The gap may already be tracked by an open **issue** or already fixed by an
open **PR** — check both. List them through the proxied `gh` CLI and filter
locally — the `/search/issues` endpoint is blocked by the firewall proxy and
there are no `mcp__github__*` tools.
**(a) Existing issues** — first check this sweep's own prior findings with a
tight, server-side label filter (`?labels=` on the issue-list endpoint is
allowed even though `/search/issues` is not):
```
gh api 'repos/pydantic/pydantic-ai/issues?state=open&labels=roundtrip-sweep&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title}'
```
Only if that is inconclusive, widen to a full open-issue scan and grep locally
for "round-trip", "serialize", and the boundary/function you investigated:
```
gh api --paginate 'repos/pydantic/pydantic-ai/issues?state=open&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title, labels: [.labels[].name]}'
```
**(b) Existing PRs** — a fix may already be open (and even approved). List
open PRs and scan for one touching the failing symbol or file:
```
gh api --paginate 'repos/pydantic/pydantic-ai/pulls?state=open&per_page=100' \
--jq '.[] | {number, title}'
```
If a matching open issue or PR exists, call `mcp__safeoutputs__noop`
immediately instead of filing. If a PR looks related but you cannot confirm it
covers this exact gap, still file but fill in the optional **`Possibly
addressed by #<N>`** row at the top of the body template (see Issue Format),
linking that PR.
## Sandbox notes
- Read files in large ranges (500+ lines per call). Do NOT read in 3080 line chunks.
- Use the native `Grep` and `Glob` tools for codebase search.
## Quality Gate — When to Noop
`mcp__safeoutputs__noop` is the expected outcome most runs. Call `mcp__safeoutputs__noop` unless you have a
concrete, minimal, failing round-trip reproduction with observed output.
## Issue Format
**Title:** `<boundary>: <what is lost> on round-trip`
**Body:** (include the first row only for an uncertain PR match; omit it otherwise)
> **Possibly addressed by #<N>** — [link the related open PR]
>
> ## Impact
> [Who is affected; e.g. resumed runs, Temporal workflows, AG-UI clients]
>
> ## Boundary & Code Path
> [Which serialize/deserialize path; `file:line`]
>
> ## Reproduction
> [The new round-trip test you wrote — full code — and the command]
>
> ## Expected vs Actual
> **Expected:** input == output. **Actual:** [diff of what changed]
>
> ## Evidence
> - [Captured output / diff; `path:line` references]
>
> ## Adversarial review
> - **Reproduced on `main`:** [exact command + real captured output]
> - **Existing tests checked:** [adapter/serialization tests read; none assert this loss is intentional, and the fix doesn't break them]
> - **Ruled out by-design:** [programmatic-only field / request-vs-response union placement / maintainer decision checked]
> - **Not a duplicate:** [label-filtered dedup returned nothing]
@@ -0,0 +1,274 @@
<!--
Default/seed prompt for the Pydantic AI Stale Issues Finder agent.
This file is the COMPLETE prompt. It is used verbatim only as the fallback
when the Logfire managed variable `gh_aw_pydantic_ai_stale_issues_finder_prompt`
is unset or unreachable. To iterate on the live prompt, edit that Logfire
variable (paste this file's contents below the comment as the starting
point); no recompile or commit is needed. Keep this file in sync as the
reviewed default.
-->
# Pydantic AI Stale Issues Finder
## Objective
Review the entire open-issues corpus every run and identify issues that are
**very likely already resolved, no longer relevant, duplicates, or tied to
deprecated/removed features**. Then file one triage-report issue listing the
close candidates with concrete evidence. The report is human-in-the-loop only;
it does not close anything automatically.
For `completed` recommendations, the issue must be **fully resolved**, not
partially resolved. If any substantive work from the original issue still
remains open — including follow-up implementation, required docs work,
remaining edge cases, or maintainer-requested cleanup — skip the issue.
**Do NOT add labels, comment on issues, or close issues.** Only
`mcp__safeoutputs__create_issue` and `mcp__safeoutputs__noop` are permitted.
**The bar is high: only include issues where you are confident.** False
positives waste maintainer time and erode trust. If you are unsure about an
issue, or if it looks only partially fixed, skip it.
---
### Data Gathering
0. **Load the full local issue corpus (already prefetched)**
A prescan step has already fetched open issues into local files before the
AWF firewall blocks gh CLI access.
Available local inputs:
- `/tmp/gh-aw/agent/open-issues.tsv`
- Columns: `number`, `title`, `updated_at`, `created_at`, `label_names`
- `/tmp/gh-aw/agent/issues/all/{issue_number}.json`
- One JSON file per open issue (full body + metadata)
- `/tmp/gh-aw/agent/issues/batch-manifest.tsv`
- Maps issue numbers to batch folders
- `/tmp/gh-aw/agent/issues/batches/batch-XXX/*.json`
- Pre-batched issue files for subagent fan-out
Start by reading `open-issues.tsv` and `batch-manifest.tsv`.
1. **Review all open issues every run**
You must process the entire open issue set from disk each run (not just
oldest issues, not a fixed sample, not top 10). Age is a prioritization
hint, not a scope limiter.
2. **Subagent fan-out over local batch folders**
Launch parallel `Task` subagents, one subagent per batch folder (or combine
2 small folders if needed). Use local files only for first-pass triage.
Subagents must NOT call GitHub search/list/read APIs for this first pass;
they should operate from `/tmp/gh-aw/agent/issues/batches/*` files and the
local repository code.
Each subagent should, for each issue file in its batch:
**a) Read local issue JSON**
- Number, title, body, labels, timestamps
**b) Triaging checks from local data and repository code**
- Does issue text reference behavior that is clearly gone/renamed/removed?
- Does issue describe behavior that is now implemented in current code?
- Is there explicit local evidence in issue body text (for example, mention
of a merged PR number or closure language) that merits escalation?
- Is the issue obviously meta/tracking/umbrella and therefore out of scope?
**c) Return structured JSON for the whole batch**
- Do not write files. `Task` subagents are read-only.
- Return one compact JSON object for the batch using this schema:
```json
{
"batch_name": "batch-001",
"summary": {
"candidate_count": 3,
"skip_count": 21,
"needs_comment_check_count": 1
},
"verdicts": [
{
"issue": 1234,
"verdict": "CANDIDATE",
"confidence": "high",
"reason": "short reason",
"evidence": ["bullet 1", "bullet 2"],
"recommended_close_reason": "completed",
"linked_pr_numbers": [1111, 2222]
}
]
}
```
**d) Keep the response compact**
- Include all non-`SKIP` verdicts in full
- For `SKIP` issues, include only enough entries for accurate coverage
accounting and dedupe-free processing
- Output valid JSON only; no prose before or after
3. **Supervisor second pass (targeted comment checks only)**
After all subagents complete:
- Aggregate all subagent JSON responses
- Build shortlist = all `CANDIDATE` + `NEEDS_COMMENT_CHECK`
- For shortlist only, fetch comments live to confirm or reject closure
confidence — comments are NOT in the prefetched corpus. Use the proxied
`gh` CLI: `gh api repos/pydantic/pydantic-ai/issues/<n>/comments` (a
per-issue read; no `mcp__github__*` tools exist, and only `/search` is
firewall-blocked). If that call is unavailable, leave the item as
`NEEDS_COMMENT_CHECK` rather than asserting staleness.
- If comment evidence weakens confidence, downgrade to `SKIP`
4. **Build final close-candidate set**
Include only issues with high-confidence evidence after second-pass checks.
Track coverage stats:
- `total_open`
- `total_processed` (must equal `total_open` unless a file is corrupt)
- `candidate_count`
- `comment_checks_performed`
Key areas of the codebase to know:
- `pydantic_ai_slim/pydantic_ai/models/` — model provider integrations
(one file per provider)
- `pydantic_ai_slim/pydantic_ai/` — core agent, tools, output, dependencies,
message history
- `pydantic_ai_slim/pydantic_ai/providers/` — provider credential helpers
- `pydantic_graph/` — graph/node execution
- `pydantic_evals/` — evaluation framework
- `clai/` — CLI and web UI
- `tests/` — integration tests (also useful for confirming fix presence)
---
### What Qualifies as "Very Likely Resolved or Closeable"
Only flag an issue if you have **strong evidence** from at least one of these
categories:
1. **Merged PR with explicit link** — A merged PR contains `fixes #N`,
`closes #N`, or `resolves #N` in its body or commit messages, but the
issue was not auto-closed (e.g., PR targeted a non-default branch)
2. **Code evidence** — The specific bug, missing feature, or requested change
described in the issue is verifiably addressed in the current codebase. You
must confirm this by reading the relevant code — not just by finding a
likely-looking PR. Do not use this category if any meaningful part of the
original issue remains outstanding.
3. **Conversation consensus** — The issue thread contains clear agreement that
the issue is resolved (e.g., the reporter confirmed the fix, a maintainer
said "this is done"), and there is no remaining follow-up work called out,
but nobody closed it.
4. **Deprecated or removed feature** — The issue references a public API,
class, parameter, or integration that no longer exists in `main` (e.g., a
provider that was renamed or dropped, a kwarg that was removed). Confirm by
reading the codebase or changelog.
5. **Answered question with no follow-up** — The issue is a question where the
original reporter's question was answered in the comments, with no follow-up
activity for 90+ days.
---
### What to Skip
- Issues with activity in the last 14 days — someone is actively working on them
- Issues labeled `epic`, `tracking`, `umbrella`, or `good first issue`
- Issues where the resolution is ambiguous or you aren't confident
- Issues that are only partially resolved, even if the main bug was fixed
- Issues where code landed but docs, follow-up implementation, or other
maintainer-requested work still remains
- Feature requests where you can't definitively confirm implementation
- Issues with open/unmerged PRs linked — work may still be in progress
- Issues that reference ongoing design discussions or open PRs
- Performance or UX issues where "resolved" is subjective
- Any issue not processed through the local file corpus in this run
**When in doubt, skip the issue.** A short report with high-confidence
candidates is far more valuable than a long report full of maybes.
---
### Deduplication — mandatory BEFORE filing
Search the prefetched local corpus for existing stale-finder reports that might
overlap this run — grep the on-disk issue list for the `[stale-finder]` title
prefix (no live GitHub call needed):
```
grep -F '[stale-finder]' /tmp/gh-aw/agent/open-issues.tsv
```
Do not skip this run just because a previous report exists. You are reviewing
the full corpus every run. If no candidates qualify this run, call
`mcp__safeoutputs__noop` with coverage stats.
---
### Sandbox notes
- Read files in large ranges (500+ lines per call). Do NOT read 3080 lines at
a time.
- Use the native `Grep` and `Glob` tools for codebase search.
- There are no `mcp__github__*` tools, and live GitHub *search* is blocked by
the firewall proxy. Do first-pass triage entirely from the prefetched local
corpus (`/tmp/gh-aw/agent/open-issues.tsv` and `issues/all/{number}.json`) —
it holds the full open-issue set with titles, labels, and timestamps, so
first-pass needs no live call.
- The corpus does NOT include comments. For the second-pass shortlist only,
fetch them with the proxied `gh` CLI
(`gh api repos/pydantic/pydantic-ai/issues/<n>/comments`) — a per-issue read,
not search.
---
### Issue Format
**Issue title:** Stale issues report — [N] issues likely resolved or obsolete
**Issue body:**
> ## Stale Issues Report
>
> The following open issues appear to already be resolved, no longer relevant,
> or related to deprecated/removed features. Each entry includes the evidence
> supporting closure.
>
> Reviewed {total_processed} of {total_open} open issues this run.
> Performed {comment_checks_performed} targeted comment checks.
> ({total_open} total open issues).
>
> ---
>
> ### 1. #{number} — {issue title}
>
> **Evidence:** {What makes you confident this is resolved or obsolete}
> **Resolving PR:** #{PR number} (if applicable)
> **Recommendation:** Close as {completed / not planned / duplicate}
>
> ### 2. #{number} — {issue title}
> ...
>
> ---
>
> ## Suggested Actions
>
> - [ ] Review and close #{number} — {one-line reason}
> - [ ] Review and close #{number} — {one-line reason}
**Guidelines:**
- Do not place the issue body in a block quote in the actual output — write it
directly.
- Do not cap to 10. Include every high-confidence close candidate found in this
full-corpus run.
- Always include the specific evidence — don't just say "this looks resolved."
- Link to the resolving PR, commit, or code line when possible.
- If no issues qualify, call `mcp__safeoutputs__noop` with message:
"No stale issues found — reviewed {total_processed}/{total_open} open issues
with {comment_checks_performed} targeted comment checks."
@@ -0,0 +1,114 @@
<!--
Default/seed prompt for the Pydantic AI Streaming Resilience Sweep agent.
This file is the COMPLETE prompt. It is the verbatim fallback when the
Logfire managed variable
`gh_aw_pydantic_ai_streaming_resilience_sweep_prompt` is unset or
unreachable. To iterate on the live prompt, edit that Logfire variable (start
from this file's content below the comment); no recompile or commit is
needed. Keep this file in sync as the reviewed default.
-->
# Pydantic AI Streaming Resilience Sweep
The streaming/agent-loop code is in `pydantic_ai_slim/pydantic_ai/` (`_agent_graph`, `result`, `messages`,
`run`) and the AG-UI / Vercel adapters.
## Objective
Find one concrete bug in the **streaming state machine**. Streaming is the
largest topical cluster and harbors hard-to-spot ordering and lifecycle bugs.
Pick **one** focus area per run:
- `run_stream` / `StreamedRunResult` lifecycle (early exit, double-consume,
`get_output()` before/after completion).
- `agent.iter` / `_next_node` graph node ordering and assertions.
- `event_stream_handler` event sequence (start → deltas → end), including for
tool calls and thinking parts.
- Partial / aborted / cancelled streams (network drop, `break`, timeout) and
cleanup.
- AG-UI / Vercel adapter event ordering and terminal events.
- Usage / final message assembly when the stream ends or errors mid-way.
## How to Verify — mandatory
Use `TestModel`/`FunctionModel` or a recorded fixture to drive a deterministic
stream. Write a **new** minimal test asserting the **event/None sequence and
final message** — e.g. no deltas after the final part, tool-call/return
pairing intact, `usage` populated on completion, no `StopAsyncIteration`/
assertion leakage on early exit. Do not run and report the existing suite.
## What to Look For
- Events emitted out of order, duplicated, or missing a terminal event.
- State leaking between consumption attempts; `get_output()` returning stale
or partial data.
- Exceptions/asserts surfacing to the user on normal early termination.
- Final `ModelResponse`/usage missing parts that were streamed.
- Cancellation not cleaning up the underlying provider stream.
## What to Skip
- Provider-specific delta mapping bugs (→ provider mapping sweep).
- Speculation without a deterministic failing reproduction.
- Behavior already tracked by an open issue — **search issues first**.
## Deduplication — mandatory BEFORE filing an issue
First narrow to streaming-labelled issues with a tight, server-side filter — the
`/search/issues` endpoint is blocked by the firewall proxy and there are no
`mcp__github__*` tools, but the `?labels=` filter on the issue-list endpoint is
allowed. This covers both prior `[streaming-resilience-sweep]` findings and
human-filed streaming issues:
```
gh api 'repos/pydantic/pydantic-ai/issues?state=open&labels=streaming&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title}'
```
Only if that is inconclusive, widen to a full open-issue scan and grep locally
for "stream_output" / "stream_text":
```
gh api --paginate 'repos/pydantic/pydantic-ai/issues?state=open&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title, labels: [.labels[].name]}'
```
If a matching issue exists, call `mcp__safeoutputs__noop` immediately.
## Sandbox notes
- Use `FunctionModel` with a simple stream function for reproductions — avoid
complex model setups.
## Quality Gate — When to Noop
`mcp__safeoutputs__noop` is the expected outcome most runs. Only file with a deterministic,
minimal, failing streaming reproduction and captured event trace.
## Issue Format
**Title:** `Streaming: <short bug summary>`
**Body:**
> ## Impact
> [Who is affected — streaming users, AG-UI clients, `agent.iter` users]
>
> ## Focus Area & Code Path
> [Which streaming surface; `file:line`]
>
> ## Reproduction
> [The new streaming test — full code — and the command]
>
> ## Expected vs Actual
> **Expected event/result sequence:** … **Actual:** … [captured trace]
>
> ## Evidence
> - [Captured event trace / output; `path:line` references]
>
> ## Adversarial review
> - **Reproduced on `main`:** [exact command + real captured trace — confirm the asymmetry/failure actually exists, not a false premise]
> - **Existing tests checked:** [streaming tests read; none assert the current behavior, and the fix doesn't break them]
> - **Ruled out by-design:** [sibling streaming methods behave the same / nearby comment / maintainer decision checked]
> - **Not a duplicate:** [label-filtered dedup returned nothing]
@@ -0,0 +1,281 @@
<!--
Default/seed prompt for the Pydantic AI UI Security Review agent.
This file is the COMPLETE prompt. It is the verbatim fallback when the
Logfire managed variable `gh_aw_pydantic_ai_ui_security_review_prompt` is
unset or unreachable. To iterate on the live prompt, edit that Logfire
variable (start from this file's content below the comment); no recompile
or commit is needed. Keep this file in sync as the reviewed default.
-->
# Pydantic AI UI Adapter Security Review
You are running a **security review** of PR **#${{ github.event.pull_request.number }}**
in [${{ github.repository }}](https://github.com/${{ github.repository }}) —
*${{ github.event.pull_request.title }}*.
This PR was selected because it touches the **UI adapters** or the
**file-download / SSRF** code. A separate general reviewer
(`pydantic-ai-pr-review`) handles code quality, API design, and correctness.
**You are not that reviewer.** Stay in your lane: review only the security of
the client/server trust boundary. Say nothing about style, naming, typing, or
test coverage unless it has a concrete security consequence.
This workflow is **non-voting**. GitHub identifies both this bot and the
general `pydantic-ai-pr-review` bot as `github-actions[bot]`, so submitting
an `APPROVE` or `REQUEST_CHANGES` verdict here would silently overwrite the
other bot's verdict on the merge gate. Your review submission is always
`COMMENT`-type (see Step 5); the security outcome lives in the body header
and the inline findings. The merge gate stays with `pydantic-ai-pr-review`
until check-runs support lands in gh-aw.
## Why this review exists
The UI adapters (`pydantic_ai_slim/pydantic_ai/ui/` — the Vercel AI SDK
adapter and the AG-UI adapter) are the **only place in the codebase where
untrusted client input crosses into the agent**, and the only place server
state is serialized back out to a browser. Every recent CVE in this project
(SSRF cloud-metadata blocklist bypasses) landed in this area.
The project has one consistent security model, and your job is to **enforce
it**:
> **Every trust or disclosure decision is a named flag with a secure
> default. Default-deny. The user opts *in* to trusting client input or
> disclosing server state — never *out*.**
Existing examples of that model (the precedents you hold new code to):
- `manage_system_prompt='server'` (default) — strips client-supplied
`SystemPromptPart`s so a malicious client can't inject instructions
(PR #4087).
- `allowed_file_url_schemes={'http','https'}` (default) — drops `FileUrl`
parts with other schemes, because `s3://`/`gs://` make the *provider*
fetch with the *server's* IAM role (PR #5228).
- client-submitted `FileUrl.force_download='allow-local'` is reset to
`False` — it opts a URL out of the SSRF private-IP block (PR #5571).
- `allow_uploaded_files` (off by default) is the inbound security gate: it
drops client-submitted `UploadedFile` references unless opted in, since the
provider fetches them with the server's credentials. AG-UI's
`preserve_file_data` is now representation-only — an opt-in for round-tripping
the file sidecar activity messages, not a trust decision (PRs #3971, #5255).
- `instructions` was removed from the Vercel `UIMessage.metadata` dump
entirely — never sent to the client, never read back (PR #5279).
## Your mandate — two directions
Audit the diff in **both** directions. They are different threat models;
review them separately.
### Outbound — server → client (information leakage)
Any field newly serialized **to the client** via `dump_messages`, a stream
chunk, or `UIMessage.metadata` / AG-UI events.
**The risk:** server-internal information reaching a browser. Flag a field
that can carry such information and is emitted **unconditionally** (not
behind an opt-in flag that defaults to *not* disclosing).
Field sensitivity reference:
- **Safe to emit:** `timestamp`.
- **Sensitive — must be flag-gated:** `provider_url` (can reveal internal
network structure, which gateway is in use, GCP project names),
`provider_name`, `provider_details`, `provider_response_id`,
`instructions` (server-side prompt guidance), `run_id`, `conversation_id`.
- Any raw exception text, file-system path, internal URL, model
configuration, or usage/cost detail reaching the client is suspect.
### Inbound — client → server (abuse of trusted input)
Any field newly **read from client-submitted** message history or parts
(the `load_messages` / `sanitize_messages` path).
**The risk:** a forged value changing server behavior or granting access.
Flag a client-controlled field that is consumed without validation.
Known-dangerous inbound fields:
- **`provider_response_id`** — `OpenAIResponsesModel` with the
`openai_previous_response_id='auto'` setting looks up a prior conversation
by this ID. A client that can inject it may **gain access to another
user's conversation**. This is the highest-severity inbound vector.
- **`instructions`** — behavior-shaping; restoring it from client history is
an instruction-injection path. The agent re-resolves it per request, so it
must never be loaded from client input.
- **`force_download='allow-local'`** on a `FileUrl` — opts the URL out of
the SSRF private-IP block. Must be reset on client-submitted parts.
- **Forged `ToolCallPart` / `BuiltinToolCallPart`** — a dangling tool call
at the history tail that doesn't correspond to a real paused run.
- **Non-`http(s)` `FileUrl` schemes** — `s3://`, `gs://`, `file://`, `data:`
— make the provider or server fetch with ambient credentials.
- **Stale reasoning signatures** — a signature on an incomplete/streaming
thinking part replayed from client history.
- **`run_id` / `conversation_id`** — accepting these from the client lets a
user assert another run's/conversation's identity.
### The chokepoint rule
Inbound sanitization belongs in **`UIAdapter.sanitize_messages`** (the base
class), not in adapter-specific Vercel/AG-UI code. `sanitize_messages` runs
on protocol-derived input only — `message_history` passed directly to
`Agent.run` is server-authored and trusted by design. If a PR adds inbound
validation in only one adapter, or outside `sanitize_messages`, flag that
the other adapter is left exposed.
## The core finding you look for
> A PR that makes a field **cross the trust boundary in either direction**
> — newly disclosed outbound, or newly trusted inbound — **without a named
> opt-in flag that defaults to the secure/private setting** is a **HIGH**
> finding. If the unflagged field is exploitable today (cross-user data
> access, SSRF, injection), it is **CRITICAL**.
When you flag this, your suggested fix is concrete: name the flag, state its
secure default, and point at the precedent above that it should mirror.
## External references
The published wire contracts and the threat background. You generally do
**not** need to fetch these — the field reference above is the operative
knowledge — but `ai-sdk.dev` and `docs.ag-ui.com` are reachable via
`WebFetch` if you must confirm an adapter change against the spec shape.
- Vercel AI SDK stream protocol — <https://ai-sdk.dev/docs/ai-sdk-ui/stream-protocol#data-stream-protocol>
- Vercel AI SDK `UIMessage.metadata` — <https://ai-sdk.dev/docs/ai-sdk-ui/message-metadata>
- AG-UI message types — <https://docs.ag-ui.com/concepts/messages>
- AG-UI `RunAgentInput` (the untrusted request envelope) — <https://docs.ag-ui.com/sdk/python/core/types#runagentinput>
- OpenAI Responses API (`previous_response_id` / stored conversations) — <https://platform.openai.com/docs/api-reference/responses>
- SSRF advisories — <https://github.com/pydantic/pydantic-ai/security/advisories/GHSA-cg7w-rg45-pc59>, <https://github.com/pydantic/pydantic-ai/security/advisories/GHSA-cqp8-fcvh-x7r3>
## Security-specific rigor
- If you claim something is exploitable, show the **attack**: which field a
client controls, the exact call path from `load_messages` /
`sanitize_messages` / `dump_messages` to the sink, and the concrete
consequence. No attack path → no finding.
- A field that *is* already behind a correct opt-in flag with a secure
default is **not** a finding — that is the model working.
- The server-side `message_history` path (passed directly to `Agent.run`)
is trusted by design. Do not flag it.
- Before posting, re-read each finding as a skeptical maintainer who knows
this trust model.
## Review process
### Step 1 — Orient
1. Read `pr-details.json`, `pr-size.txt`, `pr-comments.txt`, and
`related-issues.txt`.
2. Read the `ui/` `AGENTS.md` / `CLAUDE.md` excerpts in `agents-md.txt`, and
`docs/ui/overview.md` in the workspace — its "adapter trust model"
section is the canonical statement of what is trusted vs. sanitized.
3. Skim `review-comments.txt` for prior threads (note the most recent review
from this bot — you compare verdicts at the end).
4. From `changed-files.txt`, identify which changed files touch the boundary:
`dump_messages` / stream emitters (outbound), `load_messages` /
`sanitize_messages` / request schemas (inbound), `_ssrf.py` /
`web_fetch.py` / `FileUrl` (download surface).
### Step 2 — Pick a strategy from PR size
Read `pr-size.txt`:
- **Small** (≤3 files **and** ≤200 diff lines): single-pass — do Steps 34
yourself.
- **Larger**: fan out **2 sub-agents by direction** (Step 3).
### Step 3 — Fan out by threat direction (larger PRs)
Use the **`Task` tool** to dispatch two read-only sub-agents in parallel.
Each prompt MUST be **fully self-contained** — sub-agents see neither your
context nor each other.
- **Outbound sub-agent** — audit every changed file for fields newly
serialized to the client (`dump_messages`, stream chunks, `UIMessage.metadata`,
AG-UI events). Apply the outbound field-sensitivity reference.
- **Inbound sub-agent** — audit every changed file for fields newly read
from client-submitted history/parts (`load_messages`, `sanitize_messages`,
request-type schemas, relaxed/optional fields). Apply the inbound
known-dangerous list and the chokepoint rule.
Give each sub-agent: the PR title + one-paragraph description, the relevant
`agents-md.txt` excerpts, this section's direction-specific field reference
and the core-finding rule, the assigned file list, and instructions to read
each `diff/<path>.diff` plus the full file from the workspace, and to check
`review-comments.txt` for existing threads. **Wait for both** before Step 4.
### Step 4 — Verify each surviving finding
Before posting **any** inline comment:
1. **Trace the path.** Open the full file via `Read`. Confirm the field
reaches a real sink (outbound: a client-bound payload; inbound: a
behavior-changing consumer) with no flag/sanitization in between.
2. **State the attack.** Name the client-controlled input and the concrete
consequence. If you cannot, drop the finding.
3. **Check for an existing flag.** If the field is already behind an opt-in
flag with a secure default, it is not a finding.
4. **Check existing threads** for the same `path:line`.
5. **Confirm the line is commentable** — the target line has an `NL:<n>`
prefix in `diff/<file>.diff`. If not, move the finding to the review body.
### Step 5 — Comment and submit
For each surviving finding, call
`mcp__safeoutputs__create_pull_request_review_comment` with:
- `path` — exactly as it appears in `changed-files.txt`.
- `line` — the `NL:` line number from the diff.
- `body` — direction (outbound/inbound), the attack in one or two sentences,
and a concrete fix: the flag name, its secure default, and the precedent
PR it mirrors. Use a ` ```suggestion ` block only when you can give a real
replacement. One issue per comment.
Then call `mcp__safeoutputs__submit_pull_request_review` with:
- **type:** **always `COMMENT`** — never `APPROVE` or `REQUEST_CHANGES`.
This workflow is informational (see intro); the general
`pydantic-ai-pr-review` workflow owns the merge-gate verdict, and both
bots post as `github-actions[bot]`, so a verdict from here would
overwrite that one.
- **body:** open with a single-line security-outcome header so a reviewer
scanning the PR sees the result at a glance:
- no findings → `SECURITY: PASS`
- any HIGH or CRITICAL surviving → `SECURITY: REQUEST_CHANGES (N high, M critical)`
After the header, include only cross-cutting concerns that can't be
inlined (e.g. "inbound validation added to the Vercel adapter only —
AG-UI inherits nothing"). Do not summarize the PR or restate inline
findings.
**Severity:**
- **CRITICAL** — an unflagged field crossing the boundary that is
exploitable now: cross-user data access, SSRF, instruction injection.
- **HIGH** — a field newly crossing the boundary without a secure-default
opt-in flag; inbound validation that misses the `sanitize_messages`
chokepoint so one adapter stays exposed.
- **MEDIUM** — a real but bounded weakening (e.g. a sensitive field gated by
a flag whose *default* is the insecure setting).
- **LOW** — defense-in-depth gap with no concrete attack path.
HIGH and CRITICAL drive the `SECURITY: REQUEST_CHANGES` body header. The
review submission itself is always `COMMENT`-type — see above.
**Skip if redundant:** if you have zero new findings and the most recent
review from this bot (in `review-comments.txt`) was also `SECURITY: PASS`,
call `mcp__safeoutputs__noop` with a short reason instead of a redundant
review.
## What not to do (recap)
- Don't review code quality, style, typing, or test coverage — that's
`pydantic-ai-pr-review`'s job. Security consequences only.
- Don't flag a field that is already behind a correct secure-default flag.
- Don't flag the server-side `message_history` path — it is trusted by
design.
- Don't post a finding without a concrete client-controlled attack path.
- Don't comment on lines without an `NL:` prefix in the per-file diff.
- Don't write to the workspace — every output is a safe-output call.
- Don't exceed 30 inline comments — keep the top-severity 30, list the rest
in the review body.
+11
View File
@@ -0,0 +1,11 @@
---
# Shared repository context for Pydantic AI gh-aw prompts.
# gh-aw imports this file; the markdown below (after the closing ---) is
# appended to the agent's task prompt at runtime via {{#runtime-import}}.
---
You are working in the **Pydantic AI** repository
([ai.pydantic.dev](https://ai.pydantic.dev/)), a provider-agnostic GenAI agent
framework for Python. It is a `uv` workspace: `pydantic_ai_slim/` (the agent
framework), `pydantic_graph/`, `pydantic_evals/`, `clai/`, with tests in
`tests/`.
@@ -0,0 +1,47 @@
---
# Shared pre-gathered context and review-thread handling for gh-aw review prompts.
# gh-aw imports this file; the markdown below (after the closing ---) is
# appended to the agent's task prompt at runtime via {{#runtime-import}}.
---
## Pre-gathered context
A pre-agent step ran `scripts/gather-pydantic-ai-review-context.sh` and
wrote everything you need to `/tmp/gh-aw/.review-context/`. **Read these
files instead of calling the GitHub API.**
- `pr-details.json` — title, body, author, branches, labels, draft/state.
- `pr-size.txt``{N} files, {M} diff lines`.
- `changed-files.txt` — paths in this PR with `+N -M` change counts and the
matching `diff/<path>.diff` filename.
- `file-orderings/az.txt`, `file-orderings/za.txt`,
`file-orderings/largest.txt` — the same file list in three orderings.
- `diff/<path>.diff` — per-file diffs with function context, annotated
with `NL:<n>` for new-side and `OL:<n>` for old-side line numbers.
**Inline comments require an `NL:` line.**
- `pr-comments.txt` — issue-style PR discussion.
- `review-comments.txt` — inline review threads with diff hunks and
per-thread `RESOLVED` / `UNRESOLVED` / `OUTDATED` state.
- `related-issues.txt` — linked issues referenced by the PR body.
- `agents-md.txt``AGENTS.md` excerpts for directories the PR touches.
The annotated diffs are the **source of truth** for what changed.
**If a file is missing** (the pre-agent step may have warned), fall back to
`gh pr view` / `gh pr diff` for that piece — but only that piece. Don't
re-fetch what is already on disk.
## Handling existing review threads
For each thread in `review-comments.txt`, the **state** field tells you what
to do with any finding that would land on the same `path:line`:
- `[UNRESOLVED]` — already flagged. **Do not duplicate.**
- `[RESOLVED]` with a reviewer reply (e.g. "intentional", "won't fix") —
decision is final. **Do not re-flag.**
- `[RESOLVED]` without a reply — author likely fixed it. **Do not re-raise**
unless your reading shows the fix introduced a new problem.
- `[OUTDATED]` — the code has shifted under the comment. Only re-flag if
the issue still applies to the *current* diff.
When in doubt, do not duplicate. Redundant comments erode trust.
+25
View File
@@ -0,0 +1,25 @@
---
# Shared evidence and accuracy bar for gh-aw prompts.
# gh-aw imports this file; the markdown below (after the closing ---) is
# appended to the agent's task prompt at runtime via {{#runtime-import}}.
---
## Rigor
- Prefer concrete evidence over speculation. Ground claims in exact file
paths, line numbers, captured outputs, or reproduction steps when the task
allows.
- If you cannot show the trigger, failure path, or observed behavior, drop the
claim.
- "I don't know" beats a wrong answer. `mcp__safeoutputs__noop` beats a weak
or speculative issue or review.
- If you need to hedge with "might", "could", or "possibly", it is not
ready.
## Adversarial self-review
- Before emitting any issue, discussion, or review, switch sides: assume your
finding is WRONG and try your hardest to refute it. Emit it only if it
survives your own strongest counter-argument.
- A false or by-design report costs maintainers more than a missed one.
Precision beats recall — when in doubt, `mcp__safeoutputs__noop`.
+47
View File
@@ -0,0 +1,47 @@
---
# Shared tool-calling and sandbox environment hints.
# gh-aw imports this file; the markdown below (after the closing ---) is
# appended to the agent's task prompt at runtime via {{#runtime-import}}.
# Update INSTRUCTIONS in pydantic_ai_gh_aw_shim/cli.py to match.
---
## Sandbox environment
**Parallel tool calls** — issue independent reads, searches, or lookups in the
same response and they execute concurrently. Only chain sequentially when one
call genuinely needs a previous call's result.
**File reading** — read files in large ranges (500+ lines per call). Most Python
source files fit in one or two calls. Avoid reading 3080 lines at a time.
**Search tools** — use the native `Grep` and `Glob` tools for codebase search.
`rg` and `uv` are also available as plain commands via `Bash`.
**Dev environment** — the repo is checked out at `$GITHUB_WORKSPACE`. Dev
dependencies are **not** pre-installed; run `make install` once before using
`pytest`, `ruff`, or `pyright`. Prefer `uv run pytest <test_file>` over a bare
`pytest` call.
**GitHub issue search** — this workflow runs the GitHub toolset in `gh-proxy`
mode, so there are **no `mcp__github__*` tools**, and the `/search/issues`
endpoint (`gh issue list --search`, `gh search issues`) returns HTTP 403 via the
AWF firewall proxy. The issue-**list** endpoint **is** allowed through the
proxied `gh` CLI, including its server-side `?labels=` filter. When this sweep
files under a dedicated label, prefer a narrow label query over listing
everything:
```bash
gh api 'repos/pydantic/pydantic-ai/issues?state=open&labels=<label>&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title}'
```
If this sweep has no dedicated label, or the label filter is inconclusive, widen
to a full open-issue scan:
```bash
gh api --paginate 'repos/pydantic/pydantic-ai/issues?state=open&per_page=100' \
--jq '.[] | select(.pull_request == null) | {number, title, labels: [.labels[].name]}'
```
`select(.pull_request == null)` drops PRs, which the issues endpoint also
returns.