How to Review a PR Against a GitHub Issue: A Staff Engineer's Checklist
What we're building: A repeatable 4-step PR review process that decomposes the issue, runs automated checks, does a deep code review, tests the changes, and fixes issues — using a real PR as our example.
Prerequisites:
- Git and GitHub CLI (
gh) installed - Node.js 18+ and pnpm
- Access to the repo you're reviewing
Step 1: Decompose the Issue
First, break the GitHub issue into testable requirements. This gives you a checklist to review against.
# Get the issue details
gh issue view 825 --repo promptdriven/pdd
# Get the PR details
gh pr view 1260 --repo promptdriven/pdd
Issue decomposition for #825:
- Main goal: Gate AI agent execution behind a checkup system
-
Requirements:
- New
gatecommand that runs pre-flight checks - Checks must be extensible (plugin-style)
- Failed checks block agent execution
- Clear error messages with check names
- Configuration via
pdd.yaml
- New
Step 2: Run Automated Checks
Start with the mechanical stuff. This catches 80% of issues before you read a line of code.
# Clone and checkout the PR branch
git clone https://github.com/promptdriven/pdd.git
cd pdd
git checkout DianaTao:feat/issue-825-gate
# Install dependencies
pnpm install
# Run the full checkup suite
pnpm checkup # lint, types, tests, build
# If checkup fails, fix those first before code review
What pdd checkup should do:
{
"scripts": {
"checkup": "pnpm lint && pnpm typecheck && pnpm test && pnpm build"
}
}
If this fails, stop and fix. You can't review broken code.
Step 3: Detailed Code Review
Now the real work. Review against your decomposed requirements, not the whole PR at once.
// lib/gate/checkup.ts — The core implementation
export interface CheckupResult {
name: string;
passed: boolean;
message?: string;
durationMs: number;
}
export class Gate {
private checks: Check[] = [];
constructor(private config: GateConfig) {
this.registerDefaultChecks();
}
async runAll(): Promise<CheckupResult[]> {
const results: CheckupResult[] = [];
for (const check of this.checks) {
const start = performance.now();
try {
const passed = await check.execute(this.config);
results.push({
name: check.name,
passed,
durationMs: performance.now() - start,
});
} catch (error) {
results.push({
name: check.name,
passed: false,
message: error.message,
durationMs: performance.now() - start,
});
}
}
return results;
}
}
Review checklist I used:
-
Requirement 1 — Gate command exists:
- ✅
pdd gatecommand insrc/commands/gate.ts - ✅ Exits with code 1 when checks fail
- ⚠️ Missing:
--checkflag to run specific checks
- ✅
-
Requirement 2 — Extensible checks:
- ✅
Checkinterface withexecute()method - ✅ Custom checks via
pdd.yamlconfig - ❌ Bug: Custom checks don't receive the agent context — only config
- ✅
-
Requirement 3 — Blocks execution:
- ✅ Gate runs before agent starts
- ⚠️ Missing:
--forceflag for emergency bypass
-
Requirement 4 — Clear errors:
- ✅ Errors include check name and duration
- ❌ Bug: Error messages don't include the agent name
-
Requirement 5 — Configuration:
- ✅
pdd.yamlsupportsgate:section - ⚠️ Missing: JSON schema for IDE autocomplete
- ✅
Critical bug found:
// src/commands/run.ts — Line 42
const gate = new Gate(config.gate);
const results = await gate.runAll();
// BUG: This runs gate AFTER agent starts
// Should be:
// await gate.runAll();
// if (results.some(r => !r.passed)) process.exit(1);
Step 4: Test and Fix
Write targeted tests for the gate logic, then fix the bugs you found.
// tests/gate.test.ts
import { describe, it, expect, vi } from 'vitest';
import { Gate } from '../lib/gate/checkup';
describe('Gate', () => {
it('blocks when checks fail', async () => {
const gate = new Gate({
checks: [{
name: 'env-check',
execute: () => Promise.resolve(false)
}]
});
const results = await gate.runAll();
expect(results[0].passed).toBe(false);
});
it('passes when all checks succeed', async () => {
const gate = new Gate({ checks: [] });
const results = await gate.runAll();
expect(results).toEqual([]);
});
it('captures errors from failing checks', async () => {
const gate = new Gate({
checks: [{
name: 'flaky-check',
execute: () => Promise.reject(new Error('DB down'))
}]
});
const results = await gate.runAll();
expect(results[0].message).toContain('DB down');
});
});
Apply the fixes:
# 1. Fix the gate-before-agent bug
git checkout -b fix/gate-order
# 2. In src/commands/run.ts, move gate.runAll() before agent start
# 3. Add agent name to error messages
# 4. Add --force flag for emergency bypass
// Fixed run.ts
export async function runAgent(agentName: string, opts: RunOptions) {
// Gate FIRST
if (!opts.force) {
const gate = new Gate(config.gate);
const results = await gate.runAll();
if (results.some(r => !r.passed)) {
const failed = results.filter(r => !r.passed);
throw new Error(
`Agent "${agentName}" blocked by ${failed.length} failed checks:\n` +
failed.map(r => ` ✗ ${r.name}: ${r.message}`).join('\n')
);
}
}
// Then run agent
return executeAgent(agentName, opts);
}
Adding Observability
Now make those gate failures actually debuggable. This is where TracePilot turns "it failed" into "here's exactly why."
npm install tracepilot-sdk
import { TracePilot } from 'tracepilot-sdk';
const tp = new TracePilot(process.env.TRACEPILOT_API_KEY);
export async function runAgent(agentName: string, opts: RunOptions) {
await tp.startTrace(`agent-${agentName}`);
// Gate with full visibility
const { result: gateResults, spanId } = await tp.wrapToolCall(
'gate-checkup',
() => runGateChecks(config.gate),
undefined,
1,
false
);
if (gateResults.some(r => !r.passed)) {
// Fork & Rerun: edit the failing check right from the dashboard
throw new Error(`Agent blocked by gate: ${JSON.stringify(gateResults)}`);
}
const { result } = await tp.wrapOpenAI(
() => executeAgent(agentName, opts),
[{ role: 'user', content: `Run ${agentName}` }],
spanId,
2
);
return result;
}
Every gate check, every agent call, every failure — captured with full context. When a check fails in production, you open the dashboard, see the exact check that failed, fork it
Debugging AI agents shouldn't feel like reading The Matrix.
Join other engineers who are building reliable autonomous workflows in our community: TracePilot Discord
Top comments (0)