tunnel-doctor

Diagnoses concrete tunnel and proxy-path failures across macOS and Windows/WSL: Tailscale routing, proxy env/system bypass, SSH double tunneling, VM/container propagation, stalled DNS, TUN DIRECT split-brain, Windows-host TUN cascades, and single-hop or chained proxy node/exit throughput. Use when T

By daymade · 571 installs

npx skills add daymade/claude-code-skills --skill tunnel-doctor

Source repository · Upstream listing

Tunnel Doctor Diagnose and fix conflicts when Tailscale coexists with proxy/VPN tools on macOS, with specific guidance for SSH access to WSL instances. Methodology base: the general diagnostic discipline this skill builds on — evidence over assumption, falsification over confirmation, layered isolation, counter review — lives in the debugging network issues skill. This skill is the macOS Tailscale⨯proxy domain layer on top of it; reach for the base skill when the symptom is not a known Tailscale/proxy conflict. Ownership boundary: this skill owns concrete local network path operations: Tailscale, route/DNS/proxy interception, VM/WSL propagation, TUN forwarding planes, and proxy node/exit/chain capacity. Use debugging network issues as the general method when the boundary is still unknown or the symptom belongs to SSE/CDN/application protocol behavior. This keeps one concrete operator instead of forcing the user to choose between two overlapping proxy doctors. Target and task scope Before remote probes or changes, establish whether the target is a designated test machine, an explicitly requested colleague's working computer, or unknown. Use the owner's existing machine purpose ledger and current task; do not log into a colleague's machine to classify it. Saved SSH access and previous successful tests do not grant ongoing use. Use designated test machines for routine testing; their unavailability does not authorize another person's computer. Keep an explicitly requested one off test on a colleague's computer within its stated result and necessary verification. Stop there on completion, cancellation, or a need for an unapproved environment change. Move later performance, migration, and regression tests to designated test machines. A test failure does not authorize disabling that person's proxy/VPN, quitting apps, changing routes, or installing a background task. Proceed with disruptive repair there only when the user has explicitly requested that repair and its interruption. Existing authorization for owner test machine maintenance remains valid; do not ask again for its routine steps. Apply this boundary even when invoked directly, through another Skill, after compaction, or through a peer handoff. After access is stopped, use existing records rather than another SSH probe to verify that it stopped. Disruptive network changes Before any authorized VPN disconnect, app termination, route cutover, or service restart, read [network change recovery.md](references/network change recovery.md). Validate prerequisites before interruption, arm bounded recovery before the first disruptive action, and verify both the repaired path and the user's original network use. A successful stop command, a detached process, or a restart placed at the end of a script is not recovery evidence. Conflict Layers Proxy/VPN tools on macOS create conflicts at several independent layers. Layers 1 3 affect Tailscale connectivity; Layer 4 affects SSH git operations; Layer 5 affects VM/container runtimes. TUN state failure modes beyond this table — SSH/git connection drops, resolver stall, DIRECT split brain — are covered in Steps 2H–2J: Layer What breaks What still works Root cause 1. Route table Everything (SSH, curl, browser) tailscale ping tun excluded routes adds en0 route overriding Tailscale utun 2. HTTP env vars curl , Python requests, Node.js fetch SSH, browser http proxy set without NO PROXY for Tailscale 3. System proxy (browser) Browser only (HTTP 503) SSH, curl (both with/without proxy) Browser uses VPN system proxy; DIRECT rule routes via Wi Fi, not Tailscale utun 4. SSH ProxyCommand double tunnel git push/pull (intermittent) ssh T (small data) connect H creates HTTP CONNECT tunnel redundant with Shadowrocket TUN; landing proxy drops large/long lived transfers 5. VM/Container proxy propagation docker pull , docker build Host curl , running containers VM runtime (OrbStack/Docker Desktop) auto injects or caches proxy config; removing proxy makes it worse (VM traffic via TUN → TLS timeout) Diagnostic Workflow Step 1: Identify the Symptom Name the actual consuming command or service and its required protocols before choosing a repair. When an authorized directly reachable peer can reach the slow target, and authenticated SSH plus a representative transfer through that peer already work better, treat the gateway as an integration candidate. First inspect the consumer's effective SSH/inventory arguments and test whether its normal entry can use that path; a successful handwritten ssh J does not prove integration. If the gateway carries the required workload, connect it through the existing managed transport and replay the original operation without per request jump arguments. Verify target identity, host key trust, content integrity, elapsed time and the declared failure/recovery behavior. Do this before further work on a native relay that is not required by the user's result. Keep exact hosts, ports and live route state in the owning infrastructure configuration. Keep unmet protocols explicit: an SSH gateway does not establish UDP or arbitrary application connectivity. Continue the relevant network repair when those are required; do not replace that acceptance with SSH success. The executing agent must judge protocol fit and user intent; no bundled checker establishes either. Determine which scenario applies: Public gateway returns 502 while its backend responds normally, or the dedicated SSH tunnel key is rejected as correct key but not from a permitted host → read [SSH tunnel source restrictions](references/ssh tunnel source restriction.md) before changing TUN/firewall rules or treating a running process as recovery. Browser returns HTTP 503, but curl and SSH both work → System proxy bypass conflict (Step 2C) local.<domain fails in browser/default curl , but direct/no proxy request works → Local vanity domain proxy interception (Step 2C 1) Tailscale ping works, SSH works, but curl/HTTP times out → HTTP proxy env var conflict (Step 2A) Tailscale ping works, SSH/TCP times out → Check route ownership (Step 2B); a matching symptom alone does not prove a route conflict. Tailscale IPv4 SSH times out, or an alternate peer address triggers host key verification → Read [same peer IPv4/IPv6 SSH recovery](references/tailscale address family ssh.md) before declaring the host offline or changing trust/network state. Remote dev server auth redirects to localhost → browser can't follow → SSH tunnel needed (Step 2D) make status / scripts curl to localhost fail with proxy → localhost proxy interception (Step 2E) git push/pull fails with FATAL: failed to begin relaying via HTTP → SSH double tunnel (Step 2F) docker build RUN apk/apt fails with Connection refused instantly → OrbStack transparent proxy + TUN conflict (Step 2G 1, fix: network host ) docker pull fails with TLS handshake timeout → VM proxy misconfiguration (Step 2G 2, fix: docker.json with host.internal ) Container healthcheck (unhealthy) but app runs fine → Lowercase proxy env var leak (Step 2G 4, fix: clear http proxy + HTTP PROXY ) docker build can't fetch base images → VM/container proxy propagation (Step 2G) docker pull works but docker build still dies fetching base image tokens (TLS/i o timeout) on a WSL2 + Docker Desktop host, or every runbook proxy port on a Windows+v2rayN host silently died after a v2rayN upgrade → buildkit direct dials past the DD proxy override / v2rayN ≥7.17 LAN port split (references/windows host tun wsl cascade.md §2026 08 23) git clone fails with Connection closed by 198.18.x.x → two different mechanisms produce this; Step 2H separates them Every domestic/DIRECT rule site fails at once (TLS unexpected EOF mid handshake, proxy port CONNECT returns 503, Node CLIs report UNKNOWN CERTIFICATE VERIFICATION ERROR ) while proxied overseas sites keep working → TUN DIRECT split brain (Step 2J) One hostname fails after a DNS/delegation move while other sites work → read [single host DNS migration](references/single host dns migration.md) before diagnosing a broken forwarding plane or restarting a shared tunnel. Everything on macOS fails at once — domestic AND overseas, and even Step 2J's real IP probe fails → do not declare an outage yet; run the physical interface discriminator in "TUN full stall vs genuine outage" (above Step 2A) SSH connects but operation not permitted → Tailscale SSH config issue (Step 4) SSH connects but be child ssh exits code 1 → WSL snap sandbox issue (Step 5) TCP port 22 reachable ( nc z succeeds) but SSH fails with kex exchange identification: Connection closed → Tailscale SSH proxy intercept on WSL (Step 5A) Same kex exchange identification / Connection closed by UNKNOWN port 65535 , but there is no Tailscale — you are behind a TUN and the host is a public cloud VM whose HTTPS still works → could be your TUN or the destination's firewall allowlist; they are indistinguishable from the client. Step 2K settles it out of band Every node of one proxy provider stalls identically (TCP connects, TLS appconnect=0.000 ), and restarting the local proxy app + packet tunnel extension changes nothing → provider level event, not your tunnel; confirm with the provider's control plane and switch to a different provider's node (Step 2L) Every tested node of one proxy provider fails fast with TLS unrecognized name , while subscription refresh still works → check the provider's account dashboard for traffic limits, billing or suspension first; the symptom does not establish the cause. Read [provider account checks](references/provider account suspension.md) before rebuilding tunnels or restarting proxy processes. tailscale ssh returns "not available on App Store builds" → Wrong Tailscale distribution on macOS (Step 5B) Any tool using system DNS ( ssh , curl , git ) hangs ~60s before resolving, but nslookup returns instantly → Stalled resolver in getaddrinfo chain (Step 2I) Windows+WSL host: everything offline at once (domestic AND overseas), WSL dead too, "even Tailscale won't come online" — and/or the user tried several recovery actions (switched NICs, toggled TUN) and can't tell which one fixed it → Windows host TUN cascade + event log forensics (Step 5C) Tailscale, DNS, route ownership, and proxy reachability all pass, but large transfers remain slow and switching the active proxy node changes throughput → proxy node / exit / chain capacity workflow in [references/proxy node chain throughput.md](references/proxy node chain throughput.md) You want one app's bulk traffic (BaiduNetdisk downloads, Feishu/Lark uploads) off the metered proxy — or out of the status bar counter entirely → not a conflict; a splitting task. Use the existing local traffic splitting workflow if one is installed and verified; this skill does not bundle a splitter, target list, replay helper or subscription wipe guard. For mechanism diagnosis, read [references/proxy conflict reference.md](references/proxy conflict reference.md) § "Per app traffic splitting on macOS" — which of the three mechanisms (DIRECT rule / tun excluded routes / always real ip ) can work for the app depends on whether it connects to literal IPs or the system resolver, and the DNS layer keys are inert on the macOS Catalyst build. Key distinctions : SSH does NOT use http proxy / NO PROXY env vars. If SSH works but HTTP doesn't → Layer 2. curl uses http proxy env var, NOT the system proxy. Browser uses system proxy (set by VPN). If curl works but browser doesn't → Layer 3. If tailscale ping works but regular ping doesn't → Layer 1 (route table corrupted). If ssh T git@github.com works but git push fails intermittently → Layer 4 (double tunnel). If h