Sigil 0.17.13

release

A short follow-up to 0.17.12. sigil app install installs again, now building the app as a native binary the way a release build does, and the browser WebSocket layer can tell a still-connecting socket from a closed one. Under the hood the async runtime is more robust under concurrency: a busy goroutine no longer starves I/O, and channel wakeups no longer deadlock a long-running program. Existing projects build without changes.

Download 0.17.13Get started

sigil app install builds and installs a native binary

sigil app install, and sigil app update, had stopped installing anything. The command compiled the app and then exited without writing a binary, registering it, or reporting an error, leaving you on the previous version with no sign anything went wrong. It now builds the app exactly as its release build does, with build --config release, and installs that native binary. A failed install is loud: a real error and a non-zero exit, never a silent success.

Installing from a workspace with more than one package is now explicit. --package selects the package to install; without it, the command uses the workspace's declared default app, or the sole package that has an entry point, and otherwise lists the candidates and asks you to choose rather than guessing. The standalone sigil bundle command, whose bundling path this release stops relying on for app installs, now also fails loud instead of exiting silently.

A connecting WebSocket is no longer mistaken for a closed one

sigil-wasm-net exposes a readyState accessor for a browser WebSocket, so code can tell a socket that is still opening from one that has closed. A browser WebSocket opens asynchronously, and until now a poll during that opening window looked the same as a closed connection, so a client could give up on its very first poll and never recover, a failure that surfaced only over real network latency and never on localhost. With the distinction available, sigil-websocket can report "no data yet" instead of "closed" while a socket is still connecting.

Async I/O no longer starves under a busy goroutine

When one goroutine stayed busy, the async scheduler could starve everything else: pending socket reads, pipe drains, and subprocess waits would stall. Under load that meant an async server could stop answering, or a build could hang waiting on a subprocess that had already finished. The scheduler now keeps I/O moving even when a goroutine is hot, so servers stay responsive and builds don't stall on work that's already done.

Channels no longer deadlock when waking a blocked peer

Channels could deadlock a program. When a send, a receive, or a channel close woke a peer that had been waiting, that peer could get stranded and never run again, freezing the whole program at zero CPU with no error to point at. It was rare in short runs, but a long-running program moving data across channels could hit it under load. Those wakeups are now reliable. Two smaller channel fixes ride along: a value you send is never confused with an internal scheduler marker, and a buffered channel delivers messages in the order they were sent.

A musl build finds its toolchain, or stops

sigil-build now detects a musl cross-toolchain by scanning PATH directly instead of caching a first look that could go stale inside a nested shell, so a toolchain: musl build reliably finds musl-gcc when it is present. When it falls back to guix to supply the toolchain, it does so in a clean, isolated environment, so the musl objects come out the same regardless of the surrounding shell. And when no musl compiler is found at all, the build fails with a clear error instead of quietly compiling against the host's glibc and producing a binary that only looks static. That stricter behavior is deliberate: a musl build that cannot find a musl toolchain should stop, not silently fall back.