fix(remote-job): a job name is data, never a shell pattern

`status` built its job list with `for n in $names`, which word-splits AND globs. The
second half is the bug: a job named `x*` was expanded against the caller's working
directory, so the report described unrelated files there as jobs and never mentioned
the one that ran — on a harness whose whole purpose is a report you can trust after
the session that started the job is gone. Splitting stays (several names in one
argument is the documented call shape); expansion goes.

`tail` was honest-by-accident in the wrong direction: with no jobs it handed a
literal `$JOBS/*.out` to tail, which reports a missing *file* — indistinguishable
from a broken tool — and a missing name produced tail's errno rather than "no job
called that". Both now say what is missing and exit 1.

Red first: the name test reads the report from a directory containing an unrelated
file, so the old glob is visible as a phantom job; both new tests fail against the
previous code and pass against this one.

Refs #60
This commit is contained in:
Frank committed 2026-10-21 01:06:11 -07:00
1 parent d0587a5bc5
commit 8393dc1090
3 files changed
+48 -8

No files matched your search

+20 -6
View File
@@ -51,13 +51,21 @@ case "${1:-}" in
;;
status)
# Job names are data, never patterns. `for n in $names` word-split AND globbed: a job named
# `x*` was expanded against the caller's working directory, so the report described unrelated
# files there as jobs and never mentioned the one that ran. Splitting stays (several names in
# one argument is the documented call shape); expansion does not.
names=()
if [[ -n "${2:-}" ]]; then
names="$2"
read -r -a names <<< "$2"
else
names=$(cd "$JOBS" && ls *.out 2>/dev/null | sed 's/\.out$//' | sort)
while IFS= read -r -d '' f; do
names+=("$(basename "$f" .out)")
done < <(find "$JOBS" -maxdepth 1 -name '*.out' -print0 2>/dev/null | sort -z)
fi
if [[ ${#names[@]} -eq 0 ]]; then echo " (no jobs)"; exit 0; fi
pending=0
for n in $names; do
for n in "${names[@]}"; do
if [[ -f "$JOBS/$n.done" ]]; then
printf ' %-20s DONE rc=%s %s lines\n' "$n" \
"$(cat "$JOBS/$n.rc" 2>/dev/null)" "$(wc -l < "$JOBS/$n.out" 2>/dev/null | tr -d ' ')"
@@ -69,7 +77,6 @@ case "${1:-}" in
pending=1
fi
done
if [[ -z "$names" ]]; then echo " (no jobs)"; exit 0; fi
exit "$pending"
;;
@@ -95,8 +102,15 @@ case "${1:-}" in
tail)
# Multiplexed follow: no NAME means every job at once, so one pane shows the whole harness.
if [[ -n "${2:-}" ]]; then exec tail -f "$JOBS/$2.out"; fi
exec tail -f "$JOBS"/*.out
# Both branches check for something to follow first: an unexpanded `*.out` handed to tail
# reports a missing *file*, which reads as a broken tool instead of an empty harness.
if [[ -n "${2:-}" ]]; then
[[ -f "$JOBS/$2.out" ]] || { echo "remote-job: no job '$2' (no $JOBS/$2.out)" >&2; exit 1; }
exec tail -f "$JOBS/$2.out"
fi
mapfile -d '' -t follow < <(find "$JOBS" -maxdepth 1 -name '*.out' -print0 2>/dev/null | sort -z)
(( ${#follow[@]} )) || { echo "remote-job: no jobs in $JOBS yet" >&2; exit 1; }
exec tail -f "${follow[@]}"
;;
watch)