Back to list
Skywire Development Update — October 10, 2026

Skywire Development Update — October 10, 2026

The tenth of October was the busiest day of the month so far, with forty-seven changes merged. The leaking KCP sessions on the address resolver were finally traced, with a heap dump, to two separate causes, one in the resolver and one in the KCP library itself, and both were fixed. A whole private deployment can now be run inside a single visor, from the dmsg server through the route finder, with other visors and even browser visors able to join it, and a guide explains how. Every remaining terminal interface moved off bubbletea and tview onto one toolkit that also runs inside the browser desk, and the desk gained a real terminal and a single shared filesystem. Gin and chi left the codebase in favor of the standard library’s router, the decommissioned uptime tracker was removed outright, the generated memory limit that had pinned small hosts in garbage collection was dropped, and the browser module stopped being embedded in the native binary at all.

Skywire: The Leaking KCP Sessions, Found Twice

5550 added the tool that made the rest possible: an authenticated heap dump route on the visor’s log server, which runs a garbage collection and writes a full heap dump to the visor’s log directory, along with an option on the resolver’s session endpoint to list leaked sessions by address so the dump can be searched for whatever path keeps them alive. The process stops while the dump is written. 5577 gave the CLI a matching log heapdump command, which triggers that route over dmsg and prints where the dump was written without ever streaming it, along with debug and garbage-collection options for log pprof. Both are limited to keys on the visor’s survey whitelist; a live fetch from the local visor was refused because the CLI’s own key was not on it, so the new options have not yet been checked against a real response.

5551 was the first cause the dump revealed. A UDP lookup asks the target visor to dial the requester by writing to the target’s session, and that write had no deadline, so when a target stopped acknowledging, its send window filled and every write to it blocked forever, each pinning the requester’s own session and goroutine. One silent peer had 106 goroutines waiting on it. Every resolver write to a SUDPH session now has a ten-second deadline, which also ends the silent peer’s own read loop so its session is closed.

5552 was the second. KCP’s timer shards could miss arming a timer when their next task came due in the narrow window between taking it off the queue and arming the timer, as happens after a garbage collection pause longer than five milliseconds, and the shard would then sleep until the process exited. On one deployment host this froze half the resolver’s KCP sessions, which stopped updating, and closed ones were never freed, nearly six thousand of them after two hours. The fix in the forked KCP library arms the timer whenever a task is pending, and its test injects exactly that delay.

Skywire: A Whole Deployment in One Visor

5560 made skywire autoconfig --deployment set up a complete private deployment inside a visor: a dmsg server, dmsg discovery, the setup and transport setup nodes, transport and service discovery, the route finder and the address resolver, each under its own key, with the visor pointed at them instead of the public deployment. Stores live in memory unless a redis address is given. A new config deployment command prints the services configuration other visors use to join, and a SKYDEPLOY setting makes that file replace the public one entirely, so nothing falls back. It was tested with a deployment visor and a second visor joining it, with every health check passing, transports registered and a route returned, both in memory and on redis. Several bugs turned up on the way and were fixed, including a dmsg server block that failed to start with a port-only address, setup nodes that always held sessions on the public dmsg servers, and service discovery on redis panicking the host visor without an entry timeout.

5564 then ran the discovery services, the route finder and the address resolver under the visor’s own key, behind path prefixes, while the dmsg server, dmsg discovery and the setup nodes kept their own keys; a visor hosting its own transport discovery also stops waiting out a thirty-second boot attempt on every start. 5591 went further and folded the dmsg server itself into the visor on its own key, which had deadlocked until now because the client skipped its own server, that server was the only one, and the server waited on the client before serving. The client now connects to its own server when it is the only one configured, and hosts that know other servers keep the old behavior. Three follow-ups, 5592, 5593 and 5594, fixed a self-only visor then losing its own server: its first dial raced the shared port opening, discovery could not list the server until a session existed through it, and the visor’s own services on the same key kept replacing the server’s cache entry with a client-only one. The server entry is now seeded with both roles and kept, with new tests reproducing each case.

5570 kept the public browse origin in a deployment’s exported configuration, since it only serves the service worker bootstrap and does not depend on the deployment. 5582 let browser visors join a custom deployment too, by honoring SKYDEPLOY in the browser build and exporting the dmsg server’s secure WebSocket address, which is the only way a browser can reach one; a desk tab was verified connecting only to a test deployment’s dmsg server, registering in its discovery and getting a route from its route finder. 5574 added a guide to running a custom deployment, covering joining visors, DNS and TLS for a dmsg server’s WebSocket name, and serving a browse origin, while noting which steps were checked with a fresh run and which follow the existing setup.

5583 gave a deployment host one status page, rendering every embedded service’s charts as one document with a summary table on top, served on the visor’s dmsg HTTP port and on a new loopback address that a reverse proxy can expose. 5584 kept that address across a config regeneration, and 5588 found it had never been read from the config file at all, because the JSON mirror the config decodes through lacked the field; a round-trip test now checks it.

Skywire: New Names for the dmsg Servers and the Browse Origin

5585 moved the public dmsg servers’ WebSocket names to skywire.dev, whose DNS can be managed by script, listing the old theskywirenetwork.net names as aliases that servers still answer on and hold certificates for, so browser modules built with the old names keep working. 5586 then moved the public browse origin from haltingstate.net to theskywirenetwork.net, now that the browser visor itself lives at skywire.dev.

Skywire: Every Terminal Interface on One Toolkit

The live views behind every --live flag ran on bubbletea, which does not build for the browser, so none of them would start in the desk. 5566 moved them onto progkit on tcell v3, with one implementation for both, and gave a terminal program run as skywire in the desk a real terminal with keys and resizes. 5567 moved the interactive --tui console over, keeping its code rain and replacing lipgloss’s text wrapping with a small wrapper that carries colors across a break. 5568 did the same for the dmsg chat and both skychat interfaces, whose inputs become real HTML inputs in the desk. 5569 moved visor ping tree and mux-bw-tui, the last bubbletea users, and fixed ping tree having been unreachable because the help installer replaced any child command named tree with its own. 5571 finished with the hypervisor TUI, the util edit editor, the autoconfig form and the voice spectrogram, the last tcell v2 users, removing tview, femto and tcell v2; choice dialogs in the hypervisor TUI now open on No or Cancel so a stray Enter no longer confirms a shutdown or a delete. Together these removed bubbletea, bubbles, lipgloss, tview and femto from the module.

Skywire: A Real Terminal and One Filesystem in the Desk

5562 rebuilt the desk’s terminal on websh’s own session, which had grown into a full terminal host since the desk first wrapped websh’s shell engine in August; programs run from the desk’s filesystem now get a terminal of their own, along with placements, inline images and the kitty keyboard protocol. 5559 brought websh, bottle and xterm-go up to their current versions, which that depends on.

In the desk, skywire commands run in a separate exec worker with its own filesystem, so they could not see the shell’s files. 5572 first shared the shell’s home directory with the worker, and 5573 replaced that with a single filesystem owned by the worker and mounted at the root of the page, so a file written anywhere by either side is visible to the other and the shell’s files survive a reload. 5575 bumped bottle again so the shell can run programs that live on that mount.

Skywire: Standard Library Routing

5554 moved the rewards server, the last user of gin, onto the standard library’s router, removing gin and the modules only it used and taking about 8 MB off the stripped binary. Because the standard router unescapes path values, the two handlers that build file paths from a key now require a valid public key, redirect messages are escaped and the session cookie is set to SameSite=Lax. 5556 moved the deployment services, dmsg discovery and server, and the test helpers off chi onto a small router wrapper that keeps chi’s grouping and middleware shape, and fixed a compression middleware that held back the response to a WebSocket upgrade. 5557 moved the hypervisor, the last chi user, and dropped chi along with its CORS and rate-limit packages; all 184 hypervisor routes were requested on both builds with matching status codes apart from two that now redirect. 5555 dropped pterm from the CLI in favor of the plain color and tree drawing it already had for the browser build.

Skywire: Retiring the Uptime Tracker

5558 removed the standalone uptime tracker, which had already been decommissioned now that visors heartbeat to transport discovery and the discovery services serve uptime themselves. Its command, config block, deployment entries and documentation all went, along with Postgres and every module that only it used. The network monitor and the visor’s network view now read online visors from transport discovery, and an old config with an uptime tracker block still loads with the block ignored. 5561 removed a type conversion that change had made redundant and that failed the Windows lint lane.

Skywire: Memory Limits That Stop Fighting the Garbage Collector

5576 found that a visor with an automatic memory limit on a 1 GB host got a limit of about 360 MiB, sixty percent of the memory free at startup, below its normal live heap of around 400 MB, so the garbage collector ran back to back and pinned the core; one visor ran it 285,861 times in under fourteen hours. The automatic limit is now ninety percent of total memory or the cgroup limit, whichever is lower. 5578 stopped generating that automatic limit in new configs altogether, and 5580 made config generation write an explicit “none” rather than leaving the field out, so anyone reading a config can still see the setting exists.

Skywire: The Browser Module Leaves the Binary

5579 made every package carry the browser module beside the binary, as a compressed file with a manifest giving its version, revision, checksum and size, uploaded on every merge and with every release. 5587 then stopped embedding it in the native binary, which drops about 36 MB and removes the two-stage build. A visor serves the copy beside its binary and refreshes it only when someone opens its desk and its copy is older than the source named in the deployment config, fetching over dmsg and checking the size and checksum before swapping it in.

Skywire: TinyGo Builds the Whole Tree

5595 ported the work that lets TinyGo build the entire skywire root module for the browser, including the HTTP stack, the dmsg tools and servers and the transports, with a JSON package that falls back to the standard library where TinyGo cannot run the faster one; with the forked TinyGo toolchain the module now builds and runs commands in the desk. 5596 made RPC servers register without reflection under TinyGo, where Go’s own RPC registration panics, using generated call tables with a test that fails if they go stale, and moved the visor RPC client and pty host onto the same RPC package the router already uses. 5589 and 5590 fixed two smaller TinyGo build stops, in the dmsg pty CLI and the skychat history store.

Skywire: Smaller Fixes

5563 brought in a yamux change that writes already-queued frames together instead of making two system calls per frame, which had been about a fifth of one deployment host’s CPU; a parallel 4 KB send benchmark went from 32 to 18 microseconds per operation. 5565 gzipped the charts page once per render instead of on every request, which had been about four percent of that host’s CPU in one profile. 5581 replicated two dependency bumps from the automated updater against the current branch, and 5597 dropped stale bottle checksums from go.sum that had failed the module integrity check on every commit since the bottle bumps.