A person's hands at a workbench: one holds a brass pipe fitting, the other writes on a clipboard with a red pencil, and more fittings stand on the table. A symbol of verification: to know whether a part is right, someone has to examine it and write down what they observed.
Examining a single part and writing down what it does. Producing the part is no longer where the effort lies.

In August, Fabien Potencier disabled pull requests on the Symfony Language Tools repository. Shortly afterwards, Taylor Otwell disabled GitHub Issues on most Laravel packages. In the comments under Fabien's announcement, the two decisions were soon described as opposite moves.

I read them as the same conclusion. Both found that the diff has become the cheap part of a contribution. They only disagree about which tab on GitHub should survive. I think a question that neither announcement answers is more important: in what form does an understanding of the problem reach a project when nobody has to understand anything to produce a diff? My answer is a failing test.

Both maintainers start from the same premise

Both changes are narrower than a quick read suggests. Taylor's applies to most Laravel packages, but not to the main laravel/framework repository. Fabien's applies to exactly one repository, a young project that is still in beta, and the announcement states that nothing changes for symfony/symfony. Fabien calls the change an experiment and promises to reopen pull requests if it does not work out.

What the two share is the premise. Taylor recommends that anyone who hits a bug describes it to a coding agent and opens a pull request, even if the code is not great, because “code can be iterated on”. Fabien writes that an agent can often produce the implementation quickly, and that “understanding the problem is harder”. In Fabien's view, only the reporter knows the application, the configuration and the action that triggered the failure. That context is what a maintainer cannot generate.

Both statements rest on the assumption that producing a diff has become cheap. They differ in which of the two GitHub artefacts survives, and everything else follows from that decision:

Laravel packages Symfony Language Tools
Surviving artefact The pull request The issue
Who runs the agent The contributor, in their own environment The maintainer, with private agent memory and internal test infrastructure
What the maintainer receives A proposed fix, plus whatever problem description comes with it The observed failure: versions, configuration, logs, screenshots
How it is verified By reading and understanding a diff By reproducing a report
What the contributor gives up Reporting a bug without a patch Contributing code
What comes in uninvited Plausible but wrong fixes Speculative, mass-generated reports

Neither column comes for free. Fabien's announcement itself says that mass-generated speculative issues are not useful. Daniel Stenberg has been writing about bogus security reports generated with LLMs for curl's bug bounty programme since the beginning of 2024. And every maintainer who has reviewed a large pull request from a stranger knows what a plausible but wrong fix costs.

The diff was never the contribution

Fabien closes the announcement with a historical observation: e-mail patches gave way to pull requests, and perhaps issues combined with coding agents can become another option for some projects. I want to take that comparison one step further.

The e-mail patch solved a transport problem. It got a change from one computer to another in a form that patch could apply. The pull request solved a review problem. It put the change next to a discussion, a CI result and a merge button, and it turned “send me a patch” into a workflow that a newcomer could complete in an afternoon.

Each transition changed what a contribution was optimised for. The transition we are in now optimises for a third thing: the cost of verification.

As long as writing a diff took hours, whoever wrote it usually understood it by the time it was finished. What made a pull request valuable was the understanding that came from writing it, and the diff was the evidence of that understanding. Coding agents have removed the hours, and with them the understanding that used to come as a by-product.

The maintainer still pays for understanding

In Faster than understanding I described an experiment from earlier this year. An agent implemented the ACPATH metric, which is described in a paper, in 15 minutes. I then spent hours trying to establish whether the implementation was correct, and I could not be certain. The code looked clean and the tests passed, but neither told me what I needed to know. An agent can generate code in a domain faster than a human can understand that domain.

Now apply this asymmetry to Taylor's workflow. A developer hits a bug in a Laravel package. Usually they are not an expert in that package; if they were, they would probably fix the bug themselves. They describe the symptom to an agent, and the agent produces a patch that makes the symptom go away. Then they open a pull request. The maintainer receives a diff that its author did not understand, for a problem that nobody has described directly.

Generation has moved to the contributor. Understanding has not moved at all: it still sits with the maintainer, and it now arrives in a more expensive form. Working out what a bug is from a good report is diagnosis. Working out what a bug was from a proposed fix is diagnosis plus code review, and the author of the code cannot answer questions about it. Taylor writes that the pull request still documents the problem. I disagree: a pull request written by an agent documents the agent's hypothesis about the problem.

“Code can be iterated on” holds in a project with the kind of safety net I described in Everything we have: static analysis, tests, property-based testing, and mutation testing that checks whether the tests would notice a wrong fix. In such a project, a bad first patch is cheap, because the tooling rejects it before a human has to look at it. In a package with a thinner safety net, the maintainer does the iterating, and the dangerous patch is the plausible one that passes the existing tests.

Fabien's workflow avoids the asymmetry by never separating generation from verification. The same person, with the same agent and the same private context, produces the fix and judges it. Only in this arrangement can the person who reviews the code also say why the code is the way it is.

Private context makes one person the pipeline

Fabien's strongest argument is context. The agent on the maintainer's side does not start from a clean checkout. Local memory records past decisions, failed experiments, benchmark results and project constraints. Internal tooling runs the language server against all private Symfony applications that power symfony.com. No contributor's agent has access to any of that.

The division of labour follows from this. The contributor sends what only the contributor has: the environment in which something failed. The maintainer adds what only the maintainer has: the accumulated context. When an issue is selected, it becomes the brief for the implementation. Agents write the code, the tests and the documentation. A maintainer reviews and validates every change.

I recognise this workflow. In Beyond Best Practices I described what pair programming turns into with an agent: a human writes tests that specify behaviour, and the agent generates the implementation. Fabien extends that pair outwards, so that part of the specification comes from a reporter who never sees the code.

In the same article I warned about structural dependencies on systems whose internal decision-making remains opaque. Every fix to Symfony Language Tools now passes through one person's agent memory and one person's test infrastructure. That is efficient for as long as that person is available, and nobody outside can retrace the path from a report to a release. In Composer and Packagist under supply chain stress I asked who owns our supply chain. That question belongs here as well, even when the answer is a maintainer we trust.

Fabien confines the experiment to a young project whose code was mostly written by agents from the start. I agree that it belongs there. I am less sure that it will stay there.

Neither model trains the next maintainer

Neither announcement says where the next maintainers will come from. Almost every maintainer I know, including me, arrived through the same door: a bug that annoyed them, a small fix, a review that taught them something, a second fix, and eventually commit access. The pull request was the on-ramp. It was slow and often frustrating for both sides, and it was how expertise passed from one generation of a project to the next.

An issue-first project closes that door. Someone can write excellent bug reports for years without ever touching the code, because the project offers no way to do so. Fabien wants to see what gets lost when contributors cannot submit a pull request, and intends to look at the quality of the issues, the time needed to turn them into fixes and the experience of the people reporting problems. I think the on-ramp is what gets lost. None of these measurements will show it, because the loss only becomes visible years later, when the project needs a new maintainer.

A project that only accepts pull requests keeps the on-ramp, but a contributor who left the understanding to an agent learns little on it. The review teaches them nothing they can carry over to the next fix. In Beyond Best Practices I cited an analysis of millions of GitHub commits: junior developers rely more heavily on AI-generated code than their senior peers, while the measurable productivity gains accrue almost entirely to experienced developers. Taylor's model applies that finding to the way an ecosystem recruits its maintainers.

Both models make a project easier to maintain this year and harder to hand over in ten years. Open Source has always depended on a small number of people who understand a codebase deeply, and neither experiment produces more of them.

Both models need a failing test

A contribution that meets the actual requirements of both maintainers has four properties.

It carries the reporter's context, because that is the part the maintainer cannot generate. It is unambiguous, because an agent will implement whatever the words allow. It can be verified by running it, without anyone having to understand it first, because understanding is the resource we are short of. And it is additive, so that accepting it cannot silently change behaviour that someone else depends on.

A prose issue has the first property and, if the reporter is careful, the second. It has neither of the other two: the maintainer still has to build the reproduction. A pull request has the third at best, and only if the fix is small enough to be verified by running it. It routinely lacks the fourth, and fixes written by an agent lack it most often: an agent optimises for a green test suite, and the fastest route to green is to edit the assertion that is red.

One artefact has all four properties: a failing test.

<?php declare(strict_types=1);
namespace Acme\Money;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\TestCase;

#[CoversClass(Money::class)]
final class AllocationTest extends TestCase
{
    public function testAllocatingThreeWaysDoesNotLoseACent(): void
    {
        $shares = Money::EUR(100)->allocate(1, 1, 1);

        $this->assertSame(
            100,
            $shares[0]->amount() + $shares[1]->amount() + $shares[2]->amount(),
        );
    }
}

Read this test as a bug report. It states which call, with which arguments, produces which wrong result. There is nothing left for an agent to interpret. In Beyond Best Practices I argued that tests eliminate the ambiguity of a specification, and a bug report specifies what the fix has to achieve. The maintainer verifies the report by running it, which takes seconds, instead of by understanding a diff, which takes as long as it takes. And the test is purely additive: it adds one expectation and changes none. That is the property I argued for in Untouched tests are half the proof. Once the fix lands, the same test becomes the regression test, and the path from symptom to commit is contained in one place.

A failing test also carries the reporter's context, reduced to the part that matters. A reporter who cannot get the failure into a test has not found the failure yet, only its surroundings. Fabien points out that a minimal reproducer can be difficult to create for an editor integration. For a library, it is almost never impractical. For bugs that only appear above the level of a single unit, PHPUnit's own test suite uses end-to-end tests written as .phpt files.

This changes the question of Laravel versus Symfony. If the contribution is the failing test, then who turns it green is a detail of the workflow. Taylor can let the contributor's agent do it and receive a pull request whose first commit is the test and whose second commit is a fix that the test already vouches for. Fabien can let the maintainer's agent do it and receive an issue that can be executed. Which tab is disabled then matters much less, because what the project receives in both cases is the test.

PHPUnit requires an accountable human

PHPUnit's contribution guidelines require that a human is accountable for every contribution. Issues and pull requests must be opened by a human, and that human must not let a coding assistant act or speak on their behalf, neither when opening them nor when responding to questions or review feedback. I am not willing to communicate with an agent, and a human who merely passes on what an agent writes is no different.

Whoever opens an issue or a pull request states that they have read and understood its contents, consider it correct, and are able to explain and defend it. Whoever has not reproduced the bug they report says so explicitly. Whoever used a coding assistant to find, reproduce or write up an issue, or to generate code for a pull request, discloses how it was used. A disclosure does not disqualify a contribution. It changes how we triage and review it, just as it matters whether a bug report comes from a minimised reproducer or from a production stack trace.

The guidelines ask for steps to reproduce and sample code in a bug report, and for tests in a pull request. They do not say what I argued above, so I will say it here. The best bug report for PHPUnit is a failing test. A pull request that contains nothing but a failing test is a contribution I am glad to receive. When a report arrives as prose, the first thing I try is to turn it into a test. When that does not work, the failed attempt usually tells us more than the report did.

I review bug fixes against the additive criterion first. If a fix rewrites an existing expectation, the review starts with the question why, and the three diagnoses from Untouched tests are half the proof apply. Whether an agent typed the fix does not change that criterion. What I want to know from the person who opens the pull request is which failing test they started from.

Contribution models will follow the shape of a project

Both experiments are a few weeks old, and both can be undone with a repository setting. What follows are predictions.

Taylor suspects that most open-source libraries will soon operate the way the Laravel packages now do. I expect contribution models to split by the shape of a project instead. Issue-first fits a young project whose code was mostly written by agents and in which the private context of a single maintainer dominates. PR-first fits an ecosystem of many packages and integrations, where a failure often only occurs in the reporter's environment and the contributor's agent can at least run there. Most mature projects will do neither, and will sharpen the rules they already have. I would not adopt either model just because a well-known project tried it.

Authorship becomes an open question. In the comments under Fabien's announcement, Alexander Schranz suggested crediting the authors of an issue, and those who added valuable comments to it, as co-authors of the merge commit with a Co-authored-by trailer. I think that is a sensible answer. Otherwise, the contribution history of a project will stop showing who understood what, and projects will have to decide deliberately what an entry in that history means when the diff was generated.

And the skill that matters for contributors will once again be the one that mattered on the mailing lists before pull requests made it optional: isolating a failure, stripping it down to its essentials and stating it in a form that someone else can run. On the mailing lists, that was called a reproducer. In a PHP project today, it is a failing test.