Publishing @punktfunk/host@0.1.3 — the release that lets a library scanner register category, so Lutris and Heroic stay out of the console nav — reached no existing install. On .21 the only thing that moved it was deleting bun.lock by hand over ssh. A fix that needs an ssh session is not a fix.
Why nothing reached it
Every plugin resolves the SDK from the plugins tree, and bun.lock pins it to an exact version with an integrity hash. Nothing in any user-facing flow re-resolves that pin. Installing a plugin, reinstalling it, and even updating it to a newer release all leave the SDK where it is, because the plugin's ^0.1.x range is already satisfied by what is locked. bun update does not help either — the plugins are pinned exactly in the root manifest, so there is no direct dependency to update through (measured: it left the root on 0.1.2).
Where the fix belongs
The runner. It is bundled from this same sdk/ at the host's release commit — packaging/arch/PKGBUILD builds src/runner-cli.ts into the punktfunk-scripting package — so SDK_VERSION is by construction the SDK that matches the host now on disk. A host upgrade is the one moment that can carry an SDK fix to already-installed plugins, and now it does, before any plugin loads and with no operator action.
Why it re-resolves the lockfile instead of pinning at the root
A targeted bun add @punktfunk/host@<v> does not work while plugins declare the SDK in their own dependencies — all six scanners do, though none of them import it. bun honours their locked resolution and gives each plugin a private nested copy which then shadows the root. Measured: 5 nested copies. That is also how I first "fixed" the box while leaving every plugin still importing 0.1.2.
A lockless resolve hoists one copy for everyone. Once the plugins drop that spurious dependency (follow-up, six patch releases) this can become the targeted form.
Safety
This runs unattended at boot, on the tree the operator's plugins load from:
Plugin versions are pinned exactly in the root manifest, so a re-resolve cannot move them — verified, lutris stays 0.1.0. Only shared transitive deps float within declared ranges.
The lockfile is backed up and restored if the install fails or succeeds without delivering the version.
Every failure is logged and swallowed. A dependency refresh must never stop working plugins from starting.
The no-op path is what runs on every healthy box, so it is tested first: same version, or no SDK installed at all, touches nothing and logs nothing.
The version bump
The SDK goes to 0.1.4 because its published content changed. Republishing 0.1.3 is impossible, and letting source drift from a published version is exactly the defect that produced this whole chain — 0.1.2 was published, then category was added to the source without a bump, so the registry served pre-category code under a version whose source had it. version.test.ts fails if SDK_VERSION and package.json ever disagree again.
Verification
End to end on .21, against a tree seeded from the operator's real pre-fix backup (bun.lock.bak-presdk013), i.e. the exact state that needed manual intervention:
[plugins] @punktfunk/host 0.1.2 installed, this host ships 0.1.3 — refreshing
[plugins] @punktfunk/host is now 0.1.3
SDK: 79 tests pass (5 new), typecheck clean. The SDK has no biome config and its CI runs typecheck/test/build only, so no lint step applies.
After merge
Tag sdk-v0.1.4 to publish, then the next host build ships a runner that reconciles on upgrade. The six scanner plugins dropping their spurious @punktfunk/host dependency is the follow-up that lets this become a targeted root pin instead of a full re-resolve.
Publishing `@punktfunk/host@0.1.3` — the release that lets a library scanner register `category`, so Lutris and Heroic stay out of the console nav — reached **no existing install**. On `.21` the only thing that moved it was deleting `bun.lock` by hand over ssh. A fix that needs an ssh session is not a fix.
## Why nothing reached it
Every plugin resolves the SDK from the plugins tree, and `bun.lock` pins it to an exact version with an integrity hash. **Nothing in any user-facing flow re-resolves that pin.** Installing a plugin, reinstalling it, and even updating it to a newer release all leave the SDK where it is, because the plugin's `^0.1.x` range is already satisfied by what is locked. `bun update` does not help either — the plugins are pinned exactly in the root manifest, so there is no direct dependency to update through (measured: it left the root on 0.1.2).
## Where the fix belongs
The runner. It is bundled from this same `sdk/` at the host's release commit — `packaging/arch/PKGBUILD` builds `src/runner-cli.ts` into the punktfunk-scripting package — so `SDK_VERSION` is *by construction* the SDK that matches the host now on disk. A host upgrade is the one moment that can carry an SDK fix to already-installed plugins, and now it does, before any plugin loads and with no operator action.
## Why it re-resolves the lockfile instead of pinning at the root
A targeted `bun add @punktfunk/host@<v>` does **not** work while plugins declare the SDK in their own `dependencies` — all six scanners do, though **none of them import it**. bun honours their locked resolution and gives each plugin a private nested copy which then *shadows* the root. Measured: 5 nested copies. That is also how I first "fixed" the box while leaving every plugin still importing 0.1.2.
A lockless resolve hoists one copy for everyone. Once the plugins drop that spurious dependency (follow-up, six patch releases) this can become the targeted form.
## Safety
This runs unattended at boot, on the tree the operator's plugins load from:
- Plugin versions are pinned exactly in the root manifest, so a re-resolve **cannot** move them — verified, lutris stays 0.1.0. Only shared transitive deps float within declared ranges.
- The lockfile is backed up and restored if the install fails *or* succeeds without delivering the version.
- Every failure is logged and swallowed. A dependency refresh must never stop working plugins from starting.
- The no-op path is what runs on every healthy box, so it is tested first: same version, or no SDK installed at all, touches nothing and logs nothing.
## The version bump
The SDK goes to **0.1.4** because its published content changed. Republishing 0.1.3 is impossible, and letting source drift from a published version is exactly the defect that produced this whole chain — 0.1.2 was published, then `category` was added to the source without a bump, so the registry served pre-`category` code under a version whose source had it. `version.test.ts` fails if `SDK_VERSION` and `package.json` ever disagree again.
## Verification
End to end on `.21`, against a tree seeded from the operator's **real pre-fix backup** (`bun.lock.bak-presdk013`), i.e. the exact state that needed manual intervention:
```
[plugins] @punktfunk/host 0.1.2 installed, this host ships 0.1.3 — refreshing
[plugins] @punktfunk/host is now 0.1.3
```
- one hoisted `@punktfunk/host`, **zero** nested copies
- plugin versions preserved (lutris 0.1.0, steam 0.1.0, heroic 0.1.0)
- a second run is a silent no-op
SDK: 79 tests pass (5 new), typecheck clean. The SDK has no biome config and its CI runs typecheck/test/build only, so no lint step applies.
## After merge
Tag `sdk-v0.1.4` to publish, then the next host build ships a runner that reconciles on upgrade. The six scanner plugins dropping their spurious `@punktfunk/host` dependency is the follow-up that lets this become a targeted root pin instead of a full re-resolve.
Publishing `@punktfunk/host@0.1.3` — the release that lets a library scanner register
`category`, so Lutris and Heroic stay out of the console nav — reached **no existing
install**. Measured on `.21`: the only thing that moved it was deleting `bun.lock` by
hand over ssh. A fix that needs an ssh session is not a fix.
**Why nothing reached it.** Every plugin resolves the SDK from the plugins tree, and
`bun.lock` pins it to an exact version with an integrity hash. Nothing in any
user-facing flow re-resolves that pin: installing a plugin, reinstalling it, and even
updating it to a newer release all leave the SDK alone, because the plugin's `^0.1.x`
range is already satisfied by what is locked. `bun update` does not help either — the
plugins are pinned exactly in the root manifest, so there is no direct dependency to
update through.
**Where the fix belongs.** The runner. It is bundled from this same `sdk/` at the
host's release commit (`packaging/arch/PKGBUILD` builds `src/runner-cli.ts` into the
punktfunk-scripting package), so `SDK_VERSION` is by construction the SDK matching the
host now on disk. A host upgrade is therefore the one moment that can carry an SDK fix
to already-installed plugins, and now it does — before any plugin loads, and with no
operator action at all.
**Why it re-resolves the whole lockfile** rather than pinning the SDK at the root: a
targeted `bun add @punktfunk/host@<v>` does NOT work while plugins declare the SDK in
their own `dependencies` (all six scanners do, though none import it). bun honours
their locked resolution and gives each a private nested copy that then SHADOWS the
root — measured, 5 nested copies, which is how I first "fixed" the box while leaving
every plugin still importing 0.1.2. A lockless resolve hoists one copy for everyone.
Once the plugins drop that spurious dependency this can become the targeted form.
Safety, because this runs unattended at boot on a tree the operator's plugins load
from: plugin versions are pinned exactly in the root manifest so a re-resolve cannot
move them (verified — lutris stays 0.1.0); the lockfile is backed up and restored if
the install fails or fails to deliver; and every failure is logged and swallowed, so a
dependency refresh can never stop working plugins from starting. The no-op path is the
one that runs on every healthy box, so it is tested first: same version, or no SDK at
all, touches nothing and logs nothing.
The SDK is bumped to 0.1.4 because its published content changed. Republishing 0.1.3
is impossible, and letting source drift from a published version is precisely the
defect that produced this whole chain — 0.1.2 was published before it forwarded
`category`, then the source changed underneath it without a bump. `version.test.ts`
fails if `SDK_VERSION` and `package.json` ever disagree.
Verified end to end on `.21` against a tree seeded from the operator's real pre-fix
backup: 0.1.2 → 0.1.3 automatically, one hoisted copy, no nested copies, plugin
versions preserved, and a second run is a silent no-op. SDK 79 tests pass (5 new),
typecheck clean.
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.
Publishing
@punktfunk/host@0.1.3— the release that lets a library scanner registercategory, so Lutris and Heroic stay out of the console nav — reached no existing install. On.21the only thing that moved it was deletingbun.lockby hand over ssh. A fix that needs an ssh session is not a fix.Why nothing reached it
Every plugin resolves the SDK from the plugins tree, and
bun.lockpins it to an exact version with an integrity hash. Nothing in any user-facing flow re-resolves that pin. Installing a plugin, reinstalling it, and even updating it to a newer release all leave the SDK where it is, because the plugin's^0.1.xrange is already satisfied by what is locked.bun updatedoes not help either — the plugins are pinned exactly in the root manifest, so there is no direct dependency to update through (measured: it left the root on 0.1.2).Where the fix belongs
The runner. It is bundled from this same
sdk/at the host's release commit —packaging/arch/PKGBUILDbuildssrc/runner-cli.tsinto the punktfunk-scripting package — soSDK_VERSIONis by construction the SDK that matches the host now on disk. A host upgrade is the one moment that can carry an SDK fix to already-installed plugins, and now it does, before any plugin loads and with no operator action.Why it re-resolves the lockfile instead of pinning at the root
A targeted
bun add @punktfunk/host@<v>does not work while plugins declare the SDK in their owndependencies— all six scanners do, though none of them import it. bun honours their locked resolution and gives each plugin a private nested copy which then shadows the root. Measured: 5 nested copies. That is also how I first "fixed" the box while leaving every plugin still importing 0.1.2.A lockless resolve hoists one copy for everyone. Once the plugins drop that spurious dependency (follow-up, six patch releases) this can become the targeted form.
Safety
This runs unattended at boot, on the tree the operator's plugins load from:
The version bump
The SDK goes to 0.1.4 because its published content changed. Republishing 0.1.3 is impossible, and letting source drift from a published version is exactly the defect that produced this whole chain — 0.1.2 was published, then
categorywas added to the source without a bump, so the registry served pre-categorycode under a version whose source had it.version.test.tsfails ifSDK_VERSIONandpackage.jsonever disagree again.Verification
End to end on
.21, against a tree seeded from the operator's real pre-fix backup (bun.lock.bak-presdk013), i.e. the exact state that needed manual intervention:@punktfunk/host, zero nested copiesSDK: 79 tests pass (5 new), typecheck clean. The SDK has no biome config and its CI runs typecheck/test/build only, so no lint step applies.
After merge
Tag
sdk-v0.1.4to publish, then the next host build ships a runner that reconciles on upgrade. The six scanner plugins dropping their spurious@punktfunk/hostdependency is the follow-up that lets this become a targeted root pin instead of a full re-resolve.