From GitHub Backlog to Merged PR with AI: A Walkthrough
The pitch for autonomous AI coding agents sounds clean: connect your repo, watch PRs appear. The reality is a bit more detailed — and understanding the mechanics matters for setting up filters correctly, knowing what to review, and calibrating expectations.
This post walks through a real issue run: from a GitHub issue sitting in your backlog to a merged PR. What happens at each step, what you see, and what you're skipping vs. what you're responsible for.
The Starting State
Let's say your repo is a Node.js/TypeScript API. You have the following open issue:
Issue #247 — Bug: GET /api/orders returns 500 when order has no line items
Labels: bug, backend, p2
Assignee: (unassigned)
Story points: 2
Body:
When an order has zero line items (it was cancelled before any items were added),
the GET /api/orders endpoint returns a 500 error instead of returning the order
with an empty items array.
Steps to reproduce:
1. Create an order (POST /api/orders) → note the order ID
2. Cancel the order immediately (DELETE /api/orders/:id) — don't add items
3. GET /api/orders — 500 error
Expected: order returned with items: []
Current: 500 Internal Server Error
Relevant file: src/services/order.service.ts (suspecting mapOrderToResponse function)
This is a well-defined bug. Reproducible, scoped, with a suspected location. Good Codowave input.
Step 1: Issue Selection
Codowave's issue poller runs on your configured schedule (default: every 30 minutes, more frequently on the Team plan). It reads your open issues and scores them against your configured filters:
Your filters:
- Labels must include
bugORbackend - Story points ≤ 5
- Risk level: exclude issues tagged
auth,payments,security - Assignee: unassigned only
Issue #247 matches. Codowave marks it as "picked" (posts a comment on the issue: "Codowave is working on this"), creates a new branch (codowave/247-fix-orders-500-empty-line-items), and starts the pipeline.
What you see at this point: A comment on issue #247 and a new branch in your repo.
Step 2: Planner Agent
The Planner agent receives the issue body and runs a codebase search to understand the scope.
It reads:
src/services/order.service.ts(mentioned in the issue)src/controllers/orders.controller.ts(the endpoint handler)src/types/order.types.ts(type definitions)src/tests/orders.test.ts(existing test file, to understand test patterns)
Planner output (logged and visible in the run timeline):
Decomposition:
1. Find mapOrderToResponse function in order.service.ts
2. Identify the line causing 500 on empty items array (likely a .map() or .reduce() on undefined)
3. Add null/empty array guard
4. Verify existing tests still pass
5. Add regression test: GET /api/orders with order that has no line items
Affected files:
- src/services/order.service.ts (primary change)
- src/tests/orders.test.ts (regression test)
Risk: Low — isolated to order mapping function, no auth or payments involvement
The Planner takes about 2-3 minutes.
Step 3: Coder Agent
The Coder receives the Planner's decomposition and reads only the identified files. It checks Codowave's pattern memory for this repo:
Pattern memory notes:
- TypeScript interfaces, not
typealiases, for response types - Error handling uses
AppErrorclass, not rawError - Tests use
describe/itwith factory functions (createOrder(),createLineItem()) expect().resolvesfor async tests
The Coder finds the bug in order.service.ts:
// Before (line 34)
const mapOrderToResponse = (order: Order): OrderResponse => ({
id: order.id,
status: order.status,
items: order.lineItems.map(item => ({ // Throws if lineItems is undefined
id: item.id,
quantity: item.quantity,
price: item.price
}))
});
The fix:
// After
const mapOrderToResponse = (order: Order): OrderResponse => ({
id: order.id,
status: order.status,
items: (order.lineItems ?? []).map(item => ({
id: item.id,
quantity: item.quantity,
price: item.price
}))
});
The Coder writes the change and commits to the branch. It takes about 4-6 minutes.
Step 4: Reviewer Agent
The Reviewer receives only the git diff — not the Planner's reasoning or the Coder's implementation process. Fresh context, focused on the diff.
Reviewer checks:
- Does the fix address the reported issue? ✓
- Does
order.lineItems ?? []handle bothundefinedandnull? ✓ (??catches both) - Is there an edge case where
lineItemsis not undefined but contains null items? Flagged. - Does the fix follow the repo's TypeScript patterns? ✓
- Does the PR title/branch match commit message format? ✓
Reviewer output:
1 issue flagged (low severity):
- Line 37: order.lineItems could theoretically contain null elements within the array
(e.g., [null, {...}]). The current fix handles undefined/null array but not null
elements. Recommend: filter nulls before mapping.
Suggested fix: (order.lineItems ?? []).filter(Boolean).map(...)
The Coder gets one more pass to apply the Reviewer's suggestion:
items: (order.lineItems ?? []).filter(Boolean).map(item => ({
Step 5: Tester Agent
The Tester runs the existing test suite:
npm test -- src/tests/orders.test.ts
✓ GET /api/orders returns list of orders (45ms)
✓ GET /api/orders/:id returns single order (23ms)
✓ POST /api/orders creates new order (67ms)
✓ GET /api/orders filters by status (31ms)
4 tests passed, 0 failed
The Tester checks test coverage on the modified code — the mapOrderToResponse function wasn't covered by the existing tests. It writes a regression test:
it('returns order with empty items array when order has no line items', async () => {
const order = await createOrder({ lineItems: undefined });
const response = await request(app).get('/api/orders');
expect(response.status).toBe(200);
const found = response.body.find((o: OrderResponse) => o.id === order.id);
expect(found).toBeDefined();
expect(found.items).toEqual([]);
});
Runs the full suite again:
✓ GET /api/orders returns list of orders (45ms)
✓ GET /api/orders/:id returns single order (23ms)
✓ POST /api/orders creates new order (67ms)
✓ GET /api/orders filters by status (31ms)
✓ GET /api/orders returns order with empty items array when order has no line items (38ms)
5 tests passed, 0 failed
The Tester step takes about 5-8 minutes including test writing.
Step 6: PR Opens
Total time from issue selection to PR: approximately 22 minutes.
The PR Codowave opens:
Title: Fix: GET /api/orders 500 error when order has no line items (#247)
Body:
## Summary
Fixes #247. The `mapOrderToResponse` function in `order.service.ts` called
`.map()` on `order.lineItems` without guarding against undefined/null values.
Orders created and immediately cancelled have no line items, causing the 500.
## Changes
- `src/services/order.service.ts`: Added null guard `(order.lineItems ?? []).filter(Boolean)`
before `.map()` call in `mapOrderToResponse` (line 34)
- `src/tests/orders.test.ts`: Added regression test for GET /api/orders with
order that has no line items
## Reviewer notes addressed
- Added `.filter(Boolean)` to handle potential null elements within the array
(flagged by Reviewer agent, low severity)
## Test results
5 tests passing, 0 failing
New test added: "returns order with empty items array when order has no line items"
## Run details
<CtaButton href="https://app.codowave.com/runs/r_abc123">View full run replay</CtaButton>
Cost: $1.40 / $5.00 ceiling
Agents: Planner (0.40s), Coder (0.45s), Reviewer (0.30s), Tester (0.25s)
Step 7: What You Do Now
Since you're in watch-only mode:
- You get a notification (GitHub, Slack if on Team plan, email) — "Codowave opened PR #248"
- You open the PR — reads like a real PR from a careful developer
- You review the diff — 2 files, ~15 lines changed, looks correct
- You read the test output — 5 passing, regression test added
- You merge — or click approve if branch protection requires review
Total time on your end: 5-10 minutes reviewing, not 2 hours writing.
Once you've seen 5-10 PRs like this and they're consistently correct, you configure auto-merge for issues labeled bug + p2 + story points ≤ 2. Codowave handles those end-to-end. You wake up to already-merged fixes.
What Makes a Good Issue for This Workflow
Based on this example, the patterns that make issues Codowave-ready:
- Specific reproduction steps — the more concrete, the better the Planner's decomposition
- Suspected location — "Relevant file: X" dramatically improves accuracy
- Clear expected behavior — "expected: empty array, current: 500" gives the Tester a clear pass/fail criterion
- Bounded scope — changes to 1-3 files, not sweeping architectural changes
You don't need perfect issues — Codowave posts clarifying questions when issues are ambiguous. But well-written issues produce better PRs faster.