fix(statusline): bound the /tmp pai-parallel leak (follow-up to #1297) - #1767
Open
gcaspar wants to merge 1 commit into
Open
fix(statusline): bound the /tmp pai-parallel leak (follow-up to #1297)#1767gcaspar wants to merge 1 commit into
gcaspar wants to merge 1 commit into
Conversation
Cleanup lived only on the exit path -- the rm -rf sits ~480 lines after the mkdir, past several network calls -- so any render Claude Code killed leaked its scratch dir permanently. At refreshInterval=1 across 4 sessions this reached 244,760 directories and exhausted the tmpfs inode table while df -h still showed 81% free, which kills every shell-based tool in the session. Adds two layers, because neither is sufficient alone: - Entry-path sweep of dirs older than a minute. The only layer that covers SIGKILL, which cannot be trapped. The trailing slash on /tmp/ is required or this is a silent no-op on macOS, where /tmp is a symlink find(1) will not descend into. - A trap, measured to fully close the group-SIGTERM path: 5/5 renders leaked without it, 0/5 with it. Per-PID dirs are kept deliberately; a fixed name races and concurrent sessions clobber each other's files mid-source. Follow-up to danielmiessler#1297, which was closed for architectural reasons before the installer layout landed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #1297, which was closed on 2026-06-21 for architectural reasons rather than on merit ("If the need still stands against the new model, we'd love a fresh PR once it lands"). The new model has landed, the leak is still present in the installer layout, and it took down a box last week — so here is that fresh PR.
Still present on
maintodayLifeOS/install/LIFEOS/LIFEOS_StatusLine.sh:_parallel_tmp="/tmp/pai-parallel-$$"mkdir -p "$_parallel_tmp"rm -rf "$_parallel_tmp" 2>/dev/null482 lines apart, several of them network calls, with no
trap. Cleanup is on the exit path only, so any invocation that doesn't reach L1067 leaks a directory permanently.A second failure mode worth knowing about: inodes, not bytes
#1297 reported this as a disk-space problem (~8 GB of block metadata on ext4). On a tmpfs it presents completely differently and is much easier to misdiagnose.
Headless Ubuntu 22.04 box,
/tmpon a 1.9 GB tmpfs, 4 concurrent SSH sessions,refreshInterval: 1:Every shell tool died with
ENOSPC: no space left on devicewhiledf -hshowed 380 MB free, because Claude Code stages each Bash command and its output under/tmp. Anything shell-based (curl,jq, evenecho) fails; only the non-shell file tools keep working. If you're debugging this,df -iis the tell —df -hwill send you the wrong way.Diagnosis is also slow once it happens:
ls -la /tmp,du, and any recursivefindall hang on ~250k entries.ls -Uandfind -maxdepth 1are the usable tools.Correction to #1297: a trap does help, for the dominant signal path
#1297 states that a
trap ... EXIT INT TERM HUP"does not help" because Claude Code invokes via/bin/sh -cand the bash child blocked inwaitnever receives the signal. That holds for one termination pattern but not the one that actually leaks. Measured on the affected box, real script, 5 reps per cell, killed at 0.4 s into a run that takes ~0.78 s when healthy:SIGTERMto theshparent onlySIGTERMto the process groupSIGKILLto the process groupReading:
shparent never leaks either way — the orphaned bash child keeps running and reaches L1067 on its own. This is likely the case statusline-command.sh: /tmp leak from pai-parallel-$$ dirs that are never cleaned up #1297 tested.SIGTERMis the path that leaks, and the trap fixes it completely.SIGKILLis untrappable, so no in-script trap can ever be sufficient on its own.So neither layer alone is enough: the trap covers group-
SIGTERM, the entry-path sweep from #1297 coversSIGKILL. This PR ships both.The fix
Notes on the details, all of which matter:
sourceand statusline sections silently vanish. (Same conclusion as statusline-command.sh: /tmp leak from pai-parallel-$$ dirs that are never cleaned up #1297.)/tmp/is required, not cosmetic. Per @jmmarkiewicz in statusline-command.sh: /tmp leak from pai-parallel-$$ dirs that are never cleaned up #1297: on macOS/tmpis a symlink to/private/tmp, andfind(1)will not descend into a symlinked start point, sofind /tmp -maxdepth 1 ...silently matches nothing — the same "looks like it ran, didn't" failure as the original bug.-mmin +1is comfortably safe. A healthy render completes in ~0.78 s.Verification
Worst case, every single render
SIGKILLed mid-run (so the trap can never fire and L1067 is never reached):Accumulation is bounded by one minute of render rate instead of being unbounded. On the affected box, steady state went from 244,760 dirs to ~50, and inode usage from 100% to 3%.
bash -nclean. Tested on Ubuntu 22.04 / bash 5.1. The macOSfindbehaviour is quoted from #1297 rather than retested here.Operator note, separate from this patch
refreshInterval: 1is the amplifier — it turns any statusline bug into a 1 Hz bug, and a healthy render already takes ~0.78 s, so renders nearly overlap. Worth considering a higher default independently of this fix.