Enter the project manager
Somebody reviewed one of our pull requests properly, line by line, and approved it — while the author was still pushing to the branch. The approval landed on a version that no longer existed. That is a symptom; the disease is that a code review here could be requested four different correct ways and therefore reached nobody. On queues that aren't queues, what coding agents did to our capacity to review, and why the fix begins by deleting one of the places we ask. Enter the project manager.
My degree is in management accounting. Not computer science.
It’s the inward-facing kind: financial accounting produces the numbers the outside world is shown, management accounting produces the numbers the people running the thing need in order to decide. Efficiency, planning, budgeting, management — four words that look like four subjects and are really one question asked four ways. Where does a scarce resource go, and how would you know if it went somewhere it shouldn’t have.
Point that at software delivery and the word that changes shape first is efficiency. It stops meaning working faster and starts meaning not spending the same scarce resource twice. And the resource isn’t money, and it isn’t developer hours. It’s attention — the sustained kind needed to read somebody else’s code properly.
My title is Solution Architect and Technical Project Manager. People ask about the first half, and it’s the easy one: I design the architecture for the new services we build, and redesign the parts of what we already run that need it. That’s the work with drawings attached.
Nobody has ever asked me anything about the second half, and it’s the half that decides whether the first one ever ships. An architecture nobody can find the task for, get a review on, or hear about when it’s ready is a drawing. So: the roadmap, task management, GitHub, and how people find out that something has happened.
I’ll give the verdict first, because a post that withholds it to sound balanced is wasting your time. The way we request and get code reviews is a mess — no single place answers the question “what is waiting for me”, and there is no mechanism anywhere in it except people remembering. At one point we had over a hundred pull requests open and marked ready for review. I helped build that. It does not work.
And an accountant would give that pile a less forgiving name than backlog. A hundred finished-but-unmerged changes is inventory: work already paid for in full, from which we have realised nothing, sitting in a warehouse and quietly depreciating as the codebase moves on underneath it. It’s the most expensive thing in the department, and none of it is on anybody’s books.
Getting a review sometimes meant chasing a colleague
Not always, and not everybody. But often enough to notice, and often enough that it became a part of the job I hadn’t expected to be doing.
A pull request is open. It’s small, it’s ready, and it’s been sitting. Everything is where it should be: the branch is pushed, the description says what changed and why, the reviewer is on it. Nothing happens, so I go and ask. Sometimes I ask again.
Some of ours have been sitting for months. Not blocked, not disagreed with, not waiting on a decision. Just open, since spring.
I want to be exact about the failure, because the obvious explanation is wrong. Nobody is refusing. Nobody thinks the change is bad. There is no disagreement anywhere in this, which is what makes it so maddening — it’s pure friction, producing no information and no decision, only delay.
We made writing code cheap
We use coding agents heavily, and this blog has spent several posts being enthusiastic about them. Here’s the invoice.
An agent lets one person keep several things in flight at once. That’s real, and it’s most of the productivity gain. But work in flight isn’t free. Each thread occupies a slot in your head, and the slots don’t multiply just because the typing got faster.
So the throughput of producing changes went up and the capacity for reviewing them didn’t move at all. Reviewing is still one human, reading, at human speed, and it needs exactly the kind of sustained attention that parallel work is designed to break up.
Two things follow, and they compound. More pull requests arrive. And the people who should read them are each holding three threads of their own, which is precisely the state in which somebody else’s branch is easiest to postpone.
I’ve written here before that agents made rewrites cheap to produce and no cheaper to review. This is the same sentence pointed at ordinary work. The bottleneck didn’t disappear when we got faster. It moved to review, and we didn’t move with it.
We never chose
The second cause has nothing to do with AI and everything to do with us.
We have a task system. We have GitHub. We have a chat channel specifically for posting pull requests that want reviewing. All three are correct. All three are in daily use. And a review can legitimately be requested in any of them — as a GitHub review request, as a comment on the task, as a post in the channel, or by turning round and saying it out loud.
Four routes for one request. Each defensible on its own; the combination indefensible.
This is worse than plain chaos, because from every individual angle it looks fine. Each request went somewhere reasonable. Each system is being used as intended. Nobody is doing anything wrong. But no single place holds the answer to “what is waiting for me right now”, so nobody has a queue. They have four partial ones, and assembling the real queue costs more effort than reading the pull request would have.
That’s the whole failure, and it isn’t a tooling failure. Every one of those systems works. We are drowning in not having chosen between them.
The doorbell
Underneath the routing problem is a smaller, meaner one.
A comment on a task notifies nobody. People write a careful comment, on the correct task, with the right detail, and then wait. Nothing happens, because nobody was told. The comment is sitting exactly where it belongs and exactly where nobody is looking.
So my colleagues told me, reasonably: if you need me to read something, tag me. Fair. A mention is what turns a message into a notification — not the message, not the channel, the mention. There’s a trap inside that, too: nobody is notified about their own message, so if you post on somebody’s behalf under your own name, the one person guaranteed not to hear it is you.
I started tagging. It helped. It did not fix it.
A notification is a doorbell, and a doorbell only works if somebody is home. Tag a person who is three threads deep in their own work and you have successfully delivered an interruption they will consciously defer — and a deferred interruption isn’t stored anywhere. It’s gone the moment the next thing arrives.
It would be easy to write this as a complaint about colleagues who forget. That isn’t what’s happening and it isn’t fair. If a process depends on people remembering, the process has already failed — the remembering is load-bearing, and human memory is the wrong material for that. My colleagues are not the defect. A system whose only mechanism is “tag them and hope” is the defect, and I built that system.
“Ready for review” doesn’t mean anything
Go back to that number. Not a hundred pull requests open — a hundred marked ready, which is a hundred assertions that a change was finished and waiting on somebody.
Then this. Somebody picked one up, reviewed it properly, line by line, and approved it. The author’s reaction was that the task wasn’t done: he was still working on it, and had been pushing to the branch while it was being read.
So the approval landed on the previous version. The reviewer spent their attention on code that no longer existed by the time they finished — and unless the repository dismisses stale approvals when new commits arrive, that green tick is now sitting on code nobody has read. Worse than an unreviewed pull request, which at least looks unreviewed.
Nobody broke a rule. That’s the point: there wasn’t one. “Ready for review” was a label anyone could apply at any moment, meaning anything from “this is finished, please read it” to “this branch exists”. A signal that can mean two things means nothing, and a hundred of them mean nothing a hundred times over.
Which also explains the months. A pull request open since spring isn’t one somebody keeps deciding to postpone; nobody is deciding anything about it. It stopped being a request the day it became indistinguishable from the other ninety-nine — after that it’s a row in a list, and a branch quietly ageing against a codebase that has moved on underneath it.
What I got right, and what it didn’t touch
Everything I introduced here comes out of one sentence: each fact appears in exactly one artifact.
What is needed and why lives in the task. What changed and the reasoning lives in the pull request. That a review is wanted lives in the request for it. Why a non-obvious line is the way it is lives in a code comment.
The point isn’t tidiness, it’s cost. The same fact in four places costs the writer, who retypes it, and every reader afterwards, who now has four sources that agreed on the day they were written and have been drifting since. Restating the ticket in the pull request, and the pull request in the chat message, is the single biggest source of bloat in what we produce — and bloat isn’t a style problem. It’s why nobody reads any of it.
Where that has worked is documents. Task descriptions cap at forty lines, structured as Problem, Expected, Acceptance criteria. Pull request descriptions cap at 250 words, and a trivial change gets five lines and no headings: no test counts, no “CI green”, no file inventories, because CI reports its own status and the diff lists its own files. Writing those out again just produces a staler copy of what’s already on screen, and pushes the one thing only the author knows — the why — further down the page.
My favourite, and the one people argue with: when your understanding of a task changes, rewrite the description. Don’t append. No “revised”, no “supersedes”, no dated changelog inside the description. It feels like destroying evidence, and it isn’t — the tool keeps history, the same way git does. The diary version leaves the reader working out which paragraph is still true, which is the exact job the description existed to do for them. A task says what is true now.
That rule works, because it governs where a fact is written, and writing is a deliberate act a convention can catch. It has no grip whatsoever on where a request gets answered. A convention can tell you where to put something. It cannot make anybody pick it up.
Which is the honest shape of my first months here. I wrote rules for the artifacts, because artifacts are the part a newcomer can change alone, and then assumed the human half would follow the documents into order. It didn’t. The documents are genuinely better. The flow around them is the same mess it was, and now it’s a mess with well-formatted contents.
Choosing is subtraction
Choosing sounds like adding a rule. It isn’t. The whole move is subtraction.
One place to ask, and it’s GitHub. A review is requested on the pull request, using GitHub’s own review request. That’s the entire routing decision.
No code review channel. This is the part that costs something, because the channel works — people post there, it’s a reasonable thing to do, and I’m the one who was asking colleagues to use it. It goes anyway. A second correct place to ask is what created the problem, and keeping it because it’s harmless on its own is exactly how we got to four routes. You can’t half-choose.
GitHub wins for reasons that have nothing to do with it being better software. The request lives next to the thing being requested, so there’s no gap between the notification and the work. Its state survives everything — a restart, a holiday, a forgotten mention. And it doesn’t depend on anybody remembering, which is the property every other option lacked.
The task system keeps its job, which is status, not requests. The task moves through its statuses so the board is an honest picture of where development actually is. It isn’t where you ask for a review; it’s where you look to see what’s happening.
Soon the two stop being updated by hand. When a pull request is merged, the task should move from code review to ready for release on its own. That’s the one-fact rule pointed at status instead of prose: whether something is merged is a fact GitHub already owns, and every manual status update is a second copy of it, kept in step by a human who is busy. Copies drift. A projection can’t.
Then the habit: your pull request inbox. GitHub keeps one at github.com/pulls/inbox — everything waiting on you, in one list, with a number on it. Check it often, and specifically check it before you start a new task. That placement does most of the work: it puts reviewing at the moment you’re between things rather than asking you to interrupt yourself, and it means the queue gets shorter every time somebody starts something new instead of longer.
And a pull request stays a draft until its author believes it’s ready to release. Not ready to read — ready to release, with the change already on staging and through acceptance testing. Draft is the default, and a draft costs nothing to open.
That inverts what we had. “Ready for review” used to be the default state, which is why it carried no information and why somebody could approve a version the author was still working on. Make ready a deliberate act and it starts meaning something again — and “am I still pushing to this?” becomes the author’s question to answer up front, instead of the reviewer’s to discover afterwards.
One task, one pull request. If a task needs more than one, it isn’t one task: break it into subtasks and give each of them a pull request. I expect more argument about this than anything else here, and it’s the one I’d defend hardest, because it isn’t really a rule about linking. It’s a decomposition forcing function. The moment you find yourself opening a second pull request against the same task, the task has told you it was too big — and it tells you at the point where splitting it is still cheap, rather than four months later when somebody has to review the result.
It’s also what makes the automation possible. “When a pull request is merged, move the task to ready for release” only means something if a task has one pull request. With three it’s ambiguous, somebody has to arbitrate, and we’re back to a human keeping two systems in step by hand.
For genuinely large work, the parent task gets a feature branch and the subtask pull requests target that branch instead of master. The parent merges once, when the whole thing is done. That gives one place to watch the state of a big piece of work without anybody pretending it was ever a single change.
I should be straight about the tension in that last one. I’ve argued elsewhere for collapsing to a single long-lived branch, and a feature branch is a long-lived branch. The distinction I’d hold is duration and ownership: this one exists for the life of one parent task and is deleted with it, where the development and staging and production branches we’re retiring exist forever and quietly accumulate divergence. That’s a scoped exception rather than a reversal — but it is an exception, and if I find these branches still alive months from now, I was wrong about it.
The weak point is worth naming. Two of these are mechanisms and one is a habit. GitHub as the single route is enforceable, draft-by-default nearly so. “Check your inbox before you start something” is a habit, in a post largely about how the last thing I built on a habit didn’t hold.
What’s different, I think, is that the inbox is an actual queue rather than a request to remember. It has a number, it’s the same number tomorrow, and it doesn’t quietly empty itself when your attention moves. That’s a better foundation than a chat channel you have to think to open. But it’s still a habit, and if it fails it will fail the same way.
What a piece of work actually looks like
A large task arrives, or a small project. Then, roughly in this order:
I design a solution for it. I break that into tasks and subtasks. I check that every application the thing touches can talk to the others in the way the design assumes — which is where most of the unpleasant surprises live, because two systems agreeing in a diagram is not the same as two systems agreeing about a payload. I check it’s secure, at the boundaries where it’s easy to be lazy. And I keep asking whether the result is still worth something to the people who asked for it, because it is entirely possible to design something correct, well-decomposed, secure, and of no value to anybody.
That’s both halves of the title in one piece of work, which is the honest reason they’re on one business card. The design is the architect half. Everything after the first sentence is the project manager half, and it’s longer.
I plan most of it with Claude. Not to write the plan — to interrogate it. I’ll put an approach in and ask what’s wrong with it, ask for the alternatives I haven’t thought of, ask what each one costs. It’s very good at that, and it’s fast enough that evaluating three designs is no longer a thing you skip because you’ve already sunk a morning into the first one.
It has one failure mode you have to actively manage: it is agreeable. Push a mediocre idea confidently and you will often get help making it better rather than a flat “this is the wrong shape”. So when a decision matters, or when I’m genuinely unsure, I take it to a second and sometimes a third colleague.
That isn’t a formality. A model will help me think, and it will not tell me that I’m the third person this year to propose this, or that the last attempt failed for a reason nobody wrote down. Institutional memory doesn’t live in a context window. It lives in the people who were here.
Slicing it three ways
The other initiative is duller to describe and probably matters more: projects get broken into sub-projects, each small enough to actually finish, each with the business involved during it rather than at the end.
The reason isn’t planning neatness. A chunk that takes four months produces exactly one moment where anyone outside the work can react to it, and that moment arrives after every expensive decision has been made. You don’t get feedback, you get a verdict. It produces something else, too, which this post has already spent two thousand words on: a pull request nobody can review. The mountain isn’t only a habit problem. Some of those branches are simply too big to read, and they were too big on the day they were opened.
There’s a second reason, and it’s about people rather than feedback loops.
The plan is long. One language behind everything, the same shape in every repository, two admin portals becoming one — that’s measured in seasons, and I’ve said as much in public. Stated whole, a plan like that motivates nobody. It’s a wall. And we have a plethora of them queued up, each genuinely worth doing, each months deep.
Breaking them down doesn’t make them shorter. The months are still the months. What changes is how often you get evidence the thing is working, and that turns out to be what decides whether people keep going. A dozen multi-month projects is a mood. The same dozen, with the first slice of each finishing inside a fortnight, is something you can hold in your head and keep walking towards, because you can watch it move.
That’s underrated as a reason to plan properly. Not so the plan is correct — it won’t be — but so the distance between “we agreed to do this” and “something happened” is short enough that people stay pointed at it. That’s forecasting rather than planning: you don’t get better by predicting harder, you get better by shortening the interval before reality tells you something. The caution is that a well-broken-down plan is easy to keep following after it stops being right. The slices are small so you can change your mind between them, not so you can stop having to.
This is also the least visible part of the job. I help our product manager build and maintain the roadmap, and most of that work isn’t deciding what goes on it — ideas are not the scarce resource here — but deciding what a line on it is allowed to mean. An entry that can’t finish inside a quarter isn’t a plan, it’s a wish with a date attached. Breaking those entries down until each one is something that can actually complete, and then staying on them until they do, is most of what “making sure we reach our goals” turns out to consist of. It’s unglamorous, and it’s the difference between a roadmap that describes the year and one that predicts it.
What makes this hard is that a slice has to satisfy three audiences at once.
It has to be beneficial for users — deliver something somebody outside the team can use, or at minimum see. It has to be manageable for developers — small enough to hold in your head, review in one sitting, and abandon without grief. And it has to be sensible for the business — a step somebody can say yes or no to, in terms that aren’t about code.
Miss any one and the slicing does nothing. The failure I’ve seen most is cutting along developer-shaped lines, because those are easiest to plan: all the data models, then all the endpoints, then all the screens. Perfectly sensible engineering order, and it produces months of work nobody outside can evaluate, because none of it is a thing yet. You’ve divided the work without dividing the risk.
Two shapes this takes for us.
Release it to ourselves first. A new analytics tool goes to GolfNext employees only, for a week. Then it expands, and what it expands into is shaped by what everybody said. This blog has admitted before that we have no gradual rollout mechanism and that our first real ring is geography — a few nearby clubs, and a recovery plan involving twenty minutes in a car. Staff-first is a ring that sits before that one, costs nothing, and could exist this week. It isn’t a canary deployment. It’s the cheapest possible way to find out something obvious is wrong before a club does.
Refactor a module at a time. Not the whole system, and not an arbitrary slice either: one cut where all three constraints hold at once. The users get something, the developers get a piece they can finish, the business gets a step worth funding. That’s the same discipline as the seams-before-rewrites rule I’ve argued for elsewhere. What’s new is that finding the cut is the work. It isn’t a planning formality you complete before the real job starts.
I’d rather not oversell it. Slicing badly is worse than not slicing, because a bad cut ships half a feature and teaches everyone that incremental delivery means unfinished things. The skill is in where the knife goes, and I don’t think that’s something you can write down as a rule. It’s the part of this job I’d defend as genuinely requiring judgement — which is an uncomfortable thing to admit in a post otherwise arguing that the answer is fewer choices and more mechanisms.
The question underneath all of it
We’re planning to change the architecture and the stack: one language behind everything, one shape in every repository. I’ve written about that elsewhere. What matters here is the reaction. My colleagues agree it’s a good idea. Not grudgingly. They think it’s right.
And almost nothing moved in that direction before Johann and I turned up.
That bothered me for a while, and the lazy explanations are all wrong. It wasn’t permission — this company says yes to an idea in a day, and I’ve had ideas approved here faster than I could have booked a meeting at my last job. It wasn’t ability. And it certainly wasn’t that nobody had thought of it: these are people who know this estate far better than I do, and I’d bet most of what’s in our plan had been said out loud, in some form, years before I arrived.
So why did nothing happen?
Because agreement isn’t a mechanism. A thing everybody agrees about and nobody owns has no way to start. There’s no disagreement to resolve, so no meeting gets called. There’s no customer waiting, so it never wins a Tuesday against something that has one. It doesn’t help that the work looked like an undifferentiated multi-month blob with no early payoff, which is exactly the shape nobody volunteers for — and exactly what breaking it down is for. It simply stays true and undone, and being obviously correct does nothing to move it. You can watch the same effect in a hundred pull requests nobody disagrees with.
Because for most of this company’s history, nobody’s job was bigger than one project. A lot of our software was built by external partners, and they were good at it. But you engage a partner to deliver a thing. Nobody engages a partner to fix what sits between the things. Cross-cutting work has no client, so under that model it doesn’t get built, and thirteen years of it accumulates in the gaps.
Because the people who could have done it are the people everything else queues behind. That’s the bus factor from another side: whoever knows a system best is who every request for it waits on, so they have the least slack precisely because they have the most knowledge. You don’t restructure an estate from inside that.
And because the year has a shape. Golf is seasonal. The clubs are busy in summer, so we’re busy in summer, and the slack is in winter — which makes the window for structural work narrow, annual, and easy to spend on something louder.
Which brings me to the uncomfortable part, and to the reason for the second half of my title.
What Johann and I brought was not better ideas. It was an empty queue and permission to ask why. A newcomer carries no legacy backlog for their first months, is expected to ask stupid questions, and hasn’t yet internalised any of the local “that’s just how it is”. That’s a real resource with a short shelf life; every week here spends a little of it.
So the project manager half isn’t there to impose process on people who were doing fine without it. It’s there to own the things everyone agrees with and nobody has. Somebody has to convert agreement into a task with a name on it, and that turns out to be a job rather than an afterthought.
Is any of it more enjoyable? The documents are. A task you can read in a minute, a pull request that tells you why instead of what, and a great deal less retyping of things already written down somewhere else. The review flow is not more enjoyable. It’s diagnosed, which is not the same thing, and every fix above is at most a fortnight old.
Which is why the queue is an uncomfortable thing to write about. It’s the same failure as the one I’ve just spent four paragraphs explaining, in my own area, on my watch.
Everybody agrees those hundred pull requests should be reviewed.
