Argument List Too Long
A little script that had run cleanly a hundred times started failing every single run — and the error came from the kernel, not from our code. The data was not wrong. There was simply too much of it to hand over the way we were handing it. The ceiling we hit is invisible, universal, and scales in exact proportion to how well things are going.
The script is unglamorous: once a day it rolls up the last few days of merged pull requests into a single JSON file that a dashboard reads, so anyone can see at a glance what shipped and how CI is holding up. It leans on jq to shape the JSON. It had worked, quietly, for weeks. Then the scheduled run went red. Then it stayed red — every run, same line:
jq: Argument list too long
The temptation is to read that as “jq broke” or “the JSON is malformed.” Neither was true. The JSON was fine. jq was fine. The program never got the chance to be the problem — it never started.
Two doors into a program
There are two everyday ways to get data into a command-line program, and they are not interchangeable. You can pass it as arguments — the words after the command — or you can stream it in through standard input. They feel like the same thing. Underneath, they could hardly be more different.
One is a room; the other is a pipe
Arguments are copied into the new process by the operating system at the instant it launches — and the space reserved for that copy is finite. There is a hard cap, ARG_MAX, on the combined size of everything you put on a command line. It is generous, so you rarely meet it. But it is a wall, and it does not move.
Standard input has no such wall. It is a stream the program reads once it is already running — a pipe you can pour any amount through, a byte at a time. Same bytes, entirely different physics.
Our script was pouring the merged-PR JSON through the first door. It handed the whole accumulated blob to jq as a command-line argument. For weeks that blob was small enough to fit through, and everything worked — which is precisely what makes this class of bug so patient.
The ceiling that rises to meet your success
Here is the cruel part. The size of that argument was not fixed — it grew with the number of merged pull requests in the window. Every PR we shipped made the blob a little larger. The more work the team landed, the closer the argument crept to the wall. The failure was not seeded by a bad commit. It was seeded by shipping enough good ones.
The error blames the wrong thing
Because arguments are copied in before your program runs, this fails at the launch itself. The kernel refuses to start the process at all. So the message you get — Argument list too long — is reported against jq, a program that never executed a single line. The tool named in the error is the one victim guaranteed to be innocent. You can read its source all afternoon and find nothing.
Every early run passed. Every test passed — tests are run against small, tidy fixtures, which is to say against exactly the conditions under which this bug is invisible. It waited, politely, for production and for time.
The fix was three lines
The repair was not to shrink the data or to page through it. It was to stop using the wrong door. We rerouted the same bytes off the argument list and onto standard input — a here-string in the shell, and a one-word change in the jq program to read its input as a stream rather than a named argument. Same data, same output, no wall.
That is the whole change. Three lines. The dashboard went green on the next run and has stayed green since.
The rule we took from it
argv is not a data channel. It is a place for a handful of flags and names — small, bounded, known-in-advance things. The moment you interpolate something that grows — a list, an accumulated blob, anything whose size is a function of how much has happened — you have planted a failure that will pass every test, survive every review, and work flawlessly right up until the day you have succeeded enough to trip it. When the data is unbounded, pipe it. Give the program a stream, not a sentence.
One of many small lessons from letting an autonomous loop build and operate LastDB — where the tooling has to survive its own success.