Skip to content
HN On Hacker News ↗

Pointing at the error: compiler-style diagnostics in uutils coreutils

▲ 56 points 8 comments by ingve 3d ago HN discussion ↗

Pangram verdict · v3.3

We believe that this text is a mix of AI and human-written content.

52 %

AI likelihood · overall

Mixed
57% human-written 43% AI-generated
SEGMENTS · HUMAN 3 of 10
SEGMENTS · AI 2 of 10
WORD COUNT 1,572
PEAK AI % 91% · §7
Analyzed
Sep 5
backend: pangram/v3.3
Segments scanned
10 windows
avg 157 words each
Distribution
57 / 43%
human / AI fraction
Verdict
Mixed
Pangram v3.3

Article text · 1,572 words · 10 segments analyzed

Human AI-generated
§1 AI · 75%

Pointing at the error: compiler-style diagnostics in uutils coreutils Aug 31, 2026 For 50 years, Coreutils have never stopped evolving. Now, we're pushing that innovation further by rethinking how they report errors. Unix tools report errors as a single line on stderr. That line says what went wrong, not where. For most commands there is nowhere else to point anyway, but a few take arguments that are small languages: a test expression, a chmod mode, a sort key, a tr set. When one of those fails to parse, what you actually want to know is which argument, or which character of it, the parser tripped over. rustc has been answering that question with a caret for years, and ariadne puts the same rendering one dependency away. The idea of bringing it to a command-line tool comes from uutils awk, which already reports errors in an awk program that way; coreutils arguments are smaller languages, but they parse just the same. Starting with 0.11.0, coreutils uses it. When stderr is a terminal, a parse error is printed as a report: the arguments are echoed back as a source line, a caret marks the culprit, and a help line explains the syntax when we have something useful to say about it. What it looks like Start with tr, whose GNU message assumes you already know what a collating sequence is.

§2 Human · 18%

Before: tr $ tr 'qw[y-b]' x tr: range-endpoints of 'y-b' are in reverse collating sequence order After: tr $ tr 'qw[y-b]' x tr: range-endpoints of 'y-b' are in reverse collating sequence order ╭─[ tr:1:7 ] │ 1 │ tr qw[y-b] x │ ─┬─ │ ╰─── did you mean 'b-y'?

§3 Mixed · 59%

│ │ Help: a range goes from the lower character to the higher one, as in a-z ───╯ Try it in the playground. A cut list can be long, with a single bad item in it.

§4 Mixed · 40%

Before: cut $ cut -f 1,4-2,9-12 notes.txt cut: invalid decreasing range Try 'cut --help' for more information. After: cut $ cut -f 1,4-2,9-12 notes.txt cut: invalid decreasing range ╭─[ cut:1:10 ] │ 1 │ cut -f 1,4-2,9-12 notes.txt │ ─┬─ │ ╰─── this range ends before it starts │ │ Help: a list is N, N-M, N- or -M, separated by commas, as in -f1,4-6,9- ───╯ Try 'cut --help' for more information. Try it in the playground. The caret does not have to cover a whole argument. It can land on one character. Before: chmod $ chmod 'g+rw?x' notes.txt chmod: invalid operator (expected +, -, or =, but found ?) After: chmod $ chmod 'g+rw?x' notes.txt chmod: invalid operator (expected +, -, or =, but found ?)

§5 Mixed · 52%

╭─[ chmod:1:5 ] │ 1 │ g+rw?x notes.txt │ ─ │ │ Help: a mode is either octal, as in 644, or clauses such as u+rwx,go-w ───╯ sort keys are short enough that a stray character is easy to miss.

§6 Mixed · 37%

Before: sort $ sort -k2.3x notes.txt sort: stray character in field spec: invalid field specification '2.3x' After: sort $ sort -k2.3x notes.txt sort: stray character in field spec: invalid field specification '2.3x' ╭─[ sort:1:11 ] │ 1 │ sort -k2.3x notes.txt │ ─ │ │ Help: a key is FIELD[.CHAR][OPTS][,FIELD[.CHAR][OPTS]], as in -k2.3,4nr ───╯ Try it in the playground. env -S takes a whole command line and splits it the way a shell would. The old message could only quote the offending fragment back at you. Note that the string contains spaces, so it is echoed back quoted, and the caret still lands inside the quotes. Before: env $ env -S 'echo ${1FOO}' env: only ${VARNAME} expansion is supported, error at: ${1FOO} After: env $ env -S 'echo ${1FOO}' env: only ${VARNAME} expansion is supported, error at: ${1FOO} ╭─[ env:1:14 ] │ 1 │ env -S 'echo ${1FOO}' │ ─┬─ │ ╰─── a variable name cannot start with a digit │ │ Help: only $NAME and ${NAME} are expanded; the other shell forms are not ───╯ test builds its expression out of separate arguments. The report echoes the expression on its own, without the test in front, and marks the argument that broke it. Before: test $ test 7 -eq zap test: invalid integer 'zap' After: test $ test 7 -eq zap test: invalid integer 'zap' ╭─[ test:1:7 ] │ 1 │ 7 -eq zap │ ─── │ │ Help: -eq, -ne, -lt, -le, -gt and -ge compare integers; use =, !=, < or > to compare strings │ -eq equal, -ne not equal, -lt less than, -le less than or equal, -gt greater than, -ge greater than or equal ───╯ Try it in the playground. A SIZE is a number followed by a unit. The report says which half was rejected. Before: head $ head -c 1fb notes.txt head: invalid number of bytes: '1fb' After: head $ head -c 1fb notes.txt head: invalid number of bytes: '1fb' ╭─[ head:1:10 ] │ 1 │ head -c 1fb notes.txt │ ─┬ │ ╰── not a known unit │ │ Help: a size is a number and an optional unit: K, M, G and so on for 1024, KB, MB, GB for 1000 ───╯ Try it in the playground.

§7 AI · 91%

One parser handles every SIZE in the suite, so the same report shows up for tail -c, truncate -s, split -b, shred -s, od -N, sort -S, the block sizes of du -B, df -B and ls --block-size, and the threshold of du -t. numfmt --format is a printf-style format that allows exactly one conversion. The old message just restated the rule. The annotation names the conversion you actually wrote.

§8 Human · 28%

Before: numfmt $ numfmt --format=%q 1000 numfmt: invalid format '%q', directive must be %[0]['][-][N][.][N]f After: numfmt $ numfmt --format=%q 1000 numfmt: invalid format '%q', directive must be %[0]['][-][N][.][N]f ╭─[ numfmt:1:18 ] │ 1 │ numfmt --format=%q 1000 │ ┬ │ ╰── f is the only conversion numfmt has; %d, %e, %g and the other C conversions are not accepted │ │ Help: a format is [PREFIX]%[0]['][-][WIDTH][.PRECISION]f[SUFFIX], as in "%'-10.2f" ───╯ Try it in the playground. csplit patterns contain regexes, and the regex engine already knows which character it choked on. We were simply throwing that position away. Before: csplit $ csplit notes.txt '/a{2,1}/' csplit: '/a{2,1}/': invalid pattern After: csplit $ csplit notes.txt '/a{2,1}/' csplit: '/a{2,1}/': invalid pattern ╭─[ csplit:1:20 ] │ 1 │ csplit notes.txt /a{2,1}/ │ ──┬── │ ╰──── invalid repetition count range, the start must be <= the end │ │ Help: a pattern is a line number N, /REGEXP/[OFFSET] or %REGEXP%[OFFSET], each optionally followed by {N} or {*} ───╯ Try it in the playground.

§9 Mixed · 59%

That label comes straight from the regex engine and is not translated, since it is the only place the wording exists. Where it applies 28 utilities use it in 0.11.0.

§10 Human · 12%

The linked examples run in the playground; the others are for utilities the WebAssembly build does not ship, so try those locally: UtilityWhat the caret points atTry it testthe argument that made the expression failtest 7 -eq zap exprthe argument that made the expression failexpr 9 + foo chmodthe failing clause (or character) of an invalid symbolic or octal modechmod 'g+rw?x' fruits.txt mkdirthe failing part of the mode given to -m/--modemkdir -m u+q mydir mkfifothe failing part of the mode given to -m/--modemkfifo -m u+q mypipe mknodthe failing part of the mode given to -m/--modemknod -m u+q mydev c 1 3 installthe failing part of the mode given to -m/--modeinstall -m u+q fruits.txt dest trthe part of a set that is at fault (bad class, backwards range, bad repeat count, …)tr 'qw[y-b]' x sortthe failing part of a -k/--key or field specification, or of the SIZE given to -Ssort -k2.3x fruits.txt numfmtthe failing part of a --format or --field specification, the value given to --from, --to, --from-unit, --to-unit, --padding or --header, or the input number itselfnumfmt --format=%q 1000 printfthe failing conversion or escape in the format stringprintf %5.2c q seqthe failing conversion in the format given to -f/--formatseq -f %5.2c 1 3 statthe failing directive of a -c/--format or --printf formatstat -c %d%.3 fruits.txt envthe failing part of a -S/--split-string stringenv -S 'echo ${1FOO}' ddthe failing key, value or flag of a KEY=VALUE operanddd conv=ucase,zap jointhe failing field of the output format given to -ojoin -o 1.2,2.x fruits.txt fruits.txt cutthe failing range in the list given to -b, -c, -f or -Fcut -f 1,4-2 fruits.txt csplitthe failing pattern operand, the character of its regex that broke, or the format given to -b/-ncsplit fruits.txt '/a(b/' splitthe failing part of the SIZE given to -b, -C or -lsplit -b 7zq fruits.txt shredthe failing part of the SIZE given to -s/--sizeshred -s 4vv fruits.txt headthe failing part of the SIZE given to -c or -nhead -c 1fb fruits.txt tailthe failing part of the SIZE given to -c or -ntail -c 1fb fruits.txt truncatethe failing part of the SIZE given to -s/--sizetruncate -s 10fb fruits.txt odthe failing part of the SIZE given to -j, -N, -S or -wod -N 3zz fruits.txt duthe failing part of the SIZE given to -B/--block-size or -t/--thresholddu -B 1fb dfthe failing part of the SIZE given to -B/--block-sizedf -B 1fb lsthe failing part of the SIZE given to --block-size (also dir and vdir)ls --block-size=1fb stdbufthe failing part of the buffering mode given to -i, -o or -estdbuf -o 6pq head Compatibility first Being a drop-in replacement for GNU coreutils comes first, so this is strictly an interactive nicety: Reports are only rendered when stderr is a terminal.