Summary
Observed bash processes occasionally using >15% CPU indefinitely, with no obvious trigger. Traced to an infinite loop in scripts/helpers.sh:get_workspace_dir that fires from the tmux status bar commands (workspace.sh, status.sh) every status-interval.
Root cause
scripts/helpers.sh:46-63:
current_dir=$(realpath "$current_dir")
while [[ "$current_dir" != "/" ]]; do
if [[ -d "$current_dir/.devcontainer" ]]; then break; fi
current_dir=$(dirname "$current_dir")
done
The loop only terminates when current_dir == "/". Two situations make that unreachable:
realpath fails (deleted directory, broken symlink, symlink cycle) → prints nothing → current_dir="".
dirname "" returns ., and dirname "." returns .. Since . != /, the loop spins forever at ~100% CPU.
Verified on macOS:
$ dirname ""
.
$ dirname .
.
$ realpath /nonexistent
(empty, exit 1)
Because #(...) fires on every status-interval (default 15s), a pane whose pane_current_path is no longer valid (deleted worktree, unmounted volume, cleaned-up branch directory) will start an orphaned spinning bash. Subsequent intervals spawn more, so CPU usage stacks over time.
Reproduction
mkdir /tmp/gone && cd /tmp/gone && rmdir ../gone
# wait ~15s for the next status refresh in this pane
ps -eo pid,pcpu,command | awk '$2+0 > 5'
Contributing factors (not the CPU cause, but worth noting)
- No timeouts on
docker, docker compose, or devcontainer read-configuration. A hung Docker daemon stalls status refreshes (idle, not CPU-burning).
devcontainer read-configuration (Node.js) runs at least twice per refresh — there is already a memoization TODO at helpers.sh:77.
Suggested fixes
- Guard the loop — exit when
current_dir is empty, does not start with /, or stops shrinking between iterations. This is the actual CPU bug.
- Add a depth cap as belt-and-braces.
- Wrap
docker / devcontainer calls in a timeout (BSD timeout is not default on macOS — gtimeout from coreutils, or a bash read -t wrapper).
- Memoize
devcontainer read-configuration per status refresh (the existing TODO).
Investigation tips for anyone else seeing this
# find runaway bash
ps -eo pid,pcpu,etime,command | awk '$2+0 > 5'
# confirm it is plugin-related
ps -o pid,ppid,command -p <PID>
# profile what it is actually doing (macOS)
sample <PID> 3 -f /tmp/sample.txt
# look for repeated frames in dirname / get_workspace_dir
Summary
Observed bash processes occasionally using >15% CPU indefinitely, with no obvious trigger. Traced to an infinite loop in
scripts/helpers.sh:get_workspace_dirthat fires from the tmux status bar commands (workspace.sh,status.sh) everystatus-interval.Root cause
scripts/helpers.sh:46-63:The loop only terminates when
current_dir == "/". Two situations make that unreachable:realpathfails (deleted directory, broken symlink, symlink cycle) → prints nothing →current_dir="".dirname ""returns., anddirname "."returns.. Since.!=/, the loop spins forever at ~100% CPU.Verified on macOS:
Because
#(...)fires on everystatus-interval(default 15s), a pane whosepane_current_pathis no longer valid (deleted worktree, unmounted volume, cleaned-up branch directory) will start an orphaned spinning bash. Subsequent intervals spawn more, so CPU usage stacks over time.Reproduction
Contributing factors (not the CPU cause, but worth noting)
docker,docker compose, ordevcontainer read-configuration. A hung Docker daemon stalls status refreshes (idle, not CPU-burning).devcontainer read-configuration(Node.js) runs at least twice per refresh — there is already a memoization TODO athelpers.sh:77.Suggested fixes
current_diris empty, does not start with/, or stops shrinking between iterations. This is the actual CPU bug.docker/devcontainercalls in a timeout (BSDtimeoutis not default on macOS —gtimeoutfrom coreutils, or a bashread -twrapper).devcontainer read-configurationper status refresh (the existing TODO).Investigation tips for anyone else seeing this