fix(tui): stop background cache-warning writes from corrupting TUI screen - #906
Merged
Conversation
…reen The TUI's screen occasionally gets scrolled/corrupted on a background cache save failure (#893). Two stackable root causes: 1. On Windows, MoveFileExW replacing the cache file is a well-known source of transient ERROR_ACCESS_DENIED/ERROR_SHARING_VIOLATION when AV/indexer/cloud-sync briefly holds a scan handle open on the just-written file. fs_atomic::windows_replace_file made a single attempt with no retry. 2. warn_cache_failure_once() falls back to a raw eprintln! the first time each failure context is seen, since most non-TUI commands never install a tracing subscriber. But the TUI only installs one under --debug, so in normal use that eprintln! is the only visible output — and it writes directly to stdio while the TUI owns raw mode and the crossterm alternate screen, corrupting the display. Fixes both: retry the Windows rename a few times with a short backoff on the specific transient errors (benefits all 10 existing fs_atomic::replace_file call sites, not just the cache), and add a process-wide tui_signal flag the TUI sets while it owns the terminal so the eprintln! fallback is suppressed during that window instead of corrupting the screen. Constraint: fs_atomic::replace_file is a single shared primitive used by 10 call sites across both crates — the retry must be transparent with no call-site changes Rejected: Redirect all cache warnings through the TUI's own status/error UI | would require threading a channel through every non-TUI call site too; the flag-gated eprintln! keeps non-TUI behavior byte-for-byte unchanged Confidence: high Scope-risk: narrow Not-tested: actual Windows MoveFileEx retry behavior, no Windows machine available in this environment — relying on build-native.yml CI's Windows job plus manual FFI review
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
CI's Lint job runs cargo fmt --all -- --check across the whole workspace, not just this PR's diff. Two unrelated files on main had already drifted out of rustfmt's canonical form before this branch forked, which was failing this PR's Lint check despite neither file being touched by the TUI fix itself. Constraint: cargo fmt --all -- --check must pass workspace-wide, not just on changed files Confidence: high Scope-risk: narrow
This was referenced Jul 17, 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.
Fixes #893
Root causes
Two independent, stackable issues cause the TUI's screen to get corrupted when a background cache shard save fails:
Why the write fails on Windows (
Access is denied (os error 5)):fs_atomic::windows_replace_filecallsMoveFileExW(..., MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)exactly once, with no retry. This is a well-known source of transientERROR_ACCESS_DENIED(5) /ERROR_SHARING_VIOLATION(32) on Windows — antivirus, indexing, and cloud-sync agents routinely hold a brief scan handle open on a just-written file.fs_atomic::replace_fileis the single shared atomic-rename primitive used by 10 call sites across both crates, so this failure mode isn't unique to the cache.Why the failure corrupts the TUI display:
message_cache::warn_cache_failure_oncealways callstracing::warn!, then falls back to a raweprintln!the first time each failure context is seen — because most non-TUI commands never install a tracing subscriber, sotracing::warn!alone would be silent. But the TUI only installs a subscriber under--debug. In normal (non-debug) use, thateprintln!writes directly to stdio while the TUI owns raw mode and the crossterm alternate screen, corrupting the rendered display exactly as reported.Fix
fs_atomic.rs: wrap the WindowsMoveFileExWcall in a bounded retry loop (5 attempts, ~10ms × attempt backoff, ≤100ms worst case), retrying only on the two known-transient error codes. Benefits all 10 existingreplace_filecall sites with no call-site changes. Non-Windows path (std::fs::rename) is untouched.tui_signal.rs: a small process-wideAtomicBoolflag (set_tui_active/is_tui_active), mirroring the existing style ofpaths::is_config_dir_overridden().message_cache.rs: gate theeprintln!fallback inwarn_cache_failure_onceon!tui_signal::is_tui_active().tracing::warn!still fires unconditionally and is unchanged. Non-TUI command behavior is byte-for-byte unchanged.tui/mod.rs: toggle the flag totrueright after the TUI successfully enters the alternate screen, and back tofalsein both terminal-restore paths (normal exit and the panic hook's best-effort restore), so it can never get stuck.Verification
cargo build -p tokscale-core -p tokscale-cli— cleancargo clippy -p tokscale-core -p tokscale-cli --all-targets -- -D warnings— zero warningscargo test -p tokscale-core— 1216 passed, 0 failed, including a newtui_signal::tests::round_tripstestcargo fmt --check— cleanunsafe extern "system"MoveFileExWretry loop — no Windows machine available locally to compile/run the#[cfg(windows)]path; relying on the existingbuild-native.ymlWindows CI job plus this manual reviewNot tested: actual Windows
MoveFileExretry behavior under real transient file-lock contention (AV/indexer/cloud-sync), since no Windows machine is available in this environment.Summary by cubic
Prevents TUI screen corruption when background cache save warnings fire by suppressing raw stderr during TUI and making Windows file replacement more resilient. Fixes #893.
Bug Fixes
MoveFileExWinfs_atomic::replace_file(up to 5 attempts with short backoff) on transientERROR_ACCESS_DENIED/ERROR_SHARING_VIOLATION. Non-Windows unchanged.tokscale-core::tui_signaland toggle it fromtokscale-cliwhen entering/leaving the TUI; suppressmessage_cache::warn_cache_failure_onceeprintln!while active.tracing::warn!unchanged; non-TUI behavior remains the same.Refactors
cargo fmtto satisfy CI; unrelated files only, no behavior changes.Written for commit eb31d94. Summary will update on new commits.