Home » A Coding-Agent Workflow from Issue to Reviewed Pull Request

A Coding-Agent Workflow from Issue to Reviewed Pull Request

0 comments
Metal components move through three inspection stations toward a green finished component on a workshop bench.

A coding-agent workflow should carry a defined problem through reproduction, a bounded change, verification, and review. At every transition, preserve the evidence needed by the next person: what was requested, what changed, which checks ran, and what remains uncertain.

The agent's final message is not the acceptance test. The patch and its observed behavior are.

This guide follows a small Python parser fix through a disposable local repository. The reproduction, patch, tests, worktree, and diff inspection were executed. The pull-request description and review handoff were prepared locally; no remote pull request, CI run, human approval, or merge was performed.

Make the issue executable as a decision

The synthetic issue is:

The page-limit parser accepts negative and out-of-range values. Accept None as the default 25; otherwise accept strings containing ASCII decimal digits representing 1 through 100. Preserve leading zeros. Reject other strings with ValueError. Preserve the function signature and add no dependency.

That specification resolves choices that a vague “fix pagination” request leaves open. Clamping, trimming whitespace, and changing the default are outside its scope.

GitHub's task guidance for Copilot cloud agent recommends clear scope and acceptance criteria and describes iterating on a branch before opening a pull request. The workflow here is tool-neutral; the actual commands, permissions, and review controls depend on your environment.

For a real issue, inspect the callers and contract before accepting this specification. If a documented client relies on signed or whitespace-padded input, rejecting it may be a compatibility change that needs a separate decision.

Record a baseline and isolate the work

Begin by checking the repository state, instructions, relevant source, tests, and supported test command. Preserve existing edits. Record the starting revision so the final diff has an unambiguous base.

The fixture started with:

def parse_limit(raw):
    return 25 if raw is None else int(raw)

Its two existing tests covered the default and an ordinary valid value. Both passed. That is useful baseline evidence, but neither test expresses the newly specified rejection behavior.

A separate task worktree was created from a synthetic baseline commit:

git worktree add -b codex/reject-invalid-limit ../work

Git's worktree documentation describes how multiple working trees can coexist in one repository. Worktrees help separate file edits; they do not isolate databases, ports, or credentials. The fixture had no remote and used no external service.

In a real project, choose the appropriate base explicitly and establish the environment without copying production secrets into a task checkout.

Reproduce the contract failure before changing behavior

The regression tests covered the default, valid value, boundaries and leading zeros, range errors, format errors, and non-ASCII digits.

A representative assertion was:

def test_range_rejection(self):
    for raw in ["0", "-1", "101"]:
        with self.subTest(raw=raw):
            with self.assertRaises(ValueError):
                parse_limit(raw)

Running python3 -m unittest -v against the original implementation produced seven assertion failures across the six test methods. The parser accepted values the issue required it to reject, including signs, whitespace, and a non-ASCII digit.

The distinction between six methods and seven failures matters: several assertions were subtests. Reporting “seven tests” would blur what actually ran.

Python's unittest documentation describes its test runner and discovery interface. Use the equivalent established command in your own project rather than treating this example as a universal setup.

If a reproduction fails for an unrelated reason, resolve the environment or revise the hypothesis. A missing package or unavailable database does not prove the reported application bug.

Implement the smallest change that satisfies the contract

The first patch passed the six methods, but review found a missing boundary: "0" * 4300 + "1" still raised ValueError. Python limits decimal integer conversion length; the fixture's configured limit was 4,300 digits. A numeric value of 1 can exceed that length when its representation has many leading zeros.

Two more test methods covered long leading-zero inputs and long invalid numbers. The first patch produced two errors in the eight-method suite, for the valid values 1 and 100 with 4,300 leading zeros. Passing the initial tests had not established the full contract.

The final parser was:

def parse_limit(raw):
    if raw is None:
        return 25
    if not raw or not raw.isascii() or not raw.isdecimal():
        raise ValueError("limit must be ASCII digits from 1 through 100")
    significant = raw.lstrip("0")
    if not significant or len(significant) > 3:
        raise ValueError("limit must be ASCII digits from 1 through 100")
    value = int(significant)
    if not 1 <= value <= 100:
        raise ValueError("limit must be ASCII digits from 1 through 100")
    return value

The input contract is str | None. This example does not add branches for arbitrary objects. It distinguishes an absent value from an empty string and validates the allowed representation before removing leading zeros. An empty remainder represents zero; more than three significant digits cannot fit the allowed range. Only the remaining one to three digits reach int, and the range check rejects values such as 101. This preserves leading-zero inputs without changing the interpreter's conversion limit.

All eight test methods passed after the final change. git diff --check also passed. The final diff touched the parser and its tests; the original checkout remained clean.

Those checks are bounded. They do not test an HTTP handler, a query engine, performance under large inputs, or a deployed service. A real pagination issue may require an integration check that proves invalid input never reaches the query layer.

Review the patch before writing the success story

Read the complete diff against the recorded baseline. Check that the implementation satisfies each acceptance criterion, that the tests would catch the original error, and that unrelated behavior has not moved.

The local review checked that the default still returns 25, the boundary and leading-zero behavior matches the issue, and rejection occurs before the integer is returned. The long-input regression also checks zero-only strings, a zero-padded 101, and 4,301 nines. No dependency or external integration was added.

Do not let the agent's explanation substitute for this inspection. A summary can describe the intended patch even when the actual code differs. Generated files, changed test expectations, dependency updates, and permission changes deserve particular attention when present.

When a finding requires a fix, keep the finding tied to a concrete failure condition. Make the correction, rerun the affected checks, and review the updated diff. Earlier test results no longer establish the state of newly changed code.

Give the reviewer a usable pull request

A prepared description for this example is short because the scope is small:

The page-limit parser accepted values outside its documented input contract. This change preserves the default and valid decimal inputs while rejecting signed, padded, non-ASCII, zero, and over-limit strings.

Local validation: python3 -m unittest -v passed all eight test methods. Rejection checks failed against the original parser, and long-leading-zero regressions failed against the first patch. git diff --check passed. HTTP integration and remote CI were not run because the fixture contains only the parser.

Attach the issue or acceptance criteria and identify the relevant risks. Do not add a passing CI badge, a deployment claim, or a reviewer approval unless it actually exists.

Creating the remote pull request is a separate effect. Perform it only within the user's or team's authorization and verify its base, head, diff, and saved description afterward.

GitHub's review documentation distinguishes comments, approvals, and requests for changes. A local self-review is none of those remote review decisions. “Reviewed” should identify who reviewed which revision and what outcome was recorded.

Close the loop without silently merging

After review feedback, verify findings against the current code rather than applying every suggestion mechanically. If feedback changes scope or the public contract, make that decision explicit.

For an approved change, the team's merge rules, checks, and authorization still apply. A successful agent run does not grant itself permission to merge or deploy. Preserve the final evidence so a maintainer can tell the difference between implementation complete, checks complete, review complete, and delivery complete.

Execution note: the local fixture ran October 28, 2026 with Python 3.14.2 and Git 2.54.0 (Apple Git-157). It demonstrates the local workflow through a prepared review handoff, not the performance of a particular coding agent or an actual remote PR lifecycle.

You may also like