DEV Community

Tracepilot
Tracepilot

Posted on

How to Review a PR Against a GitHub Issue: A Staff Engineer's Checklist

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
Enter fullscreen mode Exit fullscreen mode

Issue decomposition for #825:

  • Main goal: Gate AI agent execution behind a checkup system
  • Requirements:
    1. New gate command that runs pre-flight checks
    2. Checks must be extensible (plugin-style)
    3. Failed checks block agent execution
    4. Clear error messages with check names
    5. Configuration via pdd.yaml

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
Enter fullscreen mode Exit fullscreen mode

What pdd checkup should do:

{
  "scripts": {
    "checkup": "pnpm lint && pnpm typecheck && pnpm test && pnpm build"
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
  }
}
Enter fullscreen mode Exit fullscreen mode

Review checklist I used:

  1. Requirement 1 — Gate command exists:

    • pdd gate command in src/commands/gate.ts
    • ✅ Exits with code 1 when checks fail
    • ⚠️ Missing: --check flag to run specific checks
  2. Requirement 2 — Extensible checks:

    • Check interface with execute() method
    • ✅ Custom checks via pdd.yaml config
    • Bug: Custom checks don't receive the agent context — only config
  3. Requirement 3 — Blocks execution:

    • ✅ Gate runs before agent starts
    • ⚠️ Missing: --force flag for emergency bypass
  4. Requirement 4 — Clear errors:

    • ✅ Errors include check name and duration
    • Bug: Error messages don't include the agent name
  5. Requirement 5 — Configuration:

    • pdd.yaml supports gate: 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);
Enter fullscreen mode Exit fullscreen mode

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');
  });
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
// 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);
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode

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)