Tested Templates & Toolstested before it ships

● Server & Claude Code

tee /dev/stderr wipes your log file (and tee -a quietly loses lines)

✓ ReproducedUbuntu 24.04.5 · bash 5.2.212026-09-25
bashshell

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 asResulting file
./t.sh 2>&1 | catAll four lines. Fine, because stderr is a pipe.
./t.sh >out.log 2>&1warning…, then a run of NUL bytes (22 here), then step 3. Steps 1–2 are gone.
./t.sh >>out.log 2>&1warning…, 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:

  1. tee truncates out.log to 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.
  2. tee writes warning… at offset 0.
  3. 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.