From a Ten-Line Script to a Real Utility with Codex

Wait 5 sec.

I’m an experienced programmer, and I’ve worked in many different languages. Sometimes being a programmer is a two-edged sword. You want to accomplish something, and you can do it easily — but it can be a lot of work to do it right. Maybe more work than you want to do.Normally, I’ll kick out a few lines of script for something I want and be done, accepting that it isn’t production-hardened. This time, however, I decided to try an AI tool to see whether they could do the work I was too lazy to do myself. While I’ve played with chatbots, I wanted to try one of the dedicated coding agents, in this case, Codex. Outside of asking ChatGPT to write a simple function or find the cause of an error message, I haven’t done much coding with AI assistance, so I was interested to see what these agents brought to the table.A Radio ProblemThe problem was simple: I wanted an easy way to put buttons on my Linux desktop that launched Internet radio stations. Sure, I could open a player and paste in a long URL, but I’m far too lazy to remember all those URLs.I searched for a way to make Shortwave — an Internet radio player — open a URL from the command line. Apparently, you can’t. Google Gemini suggested writing a script that launches cvlc, the command-line VLC player, with the URL as an argument.That’s easy, so I did it. Of course, then I had to find the stream URLs for all my favorite stations. It turns out that Radio Browser maintains an extensive database of stations. I considered scraping the site or using its API, but honestly, the little script was becoming too much of a project.Besides, I was already struggling to manage the media player’s lifetime. I didn’t want a new station playing on top of one that was already running, and I wanted a command to stop playback, so the script had already grown larger than I first imagined.My first version used a temporary file containing the player’s process ID so a future script execution could kill the old player. That usually works, but it isn’t very robust, and I knew it. But how much work did I really want to do here? I decided I had done enough and turned the rest over to Codex, OpenAI’s coding assistant.What Can Codex Do?Codex is more than a chatbot that produces code snippets. With access to a project, and limited access to your machine, it can inspect existing files, edit them, run commands and tests, examine Git history, and manage commits and remotes. OpenAI describes Codex workflows as including coding, testing, analysis, code review, and repository automation.The important distinction is that Codex works on the actual project. Instead of copying code out of a chat window, I could say, “Have a look at this shell script,” and it examined the script in place. It also noticed that I already had an uncommitted modification and avoided overwriting it. It also understands version control, and that turns out to be one of its really nice features.Fixing ProblemsMy first request was:Have a look at this shell script. I know it needs a trap. Is there a better way to keep it from accidentally killing something with a stale playradio.tmp?Codex pointed out that a trap was not the only solution. The launcher exits immediately after starting VLC, so it is not around later to receive SIGCLD or clean up after the player. Sure, it could run something to wait around, but there was a cleaner way to get the job done.It initially suggested verifying that the saved PID still belonged to cvlc. Then it caught a subtler problem in its own proposal: if Linux reused the PID for a different cvlc process, the name check could still kill the wrong player. This is probably very rare, but when it does happen, it will be a mysterious, hard-to-reproduce bug.The final solution records both the PID and Linux’s process start-time token:printf '%s %s\n' "$pid" "$start_time" > "$pidfile"Part of a Codex session. Entire transcripts are on GitHub.Before sending a signal, the script confirms that both still match. It also uses a private per-user runtime directory, serializes concurrent start and stop operations with flock, sends SIGTERM first, waits for a graceful shutdown, and rechecks the process identity before falling back to SIGKILL. That is considerably more thought than I wanted to put into a desktop radio button. Overkill? Maybe, but it is robust.Another pleasant surprise was that Codex built a suite of tests to ensure that everything worked as it should. It runs these tests when it makes changes. So it doesn’t just create code. It creates code, executes it with test cases, and fixes any issues it discovers.Searching the Database — and MoreOnce the process handling was safe, I asked:Radio Browser allows you to search via API for radio stations. How hard would it be to make $1 a search string and take the best match, while allowing -u for a URL instead?Codex checked the current API documentation, found that curl and jq were already installed, and implemented the search. It hides broken stations, orders matches by votes, selects the top result, and reports the selection back to Radio Browser’s click counter. I told it I wanted specific command-line options over several iterations. The program can play a URL, search the database for a station, or even just query the database. It can also give you a list and let you pick. (See the README.md for the entire interface.)RadioBrowser is human-readable, but also provides the same data via API.I did make a few requests. For example, if you pass a URI, the program should figure it out and skip the database search. I also wanted the player to be configurable through a PLAYRADIO_PLAYER environment variable. I asked it to fall back on wget if curl wasn’t installed. Missing dependencies should produce useful installation advice rather than mysterious failures. I also asked it to produce a GitHub-style README and a traditional Unix man page.Human Guidance Still MattersThere were a few places where human intervention improved the result. For example, the PLAYRADIO_PLAYER configuration and its explanatory comment originally appeared near the bottom of the script. That works, but it is inconvenient for someone who wanted to edit the file. I asked Codex to move it near the top.Another example was the first version of --pick. I had specifically suggested Bash’s select command, so Codex printed the formatted list and then let select print a second numbered list. It worked, but the duplication was distracting and, as far as Codex was concerned, it was what I asked for.I pointed the duplication out. Codex replaced select with a simple numeric read, keeping the detailed table and adding only one extra row:1 Jazz One2 Jazz Two3 CancelStation number:This is a good example of how I think these tools work best. The AI handles the implementation, edge cases, tests, and documentation, while the human provides design input and flags anything awkward or that doesn’t fit the intended experience.Does It Scale?I’m not an expert at using these kinds of tools, but I think this worked very well. I generally asked for what I wanted and then made technical observations about the results.I also tried this with a script, originally from [Petar Marinov], that I’ve used and modified for years to replace the cd command (see GitHub). I didn’t tell it what to do. I just let it look at the code. It correctly determined what it was supposed to do and made solid suggestions for making it more robust. It suggested features and even a major improvement to make it work not just with cd but with anything that changes pwd, like pushd. Of course, I couldn’t resist getting a man page and a README for free.One feature I asked the tool to create was a simple bookmark system. It proposed and created a way to create and maintain a bash associative array that it persisted in a configuration file. That worked, but I knew it would be better, on many levels, to keep the bookmarks as proper symlinks. When I pointed this out, the AI did get it, saying:That has several nice consequences:changes from cdmark are instantly visible in every shell; no reload or synchronization issuels -l ~/.config/cdfunc/bookmarks is already a crude cdmark listbookmarks can be created or removed manually with ordinary Unix toolsSyncthing/git/backup tools handle them naturallyno parsing, quoting, escaping, or rewriting a bookmarks fileno associative-array initialization at shell startupbroken bookmarks are naturally represented as broken symlinksI think that’s substantially cleaner.It also noted that this makes shell completion very simple, which I had not thought about. However, its implementation broke normal shell completion for the commands. It fixed that after I pointed it out. Well, actually, it took two tries to work out all the bugs. This is another case where human guidance is critical.For a more advanced project, I forked a simple editor, kilo, and added a few Emacs commands. I asked Codex to review it. It found a number of bad edge cases, some in the original code, and fixed them. I then asked it to suggest Emacs-like features it could easily do. We added a ton! (see GitHub). It was impressive how well it analyzed and understood the code. I had done similar modifications to the code a few weeks earlier and, I have to admit, Codex understood the original code base much faster than I had.Again, though, human guidance is necessary. Emacs uses an Esc prefix for some commands. You can also hold down the Alt key to get the same result. So pressing Alt+W in a terminal sends an Esc character and a W.Initially, Codex wrote code to detect an Esc, wait a short time for a command, and then, if nothing came, treat it as a bare escape. It even understood that this would be a problem and mentioned it. Alt+W would work, but there was no way for a human to press Esc and then W in the time allotted. I prompted:Yes I see that in the program. Would it be possible to have it wait indefinitely for ESC UNLESS a caller set some flag. So when other parts of the editor (search/save/etc.) are prompting for input they would set that flag (or call a separate entry point) and, at that point, ESC=>ESC. Any other time ESC is treated as a prefix (and perhaps ESC ESC gets sent as an escape just as a — ahem — escape hatch.That fixed the problem. It is hard to remember that while Codex seems smart, it doesn’t have human judgment or human-level problem-solving skills. You have to supply that. Sure, it found problems in its own code. It found problems in my code. It devised solutions. But you still have to make sure those solutions make sense and sometimes nudge it — at least — in the right direction.If you are interested, each of the GitHub repos (playradio, cdfunc, and kilo) has a session directory that contains transcripts of the AI chats that produced the final versions of the code. Admittedly, none of these started from a totally blank slate, but working on an existing code base is certainly a realistic test.The Git AssistantOne feature I particularly liked was Codex’s ability to manage Git. I didn’t even try the GitHub plugin for Codex, which would probably be even better. I asked it to commit the current version before starting a new feature, which gave me a clean checkpoint. Later I said:Commit please. I’m going to add a remote GitHub repo. Can you set this as origin and push it after the commit?Codex committed the changes, added the remote, pushed the branch, configured upstream tracking, and verified that the working tree was clean. The entire evolution is visible in the repository’s history — from process-safety changes, to Radio Browser search, to configuration and documentation, to the interactive station picker.You can see the final project and follow each commit in the repositories along with transcripts of the AI sessions. Having things in version control is especially useful with a tool like Codex. You can easily see what has changed and roll back if you like.Wrap UpThe original script solved my immediate problem in a handful of lines. The finished utility solves the same problem safely, handles failures, searches a public database, supports different players, has good documentation, and leaves a traceable Git history. One important note. Codex and other agents have a limited context window, so you won’t get the same results trying to work with extremely large code bases unless you pay for a larger model. But for these tasks, normal consumer Codex worked well.Could I have written all of that myself? Certainly. Would I have bothered to go this far? Probably not for what is basically a one-off desktop hack.That may be the most useful role for a coding agent: They don’t always enable you to do something you couldn’t otherwise do. But they make it cheap enough in time and attention span to do all the boring and defensive coding and testing that you know you should do, but so often don’t. Codex didn’t replace me. It just augmented my patience.