fix(server): idle-reap detached terminals whose only activity is repaint noise - #539
Merged
Conversation
TDD plan: NoiseScanner repaint-noise fingerprinter (new module) + a last_meaningful_activity_at reap clock for enforce_idle_kills, plus DEV-0009 oracle deviation entry. Wire-visible lastActivityAt semantics unchanged. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…ion findings Stage-2 assumption validation (6 verified, 6 falsified; ledger + evidence in .worktrees/.the-usual-logs/idle-repaint-noise/). Plan fixes: - Ring 8 -> 32: live capture of codex 0.145.0 shows its shimmer header emits ~13-16 letter-subset fingerprints per cycle; a ring of 8 never settles and codex would never be reaped. New shimmer pinning test. - Strip set gains claude's real glyph (U+2736) and bullets U+2022/U+25E6. - New detach-grace wiring (detach() + remove_connection(), gated bump) with two pinning tests: a freshly-detached terminal gets one full threshold instead of dying on the next 30s sweep. - Frame-delivery facts corrected to measured reality (kernel-read boundaries <=4095 B, coalescing multiplicities, env-override limit). - Numeric-only-progress reaping (curl/dd-class) recorded as an explicit accepted product decision (AD-1) in contract, semantics note, DEV-0009. - DEV-0009 reshaped to the oracle's canonical seven-field unbolded schema; lib.rs module-visibility wording fixed. - Cross-crate 'Expected: PASS' re-scoped to 'no NEW failures' over the recorded pre-existing 18-test freshell-ws baseline. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…yes review - Task 3 Step 1: cargo test rejects two positional TESTNAME filters; split into three single-filter invocations and correct the expected test counts (1, 1, then 2). - Task 1 Steps 1/5/6: the not-yet-wired pub(crate) NoiseScanner emits dead_code warnings, contradicting Step 6's 'no new warnings' gate. Make the #[allow(dead_code)] unconditional on the mod idle_noise; declaration in Step 1, align Step 5/6 expectations, and have Task 2 remove the attribute (Files list, Step 3a, git add, commit message) when registry.rs wires the scanner in. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…idle reap clock Stateful per-terminal frame classifier: escape-stripped, digit/spinner- glyph-stripped FNV-1a fingerprints checked against a ring of the 32 most recent frames (sized for codex's shimmer animation, which emits ~16 distinct letter-subset fingerprints per cycle). Repaint noise (spinners, shimmer churn, ticking counters, status-bar redraws) classifies false; genuinely new content classifies true. Groundwork for keying enforce_idle_kills on meaningful activity (DEV-0009). Fails open: unrecognized content counts as activity. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
…oise can't exempt detached terminals enforce_idle_kills now reads a new private last_meaningful_activity_at clock, refreshed by input writes and by output frames the NoiseScanner classifies as genuinely new content. Spinner frames, ticking elapsed counters, and status-bar redraws no longer reset the reap clock, so an abandoned detached animated TUI is reaped after autoKillIdleMinutes (DEV-0009; second server-side hole behind the 2026-07-25 orphaned- terminal incident — the client-side hole was PR #534). Transition-to-detached (explicit detach or socket close) grants one full threshold of grace: the meaningful clock may have gone stale while a watcher was attached (attached terminals are reaper-exempt), and a just-backgrounded terminal must not be killed by the next 30s sweep. Wire-visible lastActivityAt semantics are unchanged: still bumped on every PTY output frame and every input write (terminal-core.md §1.3). Attached terminals stay exempt; autoKillIdleMinutes <= 0 stays a no-op. Also drops the temporary #[allow(dead_code)] on mod idle_noise now that the scanner has a consumer. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Ledger entry (status: proposed) for keying enforce_idle_kills on the meaningful-activity clock instead of the every-frame lastActivityAt. Objective defect bar: resource leak (detached animated TUIs were never reapable, 2026-07-25 incident). Pinning tests referenced and passing. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Fix two issues in the DEV-0009 entry of port/oracle/DEVIATIONS.md: - Replace wrong glyphs (U+23FB '⏻' and U+23F6 '⏶') with correct ones (U+273B '✻' and U+2736 '✶') in the objective_defect and port_behavior fields. These now match the glyphs used in idle_noise.rs spinner-glyph set. - Update the doc comment on registry.rs:input() to mention both the lastActivityAt bump and the DEV-0009 meaningful-activity reap clock bump that the function performs. Verified with hexdump: the bytes are now e2 9c bb (✻) and e2 9c b6 (✶). 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-authored-by: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
pull Bot
pushed a commit
to HinchK/freshell
that referenced
this pull request
Jul 27, 2026
…nal yields clean respawn waveC integration interaction pin (C2 x PR danshapiro#539): a detached terminal reaped by enforce_idle_kills while its pane is mid-reconcile must converge to a respawn verdict on the next round - never attach to the reaped id, never a wedge. Exercises the real seam: the sweep marks the terminal not-live, so the verdict scan falls through to the recovery rows. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
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.
Problem
The idle auto-kill sweep (
enforce_idle_kills,crates/freshell-terminal/src/registry.rs) useslast_activity_at, which is bumped by every PTY output frame. A detached, abandoned coding-CLI TUI that repaints (codex braille spinner / ticking "(Ns • esc to interrupt)" counter, claude's ticking "Crunched for Ns", status-bar clocks) refreshes it continuously and is never reaped. Second of two holes behind the 2026-07-25 orphaned-terminal incident (first was the client attach leak, fixed in #534; verify hardening in #535; analysis doc landed in #538, D-series findings).Fix (rust server only, legacy untouched)
NoiseScannermodule (17 unit tests): strips digits, ANSI escape sequences, and spinner glyphs from each output frame and fingerprints the residue against a 32-slot memory of recent fingerprints — repeated fingerprints = repaint noise, novel residue = meaningful output.lastActivityAtis unchanged.autoKillIdleMinutes <= 0remains disabled; attached terminals remain exempt; fails safe (when in doubt, keep alive).Accepted trade-offs (documented in DEV-0009 / plan doc docs/plans/2026-07-26-idle-repaint-noise.md)
AD-1: a detached program whose only changing output is a bare numeric counter (e.g. raw curl/dd byte counts) is indistinguishable from a ticking clock and will be reaped after the threshold; unit transitions (kB→MB) and bar-style meters (wget/pip/docker) reset the clock. Mitigation: user-adjustable threshold.
AD-2: extremely chatty frames (4KiB+ per read) overflow the detector and are never reaped — same as today's behavior, fail-safe.
Verification
TDD throughout; load-bearing assumption audit falsified 6 assumptions and the plan was corrected for each (including pinning the real codex shimmer animation behavior); all suites green (153/153, 319/319, 255/255 — 18 pre-existing baseline failures in an unrelated area predate this branch, verified no NEW failures); clippy + lint clean; independent whole-branch review "ready to merge"; fresh-eyes delta review passed round 1 with zero blocking issues.