sdk-v0.1.3 failed at the publish step: bun2nix: command not found, exit 127. Nothing reached the registry, so 0.1.3 is still free — @punktfunk/host is still at 0.1.2.
What happened
bun publish runs the prepare lifecycle script, and sdk's prepare is bun2nix -o bun.nix — regenerating the nix dependency file. bun2nix is a repo-maintenance tool, not something the oven/bun:1 publish container has; the workflow's own install is --ignore-scripts, so nothing put it on PATH either.
bun publish v1.3.14
$ bun run build
$ tsc -p tsconfig.build.json
$ bun2nix -o bun.nix
/usr/bin/bash: line 1: bun2nix: command not found
error: script "prepare" exited with code 127
This was latent, not new
prepare gained the bun2nix call on 2026-07-27 (1db8f763, "move the bun packages to bun2nix"), while the last SDK publish was 0.1.2, bumped 2026-07-20. So the hook has been broken for every SDK release since it landed — 0.1.3 is just the first one to try it.
@punktfunk/plugin-kit has no prepare at all, which is why kit 0.3.2 published fine inside that window and this stayed hidden.
Why not just copy web
web/package.json does the same job from postinstall. That is correct for web — it is never published — and would be worse here: a published package's postinstall runs in every consumer's install, so every plugin depending on @punktfunk/host would try to run bun2nix and fail.
prepare is the right hook for a published package (it does not run for consumers). It just must not assume a repo-maintenance tool exists wherever a publish happens.
The fix
The script skips when bun2nix is absent — and only then:
A present-but-failing bun2nix still fails the script. Swallowing that (|| true) would let a publish go out with a silently stale bun.nix, which is the exact hand-maintained-hash problem 1db8f763 set out to end.
Verification
Both directions measured against the same sh -e that bun and the Gitea runner use for lifecycle steps:
case
exit
want
bun2nix absent (the publish container)
0
0
bun2nix present but failing
3
non-zero
bun publish --dry-run now completes and reports + @punktfunk/host@0.1.3, and the packed dist/ui.js:139 carries the category forwarding that is the point of the 0.1.3 release.
After merge
I will delete and re-push sdk-v0.1.3 (it published nothing, so the version is not burned), confirm the run goes green and that 0.1.3 actually appears on the registry, then tag plugin-kit-v0.3.3 — that order matters, since kit 0.3.3's peer range requires @punktfunk/host ^0.1.3 and publishing it first would leave a window where the kit is uninstallable.
`sdk-v0.1.3` failed at the publish step: **`bun2nix: command not found`, exit 127**. Nothing reached the registry, so **0.1.3 is still free** — `@punktfunk/host` is still at 0.1.2.
## What happened
`bun publish` runs the `prepare` lifecycle script, and sdk's `prepare` is `bun2nix -o bun.nix` — regenerating the nix dependency file. bun2nix is a repo-maintenance tool, not something the `oven/bun:1` publish container has; the workflow's own install is `--ignore-scripts`, so nothing put it on PATH either.
```
bun publish v1.3.14
$ bun run build
$ tsc -p tsconfig.build.json
$ bun2nix -o bun.nix
/usr/bin/bash: line 1: bun2nix: command not found
error: script "prepare" exited with code 127
```
## This was latent, not new
`prepare` gained the bun2nix call on **2026-07-27** (`1db8f763`, *"move the bun packages to bun2nix"*), while the last SDK publish was 0.1.2, bumped **2026-07-20**. So the hook has been broken for **every** SDK release since it landed — 0.1.3 is just the first one to try it.
`@punktfunk/plugin-kit` has no `prepare` at all, which is why kit 0.3.2 published fine inside that window and this stayed hidden.
## Why not just copy web
`web/package.json` does the same job from **`postinstall`**. That is correct for web — it is never published — and would be *worse* here: a published package's `postinstall` runs in every **consumer's** install, so every plugin depending on `@punktfunk/host` would try to run bun2nix and fail.
`prepare` is the right hook for a published package (it does not run for consumers). It just must not assume a repo-maintenance tool exists wherever a publish happens.
## The fix
The script skips when bun2nix is absent — and **only** then:
```
"prepare": "if command -v bun2nix >/dev/null 2>&1; then bun2nix -o bun.nix; fi"
```
A present-but-failing bun2nix still fails the script. Swallowing that (`|| true`) would let a publish go out with a silently stale `bun.nix`, which is the exact hand-maintained-hash problem `1db8f763` set out to end.
## Verification
Both directions measured against the same `sh -e` that bun and the Gitea runner use for lifecycle steps:
| case | exit | want |
|---|---|---|
| bun2nix absent (the publish container) | 0 | 0 |
| bun2nix present but failing | 3 | non-zero |
`bun publish --dry-run` now completes and reports `+ @punktfunk/host@0.1.3`, and the packed `dist/ui.js:139` carries the `category` forwarding that is the point of the 0.1.3 release.
## After merge
I will delete and re-push `sdk-v0.1.3` (it published nothing, so the version is not burned), confirm the run goes green and that 0.1.3 actually appears on the registry, then tag `plugin-kit-v0.3.3` — that order matters, since kit 0.3.3's peer range requires `@punktfunk/host ^0.1.3` and publishing it first would leave a window where the kit is uninstallable.
`sdk-v0.1.3` failed at the publish step with `bun2nix: command not found`, exit 127.
Nothing was published, so 0.1.3 is still free.
`bun publish` runs the `prepare` lifecycle script, and sdk's `prepare` is
`bun2nix -o bun.nix` — regenerating the nix dependency file. That tool is a
devDependency of the repo, not something the `oven/bun:1` publish container has, and
the workflow's own install is `--ignore-scripts`, so nothing put it on PATH either.
This was latent, not new. `prepare` gained the bun2nix call on 2026-07-27 (1db8f763,
"move the bun packages to bun2nix"), while the last SDK publish was 0.1.2, bumped
2026-07-20. So the hook has been broken for every SDK release since it landed, and
0.1.3 is simply the first one to try. `@punktfunk/plugin-kit` has no `prepare` and was
never affected, which is why kit 0.3.2 published fine in that window and hid this.
The fix is NOT to copy `web/package.json`, which does the same job from `postinstall`.
That is right for web — it is never published — and would be worse here: a published
package's `postinstall` runs in every CONSUMER's install, so every plugin depending on
`@punktfunk/host` would try to run bun2nix and fail. `prepare` is the correct hook for
a published package (it does not run for consumers); it just must not assume a
repo-maintenance tool exists wherever a publish happens.
So the script skips when bun2nix is absent — and ONLY then. A present-but-failing
bun2nix still fails the script, because swallowing that would publish with a silently
stale bun.nix, which is the exact hand-maintained-hash problem 1db8f763 set out to end.
Both directions measured against the same `sh -e` bun and the Gitea runner use:
absent → exit 0, present-and-failing → exit 3.
`bun publish --dry-run` now completes and reports `+ @punktfunk/host@0.1.3`.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
sdk-v0.1.3failed at the publish step:bun2nix: command not found, exit 127. Nothing reached the registry, so 0.1.3 is still free —@punktfunk/hostis still at 0.1.2.What happened
bun publishruns thepreparelifecycle script, and sdk'sprepareisbun2nix -o bun.nix— regenerating the nix dependency file. bun2nix is a repo-maintenance tool, not something theoven/bun:1publish container has; the workflow's own install is--ignore-scripts, so nothing put it on PATH either.This was latent, not new
preparegained the bun2nix call on 2026-07-27 (1db8f763, "move the bun packages to bun2nix"), while the last SDK publish was 0.1.2, bumped 2026-07-20. So the hook has been broken for every SDK release since it landed — 0.1.3 is just the first one to try it.@punktfunk/plugin-kithas noprepareat all, which is why kit 0.3.2 published fine inside that window and this stayed hidden.Why not just copy web
web/package.jsondoes the same job frompostinstall. That is correct for web — it is never published — and would be worse here: a published package'spostinstallruns in every consumer's install, so every plugin depending on@punktfunk/hostwould try to run bun2nix and fail.prepareis the right hook for a published package (it does not run for consumers). It just must not assume a repo-maintenance tool exists wherever a publish happens.The fix
The script skips when bun2nix is absent — and only then:
A present-but-failing bun2nix still fails the script. Swallowing that (
|| true) would let a publish go out with a silently stalebun.nix, which is the exact hand-maintained-hash problem1db8f763set out to end.Verification
Both directions measured against the same
sh -ethat bun and the Gitea runner use for lifecycle steps:bun publish --dry-runnow completes and reports+ @punktfunk/host@0.1.3, and the packeddist/ui.js:139carries thecategoryforwarding that is the point of the 0.1.3 release.After merge
I will delete and re-push
sdk-v0.1.3(it published nothing, so the version is not burned), confirm the run goes green and that 0.1.3 actually appears on the registry, then tagplugin-kit-v0.3.3— that order matters, since kit 0.3.3's peer range requires@punktfunk/host ^0.1.3and publishing it first would leave a window where the kit is uninstallable.bun publishrunsprepare