All posts
Research14 min read

Why Wi-Fi always looks fine after the outage — and what a flight recorder changes

Every diagnostic you run after a wireless failure runs on a network that has already recovered. That single fact explains most of the frustration in wireless support — and it is a recording problem, not an analysis problem.

The recovery problem

A user says the call dropped. By the time you reach the machine, the client is associated at −54 dBm, DNS resolves in 12 ms, and a speed test saturates the link. You have a healthy network and an unhappy user, and nothing in front of you connects the two. So the ticket gets the only honest label available: not reproducible.

Wireless networks are unusual in how completely they erase their own failures. A crashed process leaves a core dump. A failed HTTP request leaves a log line with a status code. A five-second roam failure at the edge of a cell leaves an association that succeeded on the fourth attempt and a client that looks, from every interface you can query, exactly like a client that never had a problem at all.

The information was there. It existed for a few seconds, in kernel counters, in driver scan tables, in supplicant state machines, in DHCP transaction IDs. Then the counters were overwritten, the scan table was refreshed, the state machine advanced, and the lease renewed. Post-hoc tooling arrives at a scene that has been cleaned.

What disappears, and how fast

The erasure is not uniform. Different layers forget at very different speeds, which is why partial evidence is so often misleading — the surviving layer becomes the explanation simply because it is the only one left.

SignalTypical lifetimeWhy it goes
Instantaneous RSSI / noise1–2 secondsSampled value, never stored
Scan candidate listseconds to ~1 minuteOverwritten by the next scan
Deauth / disassoc reason codeuntil next eventNot logged by most OS stacks
Retry and PHY-rate counterscumulative, no timelineMonotonic totals hide bursts
DHCP transaction detailuntil lease renewalNo client-side history kept
Per-hop latency and lossnot recorded at allNothing samples it continuously
Roam decision inputsnever exposedLives inside driver firmware

Notice the pattern: the fastest-decaying evidence is the evidence that distinguishes causes, while the slowest-decaying evidence — cumulative counters, current association state — is the kind that tells you almost nothing about a specific minute. That is the worst possible arrangement for anyone diagnosing after the fact.

Five causes that all look identical afterwards

Consider a client that lost usable service for roughly forty seconds and then recovered. Here are five genuinely different root causes. After recovery, all five present the same way: an associated client with good signal and a user who says it broke.

Roam failure at the cell edge

The client held on to a distant AP well past usefulness, then failed association to the better one several times before succeeding. Only the roam attempt timeline separates this from plain coverage loss.

DHCP lease change

The lease expired and the renewal returned a different address. The link never dropped; the sockets did. Radio evidence looks perfect throughout.

Band-steering flap

The controller pushed the client between 5 GHz and 6 GHz repeatedly. Each individual transition is legal and fast; the aggregate is unusable.

Upstream path loss

Association was flawless and the gateway responded, but everything beyond it black-holed. Any client-only check that stops at the gateway reports success.

Power state transition

The interface entered aggressive power save, or the host briefly slept. Frames were buffered, retried, and eventually dropped — with no error surfaced anywhere.

The remedies are not interchangeable. Lowering a roam threshold does nothing for a lease collision; extending lease time does nothing for band steering. Guessing costs a change window and rarely closes the ticket, because the next occurrence looks the same as the last one.

What a recorder has to keep

Aviation solved a version of this problem by deciding in advance which parameters matter and writing them continuously to a bounded medium. The insight is not the storage — it is the commitment to record before you know which flight will matter.

For a wireless client, the minimum set that separates the five causes above is smaller than people expect. It is not full packet capture. It is five layers, sampled on one clock:

Radio

RSSI, noise, channel, band, BSSID, PHY rate and retry deltas — sampled at ~1 Hz, plus every scan result set.

Association

Auth, assoc and 4-way handshake events with timing, and deauth/disassoc reason codes verbatim.

Addressing

Full DHCP transaction detail, lease boundaries, IPv6 RA changes and DNS resolution latency.

Path

Gateway and off-net reachability with RTT and loss, so 'the link is up' and 'the path works' stay separate claims.

Host

Interface transitions, sleep and wake, VPN state, power source — the boring rows that resolve half of all mysteries.

One clock

A monotonic timestamp per row. Without it, cross-layer correlation is guesswork dressed up as analysis.

The cost of always recording

The objection to continuous recording is always cost, and it is a fair one — a tool that burns battery or fills a disk gets uninstalled before the incident it was meant to catch. Which means the design target is not "as much data as possible" but "little enough that nobody turns it off".

Structured rows are what make that possible. A second of typical activity is on the order of a few hundred bytes once it is typed fields rather than frames, so days of history fit in a few hundred megabytes with a fixed ceiling. A ring buffer means the ceiling is absolute: the recorder never grows, and old rows are overwritten rather than accumulating.

bounded by construction
$ radiochron record --retain 72h --size 512MiB
[i] ring buffer 512 MiB · overwrite oldest · retention 72h
[i] sampling radio 1Hz · events on change · path probe 5s
[i] steady state: 0.4% CPU · 31 MiB RSS · 0 network egress

From rows to a verdict

Rows alone are not an answer. A timeline of forty thousand events is a different kind of unhelpful than an empty ticket. The step that matters is correlation: taking a window around the reported minute and asking which causal story is consistent with all five layers at once, rather than with the one layer you happened to look at.

A useful verdict has three properties. It names one cause. It carries a confidence value that can be low. And it lists the specific rows behind the claim, so the reader can disagree with the reasoning rather than with the tool.

The third property is the one that decides whether anyone trusts the output. A confident answer with no evidence is indistinguishable from a guess, and in an escalation it will be treated as one — correctly. "Roam failure, 0.91, here are the four rows" survives a vendor call. "Wi-Fi issue detected" does not.

Ruling out matters as much as ruling in. Half the value in an escalation is being able to say that DHCP, DNS and the upstream path were all healthy across the exact window, with the samples to show it. That is how a ticket moves from your queue to the right one.

What this looks like in practice

The workflow inverts. Instead of reproducing a failure, you go looking for one that was already captured. The user reports a time; you open that time; the evidence is there because it was recorded before anyone knew it would matter.

after the fact
$ radiochron verdict --at "11:53"
VERDICT roam_failure (confidence 0.91)
11:52:58 RSSI -84 dBm sustained 6s · scan set empty
11:53:04 assoc → Office_AP_6E timeout ×3 (status 16)
11:53:12 gateway unreachable · 41s without default route
11:53:53 reassociated Office_AP_6E · service restored
[i] ruled out: dhcp_exhaustion, dns, upstream_loss, host_sleep
$ radiochron bundle --at "11:53" --redact ssid,bssid,mac
[i] wrote incident-2026-09-08-1153.rcz (2.1 MiB)

The change in the conversation is larger than the change in the tooling. "It happened again this morning" stops being the end of the investigation and becomes the start of one, because a timestamp is now enough to retrieve the evidence.

Limits worth stating

A client-side recorder sees what the client sees. It cannot read your controller's configuration, watch other clients contend for airtime, or observe interference that never reaches your radio. It infers the network's behaviour from one vantage point, and honest tooling should say so rather than implying omniscience.

It is also not a substitute for a sniffer when you genuinely need frames. The realistic division of labour is that the recorder runs all the time and tells you which minute and which channel deserve a capture — which is exactly the question a sniffer cannot answer for you, because it was not running either.

And a verdict remains a hypothesis. Confidence values exist because the evidence is sometimes ambiguous, and a tool that never reports uncertainty is not more accurate, only less useful. The goal is not to remove the engineer from the loop. It is to make sure that when the engineer arrives, the scene has not already been cleaned.

RadioChron is this idea, shipped

A local-first flight recorder for Wi-Fi: bounded continuous chronicle, a verdict engine that cites its evidence, and a desktop app to scrub the timeline. MIT / Apache-2.0.