security
Pr Triage Automation With Risk Scoring Failure Patterns: practical systems guide
Technical field guide on pr triage automation with risk scoring failure patterns and fixes for teams building dependable AI coding workflows.
# PR Triage Automation With Risk Scoring Failure Patterns: practical systems guide
Most teams drown in pull requests not because they lack automation, but because their automation focuses on the wrong signals. A passing test suite tells you what worked; it doesn't tell you which changes deserve immediate review or which can safely wait. The PR that accidentally exposes credentials or breaks backward compatibility sits in the queue alongside trivial typo fixes, both marked "green."
Effective PR triage automation requires risk scoring—a system that surfaces high-stakes changes before they merge. The challenge isn't building the scorer; it's recognizing when your scoring logic has failed and iterating on the failure patterns you discover.
Why static rules break down quickly
Most teams start with simple heuristics: "Flag PRs that touch authentication code" or "Escalate any change over 500 lines." These rules work for about two weeks. Then someone refactors the auth module into smaller helpers, and your 50-line change that completely rewrites the session logic flies under the radar.
Static rules fail because codebases evolve. The authentication logic you hardcoded as `src/auth/` gets split into `src/session/`, `src/identity/`, and `src/tokens/`. Your line-count threshold becomes meaningless when the team adopts auto-formatters that inflate diffs. The real risk patterns hide in context: *what* changed, *who* changed it, *how recently* that code last broke production, and *what dependencies* it affects.
A useful risk scorer needs feedback loops that capture when it guessed wrong.
Building a scorer that learns from misses
Start with these concrete signals, each tied to observable failure modes:
**Ownership proximity**: Flag PRs where the author has never touched the modified files. In Goatfied's agent loop, the `plan` phase can query your Git history to identify strangers to a subsystem. A junior engineer editing database migration logic they've never seen is higher risk than a veteran tweaking their own code.
**Blast radius**: Score based on how many downstream services or modules import the changed code. If `shared/validation.ts` is imported by 40 files, changes there carry more risk than tweaking a one-off utility. Static analysis tools can build this dependency graph; in a compile-first workflow, you already have it.
**Fragility score**: Track test flakiness and recent production incidents per file. If `checkout.py` caused two outages in the last month, any PR touching it deserves eyes-on review even if it's a one-line change. Store this metadata in a simple JSON file or database; update it from your incident postmortems and CI failure logs.
**Breaking change indicators**: Search diffs for removed functions, changed API signatures, or deleted environment variables. These patterns predict integration failures. Regular expressions work for simple cases; AST diffs catch the subtle ones.
Implement these as independent scorers that output a 0-1 risk level. Sum them with weights you tune based on retrospectives. The key insight: **every merged bug is feedback**. When a low-scored PR causes a production incident, that's training data.
Instrumenting failure patterns
Your scoring system will miss things. The goal is to *notice* quickly and adjust. Instrument these feedback loops:
**Post-incident tagging**: When you write a postmortem, tag the causative PR. Build a weekly report: "PRs scored <0.3 that caused incidents." Review these misses as a team. Often you'll spot patterns—maybe all of them touched configuration files your scorer didn't weight heavily, or involved new contributors during crunch time.
**Review duration correlation**: Track time-to-first-review for each PR, grouped by risk score. If your highest-scored PRs sit untouched for days while low-scored ones get immediate attention, your scores aren't aligned with actual human priorities. Tune accordingly.
**False positive audits**: Flag PRs that got escalated but merged cleanly after cursory review. Too many false positives train engineers to ignore your alerts. Sample a few each week: did the scorer overreact to a refactor-only change? Was a dependency update flagged unnecessarily?
In Goatfied's workflow, the `validate` gate runs tests and lints before a PR even reaches human review. Layering risk scoring before that gate means you can auto-approve trivial changes while routing high-risk work through stricter checks—additional approvers, required staging deploys, or manual security review.
Practical implementation: a risk scorer in 100 lines
Here's a minimal scorer you can adapt:
def score_pr(pr_data):
risk = 0.0
# Ownership: author unfamiliar with files?
for file in pr_data['changed_files']:
if pr_data['author'] not in get_past_authors(file):
risk += 0.15
# Blast radius: imports per changed file
for file in pr_data['changed_files']:
import_count = count_importers(file)
risk += min(import_count / 50, 0.3)
# Fragility: recent incident history
fragile_files = load_fragile_files() # from incident log
overlap = set(pr_data['changed_files']) & fragile_files
risk += len(overlap) * 0.25
# Breaking changes: API signature diffs
if has_breaking_changes(pr_data['diff']):
risk += 0.4
return min(risk, 1.0)
Hook this into your CI pipeline. When `score_pr()` exceeds 0.6, require two approvals and a staging deployment. Under 0.2, auto-merge after tests pass. Between 0.2 and 0.6, one approval suffices. These thresholds are starting points—adjust them as you collect data on what actually predicts problems.
Handling the edge cases that matter
**Large refactors**: A 2,000-line change that renames a module will score high on blast radius but might be low-risk if it's purely mechanical. Add a "refactor" label that authors can apply, then discount line count for labeled PRs but keep the fragility and breaking-change checks.
**Hotfixes under pressure**: In an incident, you'll merge PRs that score high. That's fine. Log them separately and review them after the fire is out. The scorer's job isn't to block critical fixes—it's to surface risk so you make informed trade-offs.
**New projects with no history**: For fresh repos, lean on ownership and breaking-change signals. Fragility scores need time to accumulate. Start conservative; relax thresholds as you build confidence.
Closing the loop
Every quarter, review your scorer's hit rate. Compare its predictions against actual production issues, support tickets, and rollback frequency. If the correlation weakens, your codebase has changed faster than your scorer. Update the weights, add new signals (maybe you've started using feature flags, or adopted a new database), and retrain your intuition.
Risk scoring isn't about perfect prediction. It's about building a system that learns from its mistakes faster than your team accumulates new code.
Related reading
- [Security playbook #5: practical Goatfied tactics for shipping PRs](/blog/goatfied-security-playbook-5)
- [Security playbook #13: practical Goatfied tactics for shipping PRs](/blog/goatfied-security-playbook-13)
