Make broadcast paste immune to a wedged Tailscale daemon - #7
Merged
Conversation
Typing the path now happens the moment the compressed file exists, before peer discovery. The typed string never depended on which hosts are reachable, yet it waited on tailscale status --json — which hangs for tens of seconds when tailscaled is starved (UDP-blocked networks), so a burst of pastes flushed all at once when the daemon recovered. Three layers, so no single one is load-bearing: - Broadcast.handle types before discovery. Uploads already raced the typed path, so the ordering exposes no window that wasn't open before. - runShell gains a watchdog timeout (3s for status): a hung child gets SIGTERM and the result reports timedOut, failing open to "attempt all hosts, let SSH's ConnectTimeout filter". - TailscaleStatusCache (30s TTL, single-flight): a paste burst and every menu render ride one status subprocess instead of spawning their own. Settings' "Refresh peers" bypasses the TTL so the button stays honest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.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
⌘⌥Vbroadcast pastes would lag, then flush several pastes at once a minute or two later. Root cause: on a UDP-blocked/VPN network,tailscaledgets wedged (netcheck failures,health.Change is slowevents up to ~99s), and each broadcast rantailscale status --jsonsynchronously. Every queued paste blocked on that call and then completed together the instant the daemon recovered — producing the burst.Latency data confirmed the scope: every paste ≥10s was a
Broadcast; local⌘Vpastes stayed at ~0–1s.Fix
Three layers, so no single one is load-bearing:
Broadcast.handletypes before peer discovery. The typed path never depended on which hosts are reachable, and uploads already raced it on the remote side — so the reorder exposes no window that wasn't already open. The paste no longer waits on the network at all.runShellgains a watchdog timeout (3s forstatus). A hung child getsSIGTERM, the pipe reads unblock on EOF, and the result reportstimedOut— failing open to "attempt every host, let SSH'sConnectTimeoutfilter."TailscaleStatusCache(30s TTL, single-flight). A paste burst and every menu render ride onestatussubprocess instead of each spawning its own. Settings' "Refresh peers" bypasses the TTL so the button stays honest.Scope / trade-offs
This makes CopyCat immune to a wedged daemon — it does not fix the daemon itself. When Tailscale is genuinely down, broadcasts fail open (attempt all hosts, SSH times out), so a paste made while offline may not reach peers — but it always types instantly and never bursts.
Tests
TailscaleStatusCacheTests— TTL, single-flight burst sharing, empty-result caching, forced refetch.ShellTests— timeout kills a hung process; fast processes and the no-timeout path are unaffected.swift buildclean.🤖 Generated with Claude Code