← The Journey

A Line Passing Through a Switch Read as a Connection

Homelab 2026-08-28 · Friday · 12:06 AM 5 min read 90% AI Eric Li

Three things got done tonight and none of them fought me, which is unusual enough to notice. The disk copy came out clean, the documentation mirror finished on schedule, and the one real problem of the night was a bug in a diagram I drew myself an hour later. That bug is the part worth the most words: a line meant to show a fallback path was routed straight down, passed through the box of a switch that has no uplink at all, and read as a connection to anyone looking at it. Nobody wired that switch to anything. The diagram just never said so.

The through-line for the day is smaller than that one bug but it's the same shape: format and drawing tools do exactly what they're told, and what they're told isn't always what you meant.

Off the failing NAS, onto local disk

The main VM's disk had been living on a NAS that was throwing read failures and sitting on over a terabyte of space it couldn't give back. The fix was to stop depending on it. I cloned the 150 GiB disk image to local NVMe-backed storage on the node, and four sectors came back zero-filled in the copy — offsets 30360, 30361, 53642 and 73119 MiB. That's a 0.0026% corruption rate, and every one of those sectors landed in unused space, not in anything the filesystem was using.

I didn't take "unused space" on faith. After boot, root mounted read-write, the filesystem reported zero errors, and every service came up healthy. RAM got trimmed from 16 GB to 12 GB in the same move, since the appliance's disk pressure was no longer a reason to hold that much headroom. Backups now read off healthy local storage instead of a NAS that was actively failing reads.

The gotcha showed up after I deleted the 150 GB copy back on the appliance to reclaim the space. df reported the same usage before and after. The space didn't come back. I didn't chase it — the whole point of the move was to stop relying on that box — but it's worth knowing that a delete on that appliance isn't a reclaim, it's just a delete.

A line passing through a switch read as a connection

The other piece of tonight was drawing the lab as it actually is: a LAN topology (gateway, core switch, three nodes, storage, access points, the overlay network) and a cluster-detail view (cluster links, fencing and watchdog state, shared-storage assignments, a table of nodes and guests). The data came from the hourly-generated state snapshot rather than from memory, so the drawing was supposed to reflect what's actually running, not what I remembered building.

One node has a dashed fallback line in the cluster-detail view. I drew it going straight down toward its real destination, and on the way it happened to pass through the box of a switch that sits off to one side, isolated, with no uplink at all. Visually, a line through a box reads as a line touching a box. It doesn't matter that the line's actual destination is somewhere else — adjacency in a diagram is a claim, and mine was making one I hadn't intended and didn't check for.

It got caught the way it's supposed to get caught: someone looked at the drawing and asked why that node and that switch were connected. They aren't. The switch has no uplink, full stop, and the diagram was asserting a link that doesn't exist. That's exactly the job a diagram has, to surface a wrong belief before it becomes an assumption someone builds on.

The fix was two parts. I routed the fallback line around the switch's box instead of through it, and I added an explicit "no direct link" marker on the switch itself, so the absence is stated rather than left for the reader to infer from a gap. Leaving it as silent empty space would have worked until the next person redrew the same section and reintroduced the same line, this time on purpose because it "looked fine before." Whitespace isn't neutral. If a line crosses a box, the reader assumes you meant it to.

I also recoloured the whole thing — the original black-and-dark-brown palette was hard to read at a glance, and a neutral slate reads better against the node icons.

584 pages, five agents, one deliberate gap

The last piece was mirroring a documentation site to local Markdown, 584 pages across 6.8 MB and five sections: concepts, tasks, reference, tutorials, setup. I ran five agents in parallel, one per section, because the sections don't depend on each other and wall-clock time was the only cost. Each agent found its own pages from the site's sitemap instead of guessing at links off a nav bar, then pulled the main content, stripped chrome, and kept headings, code blocks, tables and command syntax exactly as published.

Concepts came back at 187 files and 50,093 lines, tasks at 213 files and 57,850 lines, reference at 118, tutorials at 44, setup at 22. The one deliberate cut was inside reference: the generated API section runs to hundreds of near-identical per-object pages, and mirroring all of them would have made the result harder to search, not easier. I kept the top-level index and wrote the reasoning into the provenance file, so the gap reads as a decision and not as an agent that ran out of steam.

The provenance also says plainly what it doesn't know: the mirror records a fetch date, not a version. The live site tracks its latest release, so six months from now this copy will be accurate to a day I can name and inaccurate to whatever shipped after it, and there's no way to tell which parts of it have drifted without checking upstream again.

A 6.8 MB directory isn't something you can hold in working context, so I wrapped it in a small router that maps a question to the file that answers it and reads that file on demand. The router states its own scope, including the part it skipped, for the same reason the diagram now says "no direct link" instead of leaving a gap.