A GitHub Action that reviews pull requests on FRC robot code. Drop a workflow file in your repo, open a PR, comment @frc-reviewer, and get inline feedback on WPILib patterns, command-based architecture, and AdvantageKit logging.
Built by YETI Robotics (FRC Team 3506).
Reviews run in three passes:
- Summarize — reads the diff and figures out what the PR is actually trying to do, and which files are worth looking at closely
- Review — checks the changed code against a set of FRC-specific rules (skills), produces a list of candidate issues
- Verify — runs each candidate independently to confirm it's real before posting anything
The result is a summary comment on the PR plus inline review comments at the exact lines where issues were found. Pass 1 can use a cheaper/faster model than passes 2 and 3 if you want to save on inference costs.
Reviews are incremental: if you trigger the reviewer twice on the same PR, the second run only looks at commits pushed since the first review.
Install the YETI FRC Reviewer GitHub App on your repository. This gives the bot its own identity so review comments show up as frc-reviewer[bot].
In your repo: Settings → Secrets and variables → Actions → New repository secret
| Secret | Value |
|---|---|
DO_API_KEY |
Your DigitalOcean AI inference API key |
For the DigitalOcean key: go to cloud.digitalocean.com, navigate to AI → API Keys, and generate a key.
Copy this to .github/workflows/frc-reviewer.yml in your robot code repo:
name: FRC Code Reviewer
on:
issue_comment:
types: [created]
permissions:
contents: read
pull-requests: write
issues: write
jobs:
review:
if: |
github.event.issue.pull_request != null &&
contains(github.event.comment.body, '@frc-reviewer')
runs-on: ubuntu-latest
steps:
- name: Run FRC Reviewer
uses: yeti-robotics/frc-reviewer@v1
with:
api-key: ${{ secrets.DO_API_KEY }}
gateway: digitalocean
model: anthropic/claude-sonnet-4-5Open a PR in your repo and comment @frc-reviewer. The bot will add a 👀 reaction when it starts, post inline comments and a summary, then add a 🚀 when it's done.
The default mode (trigger: comment) runs when someone comments the trigger phrase on a PR. Only runs on PRs — comments on regular issues are ignored.
If you'd rather have the review run automatically on every push to a PR, switch to auto mode:
on:
pull_request:
types: [opened, synchronize]
jobs:
review:
runs-on: ubuntu-latest
steps:
# ... same steps, but remove the if: condition
- uses: yeti-robotics/frc-reviewer@v1
with:
trigger: auto
# ...Auto mode works well if your team treats reviews as a CI check. Comment mode is better if you only want a review on demand, since it avoids spending inference tokens on every trivial push.
All inputs are optional except api-key.
| Input | Default | Description |
|---|---|---|
api-key |
— | API key for the AI gateway (required) |
gateway |
digitalocean |
Which AI provider to use. Options: digitalocean, openai, anthropic, vercel |
model |
gpt-4o |
Model name. Must be valid for the chosen gateway |
fast-model |
(same as model) | A cheaper model to use for pass 1 (summarize). Pass 2 and 3 always use model |
github-token |
${{ github.token }} |
GitHub token for API calls. Defaults to the token provided by the installed GitHub App |
trigger |
comment |
comment runs on @mention, auto runs on every PR push |
trigger-phrase |
@frc-reviewer |
The phrase that triggers a comment-based review |
minimum-role |
write |
Minimum repository role required to trigger a comment-based review. Options: read, triage, write. Bot accounts are always blocked. |
skills-path |
.agents/skills |
Path to your team's custom skills directory (relative to repo root) |
fail-on-critical |
false |
If true, the Action exits with code 1 when critical issues are found |
DigitalOcean — use DigitalOcean's model slugs:
anthropic/claude-sonnet-4-5anthropic/claude-haiku-4-5meta/llama-3.3-70b-instruct
OpenAI:
gpt-4o,gpt-4o-mini,o3-mini
Anthropic:
claude-sonnet-4-5,claude-haiku-4-5-20251001
A good split is fast-model: anthropic/claude-haiku-4-5 for the summarize pass and model: anthropic/claude-sonnet-4-5 for review and verify. Haiku is significantly cheaper and the summarize pass doesn't need the best model.
Skills are markdown files that define what the reviewer looks for. Three skills ship with the action:
WPILib (wpilib) — common mistakes in WPILib robot code: blocking calls in periodic methods, incorrect CommandScheduler usage, putting logic in Subsystem.periodic(), using System.currentTimeMillis() instead of Timer.getFPGATimestamp(), missing motor safety timeouts.
Command-Based (command-based) — command-based architecture patterns: missing isFinished(), forgetting addRequirements(), using InstantCommand where a full command is needed (and vice versa), parallel command groups that conflict on subsystem requirements, end() not handling interruption.
AdvantageKit (advantagekit) — AdvantageKit logging correctness: missing IO interface pattern, side effects in updateInputs() that break replay, direct DriverStation calls inside subsystems, missing Logger.recordOutput() calls, @AutoLog usage.
All bundled skills apply globally — they run on every PR regardless of which files changed.
You can add your own skills in .agents/skills/ (or wherever you point skills-path). A skill is a markdown file with a YAML front matter block:
---
name: Team 3506 Drive Standards
---
# Drive Subsystem Rules
## Swerve module order
Always initialize swerve modules in the order: front-left, front-right,
back-left, back-right. Inconsistent ordering causes field-relative drive
to flip axes unpredictably.
## Odometry updates
Call `poseEstimator.update()` in `DriveSubsystem.periodic()`, not in a
command. Commands that need the current pose should read from the subsystem.Skills with the same filename stem as a bundled skill override it. So if your team has strong opinions about WPILib patterns that differ from the defaults, create .agents/skills/wpilib.md and it will replace the bundled one entirely.
There's no limit on the number of skills, but more skills means more prompt tokens per review. Keep rules specific and actionable — the reviewer does better with "don't call CommandScheduler.getInstance().run() manually" than with "follow best practices."
When you trigger a second review on the same PR, the action looks for its own previous summary comment and reads the SHA it reviewed last time. Only files changed in commits after that SHA get reviewed.
This means you can iterate on a PR — push a fix, ask for another review — without re-reviewing code that was already approved. The summary comment is replaced with a new one each time.
The action needs these permissions on whatever token you pass as github-token:
pull-requests: write— to post the review and inline commentsissues: write— to post the summary comment and reactionscontents: read— to read file contents at the PR's head SHA
When the YETI FRC Reviewer GitHub App is installed, ${{ github.token }} is automatically scoped to the app's identity and has all of these permissions.
The api-key is the DigitalOcean (or other gateway) key. It never touches GitHub — it's only used for AI inference calls. It won't appear in logs.
The bot isn't responding to my comment.
Check the Actions tab in your repo and look at the workflow run. If it didn't trigger at all, the if: condition on the job probably didn't match — confirm the comment is on a PR (not an issue) and contains the exact trigger phrase. If the run started but the logs say "Skipping review: [username] has 'read' on this repo", the commenter doesn't have sufficient repository access (see minimum-role). If the run started but failed, the error will be in the job logs.
I'm getting "Could not determine PR number from event context".
This usually means the workflow triggered on an event that isn't a PR comment or PR push. Double-check your on: block matches what you're expecting.
Reviews are posting but the inline comments are in the wrong place. Inline comments require a diff position, not a line number — the action maps line numbers to positions using the patch. If the model reports an issue on a line that isn't in the diff (e.g., an unchanged line), it gets dropped from inline comments but still shows in the summary. This is by design.
I want to disable a bundled skill.
Create an empty skill file with the same stem in your .agents/skills/ directory. For example, .agents/skills/advantagekit.md with just the front matter and no content will override the bundled AdvantageKit skill.
Skills are the easiest place to contribute. If you've identified a common FRC mistake that isn't covered, open a PR adding or improving a skill in skills/. Keep rule descriptions concrete — include a bad example and a good example in code blocks where it helps.
For bugs or feature requests, open an issue.