● Server & Claude Code
tee /dev/stderr wipes your log file (and tee -a quietly loses lines)
A health-check script on this server printed a clean report to the terminal. When its output was redirected to a file, only the last line survived. The cause was one innocent-looking idiom, … | tee /dev/stderr, used to copy a warning to stderr. Here's exactly what it does in each case.
Reproduce it
#!/usr/bin/env bash
echo "step 1: checking disk"
echo "step 2: checking ports"
echo "warning: something odd" | tee /dev/stderr >/dev/null
echo "step 3: done"
| Run as | Resulting file |
|---|---|
./t.sh 2>&1 | cat | All four lines. Fine, because stderr is a pipe. |
./t.sh >out.log 2>&1 | warning…, then a run of NUL bytes (22 here), then step 3. Steps 1–2 are gone. |
./t.sh >>out.log 2>&1 | warning…, step 3. Steps 1–2 are still gone. |
$ cat -A out.log
warning: something odd$
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@step 3: done$
What's happening
/dev/stderr is a symlink to /proc/self/fd/2. On Linux, opening it doesn't duplicate your existing file descriptor. It opens the underlying file again, as a new open file description with its own flags and offset. tee opens its output files with O_TRUNC unless you pass -a, so:
- tee truncates
out.logto zero bytes, which deletes steps 1–2. This happens even if the shell opened the file with>>: the truncation belongs to tee's new open, not the shell's. - tee writes
warning…at offset 0. - The script's original stdout descriptor still has its offset at byte 45, just past step 2. The next write lands there and leaves a gap, which reads back as NUL bytes.
"Just use tee -a"? Not quite
tee -a /dev/stderr stops the truncation, but the two separate offsets are still there:
$ ./t2.sh >o.log 2>&1 && cat -A o.log # t2 = step 1 / warning via tee -a / step 2
step 1$
step 2$
$
tee appended warning at the end, then stdout, still at its old offset, wrote step 2 right over it. The warning you wanted is the line that's lost. It only works when the shell also opened the file in append mode (>>).
The fix
echo "warning: something odd" >&2 # instead of | tee /dev/stderr
some_cmd | tee -a logfile >&2 # if you really want a copy somewhere
>&2 duplicates the existing descriptor (dup2). It shares the same open file description and offset, and it never reopens or truncates anything. It works the same whether stderr is a terminal, a pipe, a file opened with > or one opened with >>.
The same applies to anything else that opens /dev/stdout, /dev/stderr or /proc/self/fd/N by path. Use redirection, which duplicates, rather than a path, which reopens.