I Built a GitLab TUI in a Day. Mostly by Making Decisions.
I spend a lot of my day waiting on GitLab pipelines. Open the browser, find the project, click into the pipeline, click into the job, scroll. Repeat. I’ve wanted something like k9s for GitLab for a long time, so earlier this week I built one. It’s called kerf: a small terminal app, written in Rust, that shows your pipelines, their jobs and live logs.
The part I want to write about isn’t the app, though. It’s how it got built. From the first commit to about twenty merged changes took roughly 20 hours, including a night’s sleep. I barely wrote any code myself. Agents did that. What I did was decide things and read what the reviewers found.
The setup
Every feature went through the same steps, and each step was handled by a separate agent:
- One agent looks around the codebase and writes down what it found.
- A stronger model writes a short spec.
- I read the spec and answer its open questions.
- A fast model builds the feature in its own branch.
- Two reviewers look at the result at the same time: one hunts for bugs, one for security problems.
- The fixes go in, a merge request is opened, and a third reviewer comments on it directly in GitLab.
- When CI is green, it gets merged.
flowchart LR
A[explore] --> B[spec] --> C{me} --> D[build] --> E[two reviewers] --> F[MR + review] --> G[merge]
The trick that made this work across many sessions: every step writes a short Markdown note into the repo, not just into the chat. There is one index file per feature that says where things stand. When I came back the next morning, the new session read that file and carried on. I didn’t have to explain anything again.
My actual job
The most useful thing I did all day was one short message. For the date bar (a row of “Today, Yesterday, …” chips above the pipeline list), the spec came back with a handful of questions. Which day is selected by default? Newest on the left or the right? Created date or updated date? Which timezone? Hide empty days?
I answered them all at once, and the answers were written into the index as fixed decisions. Without that, a fast model would have guessed each one halfway through building, and I’d have found out at review time. With it, every agent after that point knew the answers.
That’s the pattern I keep seeing. Agents are quick at writing code. The slow part moves to deciding what you want and checking what you got.
What the reviewers caught
I was a bit sceptical about running two reviewers on every change. I’m not anymore.
The best catch was in the local cache. kerf keeps a copy of your projects on disk, so the project picker opens instantly. Both reviewers, independently, found that the cache didn’t check who was logged in before showing it. Switch from one GitLab account to another and the second account would briefly see the first account’s private projects. Both of them reproduced it against the real binary. The spec explicitly said this must never happen.
The same review round found a few more:
- Demo mode, which is meant to show fake data, was writing that fake data into the real cache.
- If GitLab answered with an empty list, kerf threw away the whole cached project list.
- There was a test with exactly the right name for the account-switch bug. It just didn’t test the moment the bug happened, so it passed anyway.
That last one made me laugh. A reviewer that only skims test names would have been reassured. This one actually ran the scenario.
Other rounds found smaller things. A slow log response could overwrite the log of a job you had already moved on to. The newest log line was hidden one row below the bottom of the view. Nothing exotic, just the kind of bugs you miss when the same brain writes the code and reads it.
What nobody caught
The log view splits a job log into sections, one per command, so you can jump straight to the step that failed. It shipped with passing tests and a clean review. Then I pointed it at a real pipeline, and it showed the entire log as one big section.
The tests used made-up log lines. Real GitLab logs have a timestamp and a small stream marker in front of every line, and the parser didn’t expect that. It took three attempts to get right.
No reviewer could have spotted this. The spec, the tests and the code all agreed with each other, because they were all based on the same wrong guess about what a log looks like. The only thing that caught it was me using the tool. Lesson learned: when you parse someone else’s format, start with a real sample.
What it costs
It’s not free, and I don’t want to pretend otherwise.
- Big changes. I normally aim for small merge requests. Some of these were thousands of lines. I couldn’t review them line by line, so I read the specs and review notes instead and trusted the tests. That’s a trade-off I made on purpose, but it is one.
- Some ceremony. My process has “release” and “deploy” steps, because most of my projects run on my cluster. kerf is a local tool, so those notes all just say “not applicable”. I need a lighter version for small tools.
- Tokens. Three reviews per feature adds up. After my last post about wasting tokens, I should be honest about that. For anything touching cached data or credentials, it’s worth it. For “this popup is one row too short”, it isn’t, and those fixes went in without the full process.
- The first commit skipped all of it. The initial scaffold went in as one big unreviewed commit, and a couple of later fixes were cleaning up after it.
Would I do it again?
Yes, with real sample data from the start and a lighter process for small tools.
What surprised me most wasn’t the speed. It was how useful the written notes turned out to be. I can still open the review summary for the cache and see exactly why it works the way it does, which bug forced that, and which test now covers it. Normally that knowledge lives in my head for about a week. Now it sits next to the code.
kerf is on GitLab if you want to try it. Pipelines, jobs and live logs work today. Merge requests are next, and yes, they’ll go through the same process.