Binary delta-update engine from BYK. Publish a bsdiff+zstd patch next to each release and a self-updating binary walks the chain from the installed version to the target instead of downloading itself again. Zero runtime dependencies, MIT.
Every self-updating binary has the same problem: a release changes a few hundred kilobytes and the user pulls the whole file again. binpatch fixes it in the two halves projects usually hand-roll separately and get wrong. A composite GitHub Action generates a TRDIFF10 (bsdiff + zstd) delta between consecutive builds and publishes it next to the full binary; the npm package is the runtime half that discovers which patches a given install needs, applies them in order, and verifies the result against the target's sha256. Pure Node, node:* builtins only, no runtime dependencies. Two source adapters ship with it: GitHub Release assets and OCI/GHCR patch-manifest tags. First published in July 2026 by BYK, who also has it powering self-updates in getsentry/cli.
Numbers from peektrace's cli-v0.6.0 release, which ships a .patch next to every platform binary:
| linux-x64 asset | size |
|---|---|
| full binary | 115.8 MB |
| gzipped download | 40.9 MB |
.patch | 314 KB |
The delta is 0.8% of what the compressed download costs, about 130x fewer bytes on the wire. The other platforms land in the same band: 313–387 KB patches against 74–116 MB binaries.
peektrace calls resolveAndApply with the GitHub Releases source from apps/cli/src/upgrade/patch.ts. Three constraints shape that file, and they are the part worth copying.
The delta path is best-effort by construction. No chain, a network failure, a corrupt patch, or a release that bumped Bun's own runtime bytes and so blew the size gate and shipped no patch: every one of those resolves to null and the caller falls back to the full download. The full download stays the only path that has to work, which is what makes the delta safe to attempt at all.
It is only attempted for a forward upgrade of a compiled binary. Patches are generated old to new, so a downgrade has no chain to walk. And under bun run the running executable is Bun itself rather than anything a chain can be applied to, so the guard is whether Bun.main starts with /$bunfs/, the marker Bun stamps into a bun build --compile binary.
The releases listing has to be filtered before binpatch sees it. The library builds its chain from the slice of releases between the installed and target tags, and treats any release in that slice with no .patch asset as a broken publish. peektrace ships desktop-v* tags from the same namespace, so without a filter every CLI upgrade spanning a desktop release looks malformed. The fix is a wrapped fetch handed to githubReleaseSource that keeps only cli-v* entries and widens the page size to 100, because binpatch sizes its window in releases rather than in CLI releases. Interleaved desktop tags would otherwise push the installed version out of view and silently turn delta upgrades off.
Everything product-specific is injected: the version comparator, the fetch, the cache directory, the progress renderer. The library never draws progress itself. It emits phase / bytes / done events and leaves the stderr bar or the log line to the consumer, and a handler that throws can't abort the upgrade.