Skip to content

fix(rate-limit-guard): reclaim leaked tee temp files and stop windowless clobber - #1822

Merged
kyle-sexton merged 3 commits into
mainfrom
fix/1807-statusline-tee-temp-leak
Jul 31, 2026
Merged

fix(rate-limit-guard): reclaim leaked tee temp files and stop windowless clobber#1822
kyle-sexton merged 3 commits into
mainfrom
fix/1807-statusline-tee-temp-leak

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Summary

Two independent defects in the statusline tee, both verified by reproduction. The temp-file leak is the reported symptom; the windowless clobber is the sharper one, because it destroys usable data rather than littering.

Fix

Defect 1 — no crash-safe reclaim of the atomic-write temp file. Claude Code cancels an in-flight statusline script when a new update arrives while the previous one is still running, and a cancellation between the write and the rename left the temp behind permanently. No failed rm is needed to explain it: the process never reaches the reclaim line, and the only reclaim paths were write-failure and retry-exhaustion.

Two mechanisms, because neither is sufficient alone — the report is right that a trap must not ship as the whole fix:

  • a trap reclaims on exit and on a catch-able signal;
  • an age-filtered sweep of leftover siblings on the next refresh recovers what a SIGKILL, a crash, or power loss leaves, which no trap can.

The sweep is gated on a shell glob rather than on the proposed debounce, which gets the cost property the report wanted without the cadence change: on a clean directory — every refresh in normal operation — it spawns nothing, and it only reaches find when a candidate already exists. Its one-minute age floor cannot race a concurrent session's live temp, whose write-to-rename window is sub-second and bounded by the 300 ms retry loop.

Defect 2 — a windowless session overwrote a snapshot that had windows. On a mixed-auth machine an API-key or enterprise session landed a snapshot with rate_limits absent and a fresh captured_at, so consumers never saw "stale" — they saw a current snapshot with no data and dropped to whole-guard reactive-only, on a machine where a window-bearing session had good data available. The tee now skips the write when this session has no rate_limits and the target already has them. Both tests are substring checks — one on buffered stdin, one on the target read with $(<…) — so no process is added to the hot path. A windowless session still writes when the target has no windows either, so a machine with no window-bearing session keeps an honest staleness signal.

Verification

Reproduced under a throwaway HOME with an mv shim that parks, so the kill lands inside the write-to-rename window deterministically.

tee variant SIGTERM SIGKILL
shipped (origin/main) leaks 1 leaks 1
this PR 0 leaks 1, reclaimed by the next refresh

Sweep, planting one aged orphan and one live sibling then running a normal refresh:

shipped this PR
aged orphan reclaimed no yes
live sibling spared yes yes
snapshot still written yes yes

Windowless clobber:

shipped this PR
target retains rate_limits after a windowless write NO yes
captured_at after that write refreshed, hiding staleness unchanged

Gates:

  • bash plugins/rate-limit-guard/scripts/statusline-tee.test.sh — PASS=41, FAIL=0. Seven new assertions: a cancel-mid-window case, the sweep reclaiming an aged orphan while sparing a live sibling and not disturbing the write, and three windowless-write cases. Case 7's existing "no temp-file residue" assertion — which passed while the invariant was broken, because its shim drives only mv failure — now has the cancellation stand-in it lacked.
  • shellcheck on the tee — clean; check-shell-portability.sh — clean (the test plants an aged file with POSIX touch -t, not GNU touch -d); markdownlint-cli2 and check-changelog-parity.sh --check-order — clean.

What this PR deliberately does not do

Suggestion 3, the mtime debounce, is not taken here. It is the highest-leverage item for latency, and the report's margin analysis (10x against the 600 s staleness rule) is sound — but it is the only suggestion that changes a contract-visible cadence: the reader contract requires consumers to arm a Monitor and re-evaluate on every write, because a write is the only signal the windows changed under them. It is a performance change with a contract consequence rather than a defect fix, and the reason it was coupled to the sweep — spawn cost — no longer applies now that the sweep is glob-gated. Bundling a cadence decision into a data-loss fix seemed the wrong trade; it is left for a maintainer, with the issue open.

Suggestion 6, stale-sibling counting in setup check, is likewise left open — though the fix that most reduces its importance is here: the leak is now self-reclaiming, so the condition the freshness probe cannot see is bounded to about a minute instead of being permanent.

Related

Fixes #1807

🤖 Generated with Claude Code

https://claude.ai/code/session_01RhS3T7ShwJgKTrvk2Mvd3C

…ess clobber

Claude Code cancels an in-flight statusline script when a new update arrives
while the previous one is still running, and a cancellation between the write
and the rename left the atomic-write temp file behind permanently. No failed rm
is needed to explain it: the process never reaches the reclaim line, and the
only reclaim paths were write-failure and retry-exhaustion. 61 orphans were
found clustered in one busy 27-hour window, which is the shape the correlation
predicts — the rename retry loop holds the file open longest exactly when the
target is contended, which is also when the session is busy enough to trigger a
cancelling update.

Two mechanisms, because neither is sufficient alone. A trap reclaims on exit and
on a catch-able signal; an age-filtered sweep of leftover siblings on the next
refresh recovers what a SIGKILL, a crash, or power loss leaves, which no trap
can. Reproduced with an mv shim that parks so the kill lands inside the window:
before, SIGTERM and SIGKILL each leak one file; after, SIGTERM leaks none and a
SIGKILL orphan is reclaimed by the next refresh.

The sweep costs nothing on a clean directory — a shell glob decides whether to
spawn anything at all, so a normal refresh runs no extra process on a path that
already sits at two to four times the 300 ms debounce interval. Its one-minute
age floor cannot race a concurrent session's live temp, whose write-to-rename
window is sub-second and bounded by the 300 ms retry loop.

Separately, a session with no rate-limit windows overwrote a snapshot that had
them. On a mixed-auth machine an API-key or enterprise session landed a snapshot
with rate_limits absent and a FRESH captured_at, so consumers never saw "stale" —
they saw a current snapshot with no data and dropped to whole-guard
reactive-only, on a machine where a window-bearing session had good data
available. Each landing could destroy up to the reader contract's full
ten-minute staleness budget of usable proactive data. The tee now skips the
write when this session has no rate_limits and the target already has them; both
tests are substring checks, one on buffered stdin and one on the target read
with $(<...), so the guard adds no process to the hot path. A windowless session
still writes when the target has no windows either, so a machine with no
window-bearing session keeps an honest staleness signal.

The reader contract's directory inventory named stop-events.jsonl.lock and told
tooling sweeping the directory to expect it while omitting the only litter
actually found there; it now documents the temp shape, why it can outlive its
writer, and that a cleanup tool should leave it alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RhS3T7ShwJgKTrvk2Mvd3C
@claude

claude Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 4m 43s —— View job


PR Review

  • Gather context and understand the request
  • Read changed files: statusline-tee.sh, statusline-tee.test.sh, reader-contract.md, CHANGELOG.md, plugin.json
  • Review fix logic for defect 1 (temp file leak / trap + sweep)
  • Review fix logic for defect 2 (windowless clobber guard)
  • Review test coverage
  • Review CHANGELOG and docs alignment
  • Post findings

Summary

Both defects are real, the fixes are correct, and the tests are thorough. A few details worth discussing, none blocking.


Defect 1 — Trap + Sweep (statusline-tee.sh)

Trap design is correct. Using TEE_TMP as a global (rather than a function local) to survive to trap-fire time is the right call; the comment on line 79–81 explains the invariant clearly. Routing TERM/INT/HUP through exit N so the EXIT trap stays the single reclaim path is a clean pattern that avoids double-reclaim complexity. Signal exit codes (143/130/129) are correct (128 + signal number).

TEE_TMP="" after successful mv (line 175) is correctly placed before return 0, preventing the EXIT trap from rm-ing the successfully-renamed target path.

sweep_stale_tee_temps — glob-gates the find (lines 106–115): When no temp files exist, the glob expands to a literal that fails -e, so no find is spawned. Good. One nuance to be aware of: when a concurrent session's fresh temp exists (sub-second write-to-rename window), the glob matches, the -e check passes, find -mmin +1 runs but deletes nothing, and return 0 is hit. This adds one find spawn to any refresh that races a concurrent write. The probability is very low (the window is sub-second) and the cost is minimal — just worth knowing it isn't zero on a heavily contended machine.

sweep_stale_tee_temps early exit pattern: The for candidate in ...; do [[ -e ]] || continue; find ... ; return 0; done loop invokes find exactly once (the find covers ALL old files with +), then returns. This is correct — but a future reader might wonder why the loop doesn't continue past the first hit. A one-line comment would help (# find covers all; one call is enough). Minor.


Defect 2 — Windowless clobber guard (statusline-tee.sh)

Guard logic (lines 148–154):

if [[ "$payload" != *'"rate_limits"'* && -f "$target" ]]; then
    local existing=""
    existing=$(<"$target") 2>/dev/null || existing=""
    if [[ "$existing" == *'"rate_limits"'* ]]; then
      return 0
    fi
  fi

The substring check for "rate_limits" is safe against false positives from values: jq compact-serializes a session_name value containing rate_limits as "session_name":"…rate_limits…" where the r is not preceded by a literal ", so *'"rate_limits"'* only matches the JSON key, not an embedded value. Correct.

2>/dev/null placement on line 150: In bash, var=$(<file) 2>/dev/null applies the redirect to the assignment command, which suppresses the error bash: file: No such file or directory if the target vanishes between the -f check and the read (TOCTOU). This is valid bash syntax and handles the race correctly via || existing="". It's subtler than existing=$(cat "$target" 2>/dev/null), which might be more immediately readable, but it's not wrong. Fix this →

Sweep skipped on early guard return: A windowless session that detects a window-bearing target (line 153: return 0) also skips sweep_stale_tee_temps entirely. Stale orphans will wait until the next window-bearing session's write. This is a deliberate tradeoff (sweep is coupled to the write path), and on a healthy machine windowless sessions are infrequent enough that it's acceptable. Worth documenting in the comment on that return 0 if this ever becomes a question.


Test coverage

Case 14 (trap/cancellation) — the sleep 2 before kill -TERM relies on the mv shim's sleep 10 running long enough to still be active. On a heavily loaded CI host this could in theory race, but it's the standard approach for deterministic signal-window tests and is unlikely to be fragile in practice.

Case 15 (sweep / aged orphan)touch -t 200001010000 is correct POSIX syntax (no GNU dependency), matching the portability note in the PR description. FRESH15 being left at the current mtime means find -mmin +1 correctly spares it. Good.

Cases 16/17 (clobber guard / windowless write) — comprehensively cover: (a) windowless does not clobber windows, (b) captured_at is unchanged, (c) windowless still writes on a fresh machine. All three assertions in 16 map directly to the failure scenario.

The SIGKILL case is correctly not tested with a trap (can't trap it) and instead covered indirectly through Case 15's sweep. The PR's verification table makes this gap and the reclaim path explicit, which is the right way to document it.


Documentation

reader-contract.md additions (lines 103–113) are accurate: the temp-file naming convention, the reclaim mechanisms, and the "leave these alone" guidance for cleanup tools are all consistent with the implementation.

CHANGELOG is detailed and correct. The reproduction table from the PR body would be a good addition here, but the prose description is clear enough for a changelog entry.

Version bump 0.3.6 → 0.4.0 is appropriate: the clobber guard changes observable write behavior (a windowless session no longer always writes), which is a minor-level behavioral change even though the intent is purely defensive.


No issues found in

  • Signal exit code arithmetic (143/130/129) ✓
  • reclaim_tee_tmp idempotency (clears TEE_TMP immediately so a double-call is a no-op) ✓
  • find -exec rm -f {} + safety (no injection vector; dir is constructed from $HOME) ✓
  • chmod 700 on the dir mitigates any symlink-planting risk from the $RANDOM-based temp name ✓
  • jq filter field selection unchanged ✓

@claude

claude Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 4m 32s —— View job


Security Review — PR #1822

  • Gather context and understand the request
  • Read changed files and diff
  • Analyze shell script security: injection surfaces, quoting, temp file handling
  • Analyze logic: race conditions, trust boundaries, guard correctness
  • Post findings

Reviewed statusline-tee.sh (+75/-2) and statusline-tee.test.sh (+76/-0) — the two files where meaningful security surface lives. plugin.json, CHANGELOG.md, and reader-contract.md carry no executable surface. No GitHub Actions files changed.

Findings

SUGGESTION | PLAUSIBLE — Windowless-write guard defeated by a retained key's value

The guard checks for the presence of a rate_limits key using a shell substring match rather than a jq parse:

if [[ "$payload" != *'"rate_limits"'* && -f "$target" ]]; then
local existing=""
existing=$(<"$target") 2>/dev/null || existing=""
if [[ "$existing" == *'"rate_limits"'* ]]; then
return 0
fi
fi

if [[ "$payload" != *'"rate_limits"'* && -f "$target" ]]; then
    ...
    if [[ "$existing" == *'"rate_limits"'* ]]; then

The pattern *'"rate_limits"'* matches the literal four-character sequence "rate_limits" — which appears in jq output for either a key ("rate_limits":) or a string value (:"rate_limits").

The jq filter retains the keys session_id, session_name, and any key matching test("account"; "i") alongside rate_limits. If a user sets their session name to exactly the string rate_limits, the payload produced by jq will be {"captured_at":"…","session_name":"rate_limits"}, which matches the guard pattern. Both directions are affected:

  • False negative on $payload check: the guard concludes rate_limits is present, skips the early return, and allows a windowless session to overwrite a window-bearing snapshot — the exact bug this PR is fixing.
  • False positive on $existing check: the guard concludes the existing file has window data when it does not, preventing a windowless-only machine from writing its staleness signal.

Exploiting the false negative requires a user to have named a Claude Code session exactly rate_limits, which is user-controlled but low probability. The blast radius is local and self-inflicted: only the user's own rate-limit snapshot is affected, with no cross-user or privilege-escalation component. Reported as SUGGESTION rather than IMPORTANT because it requires deliberate (if accidental) session naming and the trust boundary never crosses users.

A safe fix is one more jq parse in-flight before the skip decision, or restructuring the jq filter to emit a sentinel field rather than relying on key-presence detection in shell. Fix this →


What checked out

  • No injection surfaces introduced: find "$dir" … -exec rm -f {} + in sweep_stale_tee_temps passes $dir double-quoted and uses a fixed string -name pattern. The + form prevents per-filename shell expansion. No jq template injection: timestamp is passed via --arg, not inline.
  • Trap safety: reclaim_tee_tmp is idempotent (clears TEE_TMP after removal); signal traps re-raise via exit N so the EXIT trap remains the single reclaim path. No double-free or missed-reclaim gap visible.
  • Subshell umask: the temp file is written (umask 077; printf … >"$tmp"), correctly scoped — the wrapped command inherits the original umask.
  • $RANDOM entropy: $$.$RANDOM is low entropy (32k values), but the directory is chmod 700, so only the owner can enumerate or predict contents. Not a meaningful attack surface without pre-existing owner-level compromise.
  • Sweep age floor: the -mmin +1 guard on find ensures no concurrent session's in-flight temp (write-to-rename is sub-second) is ever a candidate. The glob pre-check correctly gates the find spawn.
  • Test make_mv_shim: the generated shim embeds counter path and REAL_MV via printf '%s' format strings; those values come from mktemp -d and command -v mv, neither of which contain quote characters on any standard system. Scoped to the test environment.

No CRITICAL or IMPORTANT findings. The one SUGGESTION above is a logic gap in the new windowless-write guard that is unlikely to be triggered in practice.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8a341ad73e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/rate-limit-guard/scripts/statusline-tee.sh Outdated
Comment thread plugins/rate-limit-guard/scripts/statusline-tee.sh Outdated
Comment thread plugins/rate-limit-guard/scripts/statusline-tee.sh Outdated
kyle-sexton and others added 2 commits July 30, 2026 16:43
…p before skip

Three review findings on the windowless-clobber guard. Window-bearing is
now decided with jq has(), not a substring a forwarded value like
"session_name":"rate_limits" defeats. The preservation check and the
rename are serialized through a mkdir writer lock with a stale-holder
age floor, closing the check-then-write race between a windowless and a
window-bearing writer. The orphan sweep runs before the preservation
early-return so a machine with only windowless sessions still reclaims
a killed session's temp. Regression tests for all three.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01STASxNiqEm7doy2Z65un4H
…ee-temp-leak

# Conflicts:
#	plugins/rate-limit-guard/.claude-plugin/plugin.json
#	plugins/rate-limit-guard/CHANGELOG.md
@claude

claude Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error after 1s —— View job


I'll analyze this and get back to you.

@claude

claude Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error after 1s —— View job


I'll analyze this and get back to you.

@github-actions

Copy link
Copy Markdown

Warning

Automated review did not complete — this is an infrastructure failure, not a review verdict.

Treat any Claude comment on this PR (including a placeholder like "I'll analyze this and get back to you") as incomplete, not "no findings."

Re-run the job to retry the review. A new push does not re-trigger this lane.
An automatic retry may already have run — it is skipped when a partial review could duplicate comments, or when the failure class needs an operator (auth).

@claude

claude Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 4m 31s —— View job


Security Review — PR #1822

  • Gather context (changed files at current HEAD)
  • Review latest commit (cd2fa56: structural jq has() check, writer lock, sweep-before-skip ordering)
  • Re-analyze shell script security: injection, quoting, temp file/lock handling
  • Analyze logic: race conditions, trust boundaries, guard correctness
  • Post findings

Reviewed statusline-tee.sh at HEAD (e0dc9b6), focusing on the changes since the last review round: the move from substring matching to jq -e 'has(...)', the new acquire_tee_lock/release_tee_lock mkdir-based lock, and the sweep-before-early-return reordering. These close the three issues raised by Codex and the earlier automated review. One residual logic gap remains in the lock's fallback path.

Findings

SUGGESTION | Confidence: Low (narrow, timing-dependent, Windows-specific) — Lock's "proceed unlocked on acquisition failure" path can still let a windowless write clobber a concurrent window-bearing write

acquire_tee_lock() {
local dir="$1" lock="$1/.rate-limits.json.lock" _try
# shellcheck disable=SC2034 # bounded-retry counter; the value itself is unused
for _try in 1 2 3; do
if mkdir "$lock" 2>/dev/null; then
TEE_LOCK="$lock"
return 0
fi
find "$dir" -maxdepth 1 -type d -name '.rate-limits.json.lock' \
-mmin +1 -exec rmdir {} + 2>/dev/null || true
sleep 0.1 2>/dev/null || true
done
return 1
}

if ! acquire_tee_lock "$dir"; then
[[ "$has_windows" == true ]] || return 0
fi
# A session with no windows — API-key or enterprise auth — must not overwrite
# a snapshot that HAS them. The reader contract routes a snapshot missing
# rate_limits to whole-guard reactive-only, and this write would carry a FRESH
# captured_at, so consumers would never see "stale" and would instead see a
# current snapshot with no data: up to the contract's full 10-minute staleness
# budget of usable proactive data destroyed on a mixed-auth machine, silently.
# A target jq cannot parse counts as windowless — torn or corrupt content is
# exactly what an atomic overwrite should replace.
if [[ "$has_windows" != true && -f "$target" ]]; then
if jq -e 'has("rate_limits")' "$target" >/dev/null 2>&1; then
release_tee_lock
return 0
fi
fi
local tmp="$dir/.rate-limits.json.tmp.$$.$RANDOM"
TEE_TMP="$tmp"
# Subshell umask so the snapshot lands owner-only without altering the
# umask the wrapped statusline command inherits.
(
umask 077
printf '%s\n' "$payload" >"$tmp"
) 2>/dev/null || {
reclaim_tee_tmp
release_tee_lock
return 0
}
local _try
# shellcheck disable=SC2034 # bounded-retry counter; the value itself is unused
for _try in 1 2 3; do
if mv -f "$tmp" "$target" 2>/dev/null; then
# The temp path is the target now; clear it so the EXIT trap cannot
# reclaim a name that no longer refers to this refresh's file.
TEE_TMP=""
release_tee_lock
return 0
fi
sleep 0.1 2>/dev/null || true
done
reclaim_tee_tmp
release_tee_lock

The new lock correctly serializes the check-then-rename between two lock-abiding writers. But acquire_tee_lock gives up after 3 attempts (~0.2–0.3s total, L141-149), and on failure a window-bearing writer proceeds unlocked (L195-197) — by design, per the inline comment and Case 20's test. Separately, the windowless writer's mv -f retry loop (L226-237) runs while still holding the lock and can itself take up to ~0.2–0.3s on the documented Windows EACCES-retry path (the exact case the retry loop exists for, per the file's own ATOMICITY comment, L34-43).

Those two windows are the same order of magnitude, so this interleaving is reachable:

  1. Windowless writer W-less acquires the lock, checks the target (no windows present at that instant), passes the guard, and begins its mv -f retry loop — but the first mv hits a transient EACCES and retries, so W-less is still holding the lock past T+0.1s.
  2. Window-bearing writer W-full calls acquire_tee_lock, fails all 3 attempts (W-less still holds it), and per L195-197 proceeds unlocked: it writes its own tmp file and successfully mv -fs real window data onto the target.
  3. W-less's own retry loop (still executing, unaware of step 2) succeeds on its 2nd or 3rd attempt and unconditionally mv -fs its windowless payload — with a fresh captured_at — over the target, silently destroying the window data W-full just wrote.

The net effect reproduces the exact defect this PR fixes (a windowless session clobbers good window data with a fresh timestamp, hiding staleness from consumers) — just narrowed to the specific interleaving where a Windows rename-retry and a concurrent lock-acquisition timeout overlap, rather than the previous unconditional case. The design comment on L195-197 ("last-writer-wins between window-bearing snapshots is the pre-existing contract") only reasons about two window-bearing writers racing; it doesn't cover a window-bearing writer racing a windowless writer's delayed completion of an already-passed check.

A structural fix would re-check has_windows on the target immediately before W-less's mv (inside the retry loop, after the lock is confirmed still held), rather than relying on a check made once before the loop began. Given the narrowness (requires the Windows EACCES-retry branch plus tight concurrent timing), this doesn't block the PR, but it's worth a maintainer decision on whether to close it now or track it alongside the existing TODO(#1218) single-account gap. Fix this →


What checked out

  • Structural window detection (L177-182, L207-211): both the payload and the existing target are now tested with jq -e 'has("rate_limits")', closing the substring false-positive/false-negative Codex and the prior review flagged ("session_name":"rate_limits" no longer misclassifies).
  • Sweep-before-skip ordering (L184-187): sweep_stale_tee_temps now runs unconditionally before the windowless early-return, so a machine where only windowless sessions remain active still reclaims an orphan left by a killed window-bearing session.
  • Lock directory hygiene: mkdir as the lock primitive is atomic on every platform this script targets, including Git Bash; the stale-lock steal uses the same 1-minute age floor as the temp sweep, well above any live writer's hold time; rmdir only removes empty directories, no risk of removing a populated/foreign directory.
  • No new injection surfaces: find/rmdir/mkdir calls use fixed -name patterns and quoted "$dir"; no jq template injection (timestamp still passed via --arg).
  • existing/target read via direct jq -e ... "$target" rather than shell substring — also removes the earlier 2>/dev/null TOCTOU-suppression subtlety a prior review flagged, since jq now handles a vanished/corrupt target by failing closed into "treat as windowless" (documented as intentional at L205-206).
  • No GitHub Actions files changed in this diff; no secrets/token handling touched.

No CRITICAL or IMPORTANT findings with high confidence. The one SUGGESTION above is a narrow, timing-dependent residual of the same defect class this PR fixes — real but unlikely to trigger outside the Windows rename-retry path under tight concurrent contention.

@kyle-sexton
kyle-sexton merged commit 656de7a into main Jul 31, 2026
32 of 33 checks passed
@kyle-sexton
kyle-sexton deleted the fix/1807-statusline-tee-temp-leak branch July 31, 2026 02:26
@kyle-sexton
kyle-sexton restored the fix/1807-statusline-tee-temp-leak branch August 1, 2026 01:39
@kyle-sexton
kyle-sexton deleted the fix/1807-statusline-tee-temp-leak branch August 14, 2026 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

rate-limit-guard: statusline tee leaks temp files on harness cancellation, and a windowless session's write flips consumers to reactive-only

1 participant