HOW WE REPLACED OUR HIRING SPREADSHEET
For a long time, our recruitment ran on a spreadsheet. It was a big file and a hard one to read. If you knew the name you were after you could search for it; anything else meant scrolling through thousands of rows. Every state change, every note about expectations, every detail from a CV was typed in by hand, by someone, one field at a time.
If you have ever applied to us, you moved through that process. You never saw the file. That is the part I keep coming back to: the people most affected by a tool like this are the ones who never open it.
We build software for other companies for a living. Earlier this year we built something for ourselves — an applicant tracking system called InfiniHire, to replace the spreadsheet. I built it in six days spread across two months, and none of the code is mine in the way that used to mean.
What follows is what that changed, what it cost, and the places where I do not know the answer. There turned out to be more of those than I expected.
What a morning looks like now
What did it actually change? Something smaller than “it transformed hiring,” and easiest to describe as a morning.
Before, you opened the file and scrolled. If you had an interview that day, working out which one meant reading rows. Everything you learned in it — what someone is looking for, the things from their CV worth remembering — you typed into cells in whatever shape seemed right at the time, and so did everyone else. Finding out what a colleague had already asked someone meant asking the colleague.
Now you open a board and it shows you what today needs. A candidate’s history is on one screen instead of assembled from a row. You can search. You can see that a colleague has already spoken to someone, and what they made of it.

The running application, with invented candidates — the real ones are not mine to show. This is what replaced scrolling a file to work out where things stand.

The same invented candidate, one screen. The timeline is the part that used to be a row you reassembled by reading across it.
And the thousands of rows came across, so there is no second file anyone still opens for the old stuff. Around 250 candidates have moved through it since January.
What “six days” means
That number is the easiest thing here to misread, so it is worth defining before it does more work.
Six days is the count of days on which I committed — not hours worked, and not elapsed time. The first of them was in June and the last was in August. Planning happened before any of them, and a spike to prove out one awkward part of the database design ran two months before the first commit.
The way the commits cluster is the interesting part:
- Day one — fifteen commits, covering all twelve of the plan’s units of work. Not twelve of some larger number: the whole plan, scaffold through deployment hardening. This is where the application got built — though day two had to go back and finish the wiring on one of them.
- Day two — the bulk of the end-to-end suite, the wiring that stops a candidate moving to the next stage until the required assessment is filled in, two rounds of CI setup, and a code-review fix. The suite had started the day before.
- Then nothing at all for seven weeks.
- Days three to six — a different kind of work entirely: infrastructure hardening, fixes that came out of actually using the thing, and a round of review.
I spell this out because “we built it in six days” is the sentence most likely to be lifted out of a post like this one, and it would be lifted as though it meant a working week. Most of the application arrived on day one. Most of the commits did not.
What gets cheap, and what doesn’t
There is a version of this story where the interesting part is that building became cheap. I spent a while resisting it, because the tidier claim is the one I wanted to make: that agent-assisted building changed whether we could, not whether we should.
That claim is wrong, and this project is the counterexample. Cheap building is the reason we never seriously priced the alternative. It did not make the decision affordable. It made the decision.
If cheapness can move a decision that easily, the useful question is what it cannot move. Writing the code is the part getting cheaper. Three things did not get cheaper alongside it:
- Knowing which parts of how a business works need to be enforced by software rather than remembered.
- Knowing which vendor will fit, and which one will quietly reshape the business to suit the product.
- Recognizing that something which runs is nonetheless wrong, which is much harder than recognizing something broken.
This project consumed nearly all of the first and third. The second we did not do at all: we never opened a vendor.
The tooling cannot supply any of them. Nor could it supply what I got wrong next.
The plan had InfiniHire deploying to a public platform-as-a-service. Colleagues pointed out that we already ran our own infrastructure for our own internal tools, and that is where it went instead. The model did not have that because nobody had ever written it down — but I did have it, and I still put the wrong thing in the plan.
Knowing what the company already owns is not a technical skill and it is not one the tooling can supply. It is also not one I reliably supplied.
Why we didn’t buy
We considered buying in the sense that a planning document from March names two well-known applicant tracking products, and nobody ever opened either of them. No trials, no signups, nothing.
The honest reason is not that they failed a requirement. It is the one above: building had become cheap enough that not paying looked easy.
There was something we wanted that I did not think we would get off the shelf — enforcement, meaning software that holds us to our own process while we are working, rather than recording what we did and reporting on it afterwards.
It is the whole difference between a process you have and a process you follow.
I am not going to set out the rules that process consists of. They govern how we handle individual applications, and that is not something to publish offhand.
I never tested whether either product could meet it either. I read their product pages while preparing this post, months after the decision was made and built. They may well do it — I cannot tell from a product page, which is rather the point.
And selling implementation is what we do. Skipping the evaluation is a shortcut something small and internal can afford; on a client’s project it is contracted work that someone signs off on.
The ledger for what we did instead:
- No per-seat license, against a tier I never established and a price I never asked for.
- The subscription that made the build possible was already being paid for.
- The infrastructure was already running.
- Six days of mine — commit-days, so the real figure is larger by an amount I never tracked.
- Everyone else’s time — the plan reviews, the demo, the code read. Untracked like mine, and the largest line here I can name but not count.
- Maintenance, indefinitely. There is no vendor to escalate to now, and one more system that we patch, host and own.
None of that shows up on a bill, which is exactly how internal builds get talked about as cheaper than they are. I can tell you what we avoided paying, not whether it was cheaper.
What we were left with even so: a thing that bends to how we work rather than the other way round, and a company that now knows how to build this way. That is why I still think it was the right trade — on fit, not on cost, which is a judgement rather than a finding.
The process is data, and where that stops
The part I expect to still be true in a few years is not the tooling. It is the shape of the thing we built: the process is data, not code. The steps, the rules for moving between them, and the fields on each form are all rows in a database that anyone who can sign in can change. Nobody has to ask an engineer to change how we work.
The screens exist and they work, but nobody has yet changed a process without me, so this is still a property of the schema rather than a property of the company.
That shape cuts both ways, and the second edge is sharper. While a process is running the software genuinely refuses: it will not move someone along a path the rules do not allow, it will not accept a stage as complete with a required field empty, and it decides both server-side, from the database rather than from anything the browser claims. The rules themselves have no such protection.

What refusing looks like. Culture Fit is the first of several required fields, not the only one — the move does not happen until they are all filled in, and that is decided on the server rather than in the browser.
Any one of us can change what the software enforces, alone, and it takes effect on the next move. That layer records rather than refuses — which is exactly the after-the-fact reporting I said an off-the-shelf product would have left us with. A rule change and the move it newly permits are both on the record, and nothing joins them together.
Everyone’s project, one keyboard
All of it went through one keyboard, and it was mine. One thing I did not do alone: somebody else read the code before any of it went live.
Consultation at both ends, one person in the middle:
- Plans shared first. The brainstorms and the plans went to colleagues before any code existed.
- Pushback. The plan changed before the first unit was written — the deployment target above is the correction I think about most, because it is not a technical one.
- A demo to the whole company when it was finished, and the next round of work came out of that session rather than out of my head — a knowledge base for people who are not in a process yet but might be in six or nine months, and improvements to the analytics page.

The pipeline funnel, which is the top of the analytics page rather than all of it — time in state and rejection analysis sit below. Small numbers: still the invented candidates.
Nothing on that list is one of the debts further down this post. The group chose what it could feel; the rest are mine to carry.
Colleagues have been into the codebase since, but nobody has yet had to make a large, unfamiliar change to it without me. There is documentation meant to make that possible; until someone needs it, “we have documentation” is a claim about the repository rather than about the team.
The team supplied what to build, whether what got built was right, and the willingness to live with it. Typing was the smaller half.
Where the tooling ran out
The whole thing was built by directing Claude through the compound engineering plugin — workflows for going from a rough idea to a specification to a plan to an implementation. It worked well, and it did not work by itself.
The stack: a Next.js and React application, TypeScript throughout, Prisma against Postgres, Tailwind for styling, Playwright for the end-to-end tests. Nothing exotic anywhere in that list — which I suspect is a large part of why any of this worked.
One routing change was built and then backed out, before it reached anyone else. It looked correct and it broke navigation across the whole application — every soft navigation to a candidate’s detail view, the redirect after creating a record, the links out of search.
The suite did not catch it, and the reason matters more than the bug: the specs reach those pages by typing the URL, which bypasses the client router, and where they follow the redirect they assert that the address changed rather than that the right page rendered. They were checking the URL, not the page. My own plan had flagged that failure mode as an unverified assumption before a line was written. Reading the diff did not catch it either. Using it did.
So: not one line of what shipped was typed by a human — not by me, not by anyone. One failure got no regression test, and I agreed with the tooling that there was no clean way to write one. I read a great deal of the rest, rejected some of it, and made the calls it could not make.
Was it faster than writing it myself? I did not measure, so I do not know. METR tried and could not get a clean read: their 2025 trial found experienced developers 19% slower with AI assistance while convinced they had been faster, but when they ran it again a year later, developers were refusing to take part rather than work without it, and between a third and a half of those who did were holding back tasks they did not want to do by hand. The measurement broke because the thing being measured had moved.
The comparison is not the right one anyway. The alternative was never me writing this more slowly. It was buying one of the two products we never opened, or staying on the spreadsheet.
What we said, and what shipped
We wrote the requirements down before any of this was built. Fifty-five of them, numbered. I have not audited them against the code — not one of them, systematically.
The one that matters is authentication. The specification asked for simple email and password login, and explicitly put single sign-on out of scope. What shipped is company-identity-provider sign-on and nothing else: better on the axes we cared about — no passwords to store or reset, account lifecycle in one place — at the cost of a hard dependency on the provider and no way in for anyone outside the company directory. It is also the opposite of what was written down.
Three others diverged too: a user-administration screen specified, built, then deleted; notification scoping that moved from role-based to assignment-based; and the re-applicant machinery, which ships complete and unreachable.
Four is the number I happened to remember, not the number that diverged. The plan itself records ten — two requirements deferred, eight delivered in simplified form — which I had forgotten, and which is its own answer to how much attention the paperwork gets once the thing runs.
The traceability table exists to answer exactly that question, and it looks complete. But the authentication row records the substitution in a note and still presents the requirement as cleanly delivered. Once a table will tell you a requirement was delivered when its opposite shipped, the count stops being something you can read off a page.
Every layer here did its job. The code was generated, reviewed, and read; the plan was written, shared, and pushed back on. It still shipped the opposite of what was specified, and the document meant to catch that recorded it as delivered. Code fails loudly when you exercise it. A document nobody exercises tells you nothing, whoever owns it.
What we are left holding
Four things are still open, and none of them is scheduled. None of them is a discovery either — all four were known before it held anybody’s real application, and it went live anyway.
The four raw-SQL migrations. Four features needed something Prisma’s schema language could not describe, so they are written out by hand in the migration files instead. Our written description of the database no longer matches the database: anything that reads the description does not know those parts exist, which makes them what a second engineer will find hardest to change safely.
The re-applicant path. We still have not decided how we want to treat someone who comes back.
Access. There is no role system. Everyone who can sign in can open every candidate, and export the lot to a CSV — the same access model the spreadsheet had. What changed is that the data no longer lives in a file that gets copied around by default, though anyone can still make one.
The log. Changes to a candidate’s record are logged, so are changes to the process rules themselves, and so is a bulk export. Opening a record is not. It is a compensating control, and a partial one. The application cannot unwrite it — the database account it runs as may insert, and may not update or delete. What it is not is an approval step, and it is not an alerting system. It is a record, and a record only helps whoever goes looking.
That is what the controls are, not how we would respond if one of them failed, and that part I am not publishing.
Back to the people who never open it
We built a tool instead of buying one, in a shape we can keep changing, and the people who use it every day spend less of that day fighting a file.
The people it affects most never open it at all. What they get is consistency built into the thing rather than remembered by whoever is looking at the file that week. I cannot show you that this is better for them; we do not measure it. The software makes it hard for the steps to differ, and whether they do, I do not yet know.
That last part is the reason I wanted to write this down. Internal tools are easy to treat as plumbing. This one decides how we treat people.
What is QA Automation and Why It’s Important
Why Mobile Bugs Are Hard to Explain — and How Charles Proxy Helps
Embracing BDD via Cucumber
Building a Subscription System with Stripe
Mastering Clean Code: Best Practices for Software Engineers