Skip to content

Statusline: the fork volume left after #1998 — ~118 external commands and ~300 processes per render, with git already cached #2137

Description

@hjbrandt

Version

LifeOS 7.40.4 (latest release, 2026-08-14) — component LIFEOS/LIFEOS_StatusLine.sh; measured against main @ 5e2f2e8, which carries the same file.

What is broken

This is a follow-up to #1998, not a re-report of it. Two of the three things raised there
landed: installs still at refreshInterval: 1 get migrated to 5 on the next component
deploy, and the git block is cached inside the refresh interval. Neither has shipped yet
(no release since 7.40.4), so the numbers below are measured on main and the git saving
is subtracted arithmetically rather than observed.

What remains after both: a render executes 118-120 external commands and spawns ~300
processes when the process working directory is inside a repository, 111-113 when it is
outside one, of which git is 5 and 1 respectively. The couple of commands of spread is
which of the script's own caches happen to be warm. Caching the git block therefore
removes those five calls and the wc and tr around them. The rest is jq ×22-25,
tr ×19-20, wc ×12-13, sed ×10, cat ×7, awk ×7, date ×6, head ×5, and a
long tail, none of it git and none of it individually slow. A render costs ~0.35 s of
CPU (0.12 user, 0.23 sys) and ~0.37 s of wall time. At the current default of
refreshInterval: 5 that is ~7 % of one core per idle session, before any
event-driven render. The script re-reads the same file contents on every tick
and caches nothing between renders, so the recomputation is the cost rather than any one
expensive command.

On the causation you said you would revisit for: I cannot show it. I have no launchd
failure to attribute, and I am not claiming the login-degradation chain from #1998. What
I can show is that the fork volume survives both landed fixes, which is the premise those
fixes were aimed at.

Where (file:line)

LifeOS/install/LIFEOS/LIFEOS_StatusLine.sh:305 (the per-render stdin parse; the cost is spread across the file, with the full call table in the negative control below)

Repro on a clean tree

# Counts external commands in ONE render by putting a counting stub in front of each
# on PATH. Stock tools only. jq must be installed or the parse at :305 short-circuits
# and the count is meaningless.
#
# The count depends on what the script finds on the machine: it reads $HOME/.claude
# and $LIFEOS_DIR. Expect 105-120 on any real install; with neither present it drops
# to about 65, because most sections find nothing and skip. The shape of the
# breakdown is the point, not the exact total.
#
# Run it from a directory that is NOT a git repo, then from one that is: the git block
# at :698 reads the PROCESS working directory, not the payload's current_dir, so the
# two differ by four git calls.

cat > /tmp/count-render.sh <<'SH'
#!/bin/bash
set -u
SCRIPT="${1:?usage: count-render.sh /path/to/LIFEOS_StatusLine.sh}"
W="$(mktemp -d)"; SHIM="$W/shim"; C="$W/c.txt"; mkdir -p "$SHIM"; : > "$C"
for cmd in jq git date stat sed awk grep tr wc cut head tail sort uniq \
           curl security find ls basename dirname cat realpath readlink \
           bun node python3 stty tput defaults sw_vers uname id whoami \
           pgrep pmset ioreg osascript kitten; do
  real="$(command -v "$cmd" 2>/dev/null)" || continue
  [ -n "$real" ] || continue
  printf '#!/bin/bash\nprintf "%%s\\n" "%s" >> "%s"\nexec "%s" "$@"\n' "$cmd" "$C" "$real" > "$SHIM/$cmd"
  chmod +x "$SHIM/$cmd"
done
P='{"session_id":"count-'"$RANDOM"'","cwd":"'"$W"'","workspace":{"current_dir":"'"$W"'"},"model":{"display_name":"Opus"},"version":"2.0.0","context_window":{"context_window_size":200000,"used_percentage":12,"total_input_tokens":24000},"cost":{"total_cost_usd":0.5}}'
# Backgrounded with no pipe or $( ) around it, so nothing the script spawns can
# hold this script open, and bounded by a wall clock so a stall prints a process
# tree instead of sitting there. The script has three unbounded calls of its own
# (the keychain read, `claude --version`, and a git status on Linux).
PATH="$SHIM:$PATH" COLUMNS=200 bash "$SCRIPT" <<< "$P" >/dev/null 2>&1 &
pid=$!; t=0
while kill -0 "$pid" 2>/dev/null && [ "$t" -lt 120 ]; do sleep 0.5; t=$((t+1)); done
if kill -0 "$pid" 2>/dev/null; then
  echo "render still running after 60 s; process tree:" >&2
  ps -axo pid,ppid,stat,etime,command | awk -v r="$pid" 'NR==1 || $1==r || $2==r' >&2
  pkill -P "$pid" 2>/dev/null; kill -9 "$pid" 2>/dev/null
fi
echo "external commands: $(wc -l < "$C" | tr -d ' ')"
sort "$C" | uniq -c | sort -rn | head -8
rm -rf "$W"
SH
chmod +x /tmp/count-render.sh

git clone https://github.com/danielmiessler/LifeOS.git /tmp/lifeos-clean
S=/tmp/lifeos-clean/LifeOS/install/LIFEOS/LIFEOS_StatusLine.sh

cd "$(mktemp -d)" && /tmp/count-render.sh "$S"   # outside a git repo
cd /tmp/lifeos-clean && /tmp/count-render.sh "$S" # inside one

# Total processes, including the subshell forks the PATH shim cannot see.
# Sequential PIDs, so read it as a magnitude, not an exact count.
cd "$(mktemp -d)"
a=$(sh -c 'echo $$'); COLUMNS=200 bash "$S" <<< '{"session_id":"pid","cwd":".","workspace":{"current_dir":"."},"model":{"display_name":"Opus"},"version":"2.0.0","context_window":{"context_window_size":200000,"used_percentage":12,"total_input_tokens":24000},"cost":{"total_cost_usd":0.5}}' >/dev/null 2>&1; b=$(sh -c 'echo $$')
echo "processes: $((b - a - 1))"

# CPU, not just wall time:
time -p bash "$S" <<< '{"session_id":"t","cwd":".","workspace":{"current_dir":"."},"model":{"display_name":"Opus"},"version":"2.0.0","context_window":{"context_window_size":200000,"used_percentage":12,"total_input_tokens":24000},"cost":{"total_cost_usd":0.5}}' >/dev/null

Negative control

Unpatched main @ 5e2f2e8, macOS 27.0, /bin/bash 3.2.57, no local patches. One unedited run of the block above, process working directory inside the clone:

external commands: 118
  22 jq
  20 tr
  13 wc
  10 sed
   7 cat
   7 awk
   6 date
   5 head

Outside any git repository, same run: 111, with git appearing once instead of five times. Repeated later the pair came out 120 and 113; the extra couple is the script's own location, version and usage caches expiring between runs. The shape does not move.

Total processes per render, sequential-PID delta, no shim: 294, and 301 / 295 on the repeat.

CPU and wall time per render, no shim: real 0.36 user 0.12 sys 0.22, and real 0.38 user 0.12 sys 0.24 on the repeat.

The red is the residual: with the git block cached away, those git calls and the wc and tr around them go, and ~110 external commands and ~290 processes remain per render, repeated on every event and every refresh tick for every open session.

Suggested fix

Not the single-process renderer you declined. Same file, same language, /bin/bash 3.2, byte-identical output, every community fix in place, installer untouched.

Everything not derived from stdin moves into three small cache files written by a refresher (LIFEOS_StatusLine.sh --refresh), which the render spawns detached when a cache is past its TTL, behind an mkdir single-flight lock. The refresher recomputes a component only when the mtimes and sizes of its inputs changed, and installs each cache atomically. A missing cache is filled inline once, so a first render is never blank; a stale one is served while the refresh runs. In the render itself, $(…) wrappers around builtins become printf -v, echo | tr becomes pure-bash case folding, get_mtime triples become [ -nt ], and the settings parse moves into the refresher.

Running here since 2026-09-07: 2 external commands and ~30 ms for a warm render in a hermetic fixture, 5 and ~42 ms live. Output is pinned byte-for-byte against goldens captured from the unmodified script for the nano, micro, mini, normal and startup-estimate payloads.

It is a large diff for one file, which is why this is an issue and not a PR. I built it as one change because the render-path diet and the snapshot layer touch the same lines section by section, so separating them means editing every section twice. If you would rather take it in two steps I will do that work and carve the diet out first; it is not free, but it is mine to pay.

Two things found on the way, reported separately rather than folded in: #2136 (timeout 1 git never runs on macOS, so the startup estimate's git share is always zero), and :885 hardcoding $HOME/.claude/LIFEOS/TOOLS/GetCounts.ts where :2154 uses $LIFEOS_DIR, which makes a relocated install read zero hooks.

Before submitting

  • I searched open and closed issues for this defect.
  • The repro runs against a clean tree of the version above, not against my modified install.
  • I removed personal data from the pasted output — real names, absolute home paths, tokens, my own content.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions