Skip to content

FAQ

Common questions about binpatch.

Yes. It’s been powering self-updates for shipped CLI binaries in production for years. Same reliability you’d build into your own tool — minus the years of accumulated fixes.

TRDIFF10 — the bsdiff format with TRDIFF10 magic, three zstd-compressed blocks (control / diff / extra), and SHA-256 integrity verification. The wire format is documented on the Wire Contract page.

Yes, as long as it produces TRDIFF10. The apply functions (applyPatchChainInMemory, applyPatchToMemory) accept raw TRDIFF10 bytes — they don’t care who produced them.

Common off-the-shelf generators:

Yes. Each platform’s patch is a separate OCI manifest layer with its own org.opencontainers.image.title annotation (<binaryName>-<platform>.patch). Consumers pick the layer matching their platform.

Can I use it offline / without a registry?

Section titled “Can I use it offline / without a registry?”

Yes. Call applyPatchChainInMemory directly with patch bytes you already have on disk. Discovery is optional.

What’s the overhead vs. shipping full binaries?

Section titled “What’s the overhead vs. shipping full binaries?”

For a typical CLI update (small code change), the patch is ~0.07% of the binary size. For a 100 MB binary, that’s ~75 KB. Bandwidth savings at scale: 99.93%.

Why not Courgette-style executable-aware diffing?

Section titled “Why not Courgette-style executable-aware diffing?”

See the Architecture → page. Short answer: for our use case (Node/Bun-based CLIs where the bulk of the binary is a JS snapshot), bsdiff already crushes pointer churn via its seek mechanism, and the rest of the binary is too small for executable-aware diffing to matter.

See Architecture →. Briefly: mmap-based readers break esbuild + Node SEA bundling, and in-memory apply is fast enough for typical binaries.

See CI integration → for a complete example, or use the bundled GitHub Action → for the end-to-end generate-and-publish flow.

binpatch. Install via:

Terminal window
npm install binpatch

What’s the difference between applyPatch, applyPatchToMemory, and applyPatchChainInMemory?

Section titled “What’s the difference between applyPatch, applyPatchToMemory, and applyPatchChainInMemory?”
  • applyPatch — single patch, file-based. Reads oldPath, writes destPath. Returns SHA-256.
  • applyPatchToMemory — single patch, in-memory. Takes a Uint8Array old binary, returns the new Uint8Array.
  • applyPatchChainInMemory — chain of patches, file-based for old/dest (reads oldPath, writes destPath). Intermediate hops stay in RAM; only the final hop writes to destPath. Returns SHA-256.

applyPatchChainInMemory is what resolveAndApply uses internally and is the right entry point for most consumers.

Why are there both ghcrSource and githubReleaseSource?

Section titled “Why are there both ghcrSource and githubReleaseSource?”

Different update channels with different storage:

  • ghcrSource — nightly / canary. OCI registry, content-addressable.
  • githubReleaseSource — stable. GitHub Releases, asset downloads.

The wire format is shared; only discovery differs.

The library is ESM and pure Node.js; it runs in Bun. We’ve not done extensive testing on Bun’s networking quirks. The customFetch escape hatch lets you inject Bun-native fetch if needed.

Can I use my own registry (not GHCR, not GitHub Releases)?

Section titled “Can I use my own registry (not GHCR, not GitHub Releases)?”

Yes. Implement SourceStrategy directly — see Discovering chains →. A 30-line custom source is enough for most bespoke registries.

Craft-driven. See .craft.yml and the release workflow at .github/workflows/release.yml. A maintainer runs gh workflow run release.yml -f version=<x.y.z>, Craft opens a release-request issue, a second maintainer labels it accepted, and publish.yml (with OIDC trusted publishing) ships the npm tarball.

Most likely: your registry’s redirects are stalling. Check your fetch implementation — every HTTP call inside the library is wrapped in a 10-second request timeout (30 seconds for blob downloads). If your fetch doesn’t honor AbortSignal.timeout(...), the library’s timeout wrapper won’t fire.

See Custom fetch / CA →.