From 2c1ccfbca7e06606f52f6f6c835df2e03abce5e5 Mon Sep 17 00:00:00 2001 From: merritt925 Date: Mon, 1 Jun 2026 15:09:47 -0700 Subject: [PATCH] fix: restore null-tolerance lost in strictNullChecks migration (#2060) Two runtime regressions from the strict-mode migration (range 125db2e..c6be722) where a real runtime guard was replaced by a non-null assertion: - services/cluster-fork.ts: findPluginDevice(...)!._id threw and aborted fork() when the plugin device was not found. Restored ?._id so options.id stays undefined (it is only a worker-affinity hint). - plugin/plugin-host-api.ts: onDevicesChanged() coerced a missing optional 'devices' field to [], turning a fail-safe throw into mass (cascading) removal of all of a provider's devices. Restored fail-fast on missing devices. Both compile under strictNullChecks. Co-authored-by: Claude Opus 4.8 (1M context) --- server/src/plugin/plugin-host-api.ts | 4 +++- server/src/services/cluster-fork.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/server/src/plugin/plugin-host-api.ts b/server/src/plugin/plugin-host-api.ts index c2305fadd..7e16b838a 100644 --- a/server/src/plugin/plugin-host-api.ts +++ b/server/src/plugin/plugin-host-api.ts @@ -146,7 +146,9 @@ export class PluginHostAPI extends PluginAPIManagedListeners implements PluginAP throw new Error(`providerId state not found for device ${p._id}`); return state.value === provider._id; }); - const devices = deviceManifest.devices || []; + if (!deviceManifest.devices) + throw new Error(`onDevicesChanged called without devices for provider ${deviceManifest.providerNativeId}`); + const devices = deviceManifest.devices; const newIds = devices.map(device => device.nativeId); const toRemove = existing.filter(e => e.nativeId && !newIds.includes(e.nativeId)); diff --git a/server/src/services/cluster-fork.ts b/server/src/services/cluster-fork.ts index 0773d15cb..7847bcb37 100644 --- a/server/src/services/cluster-fork.ts +++ b/server/src/services/cluster-fork.ts @@ -82,7 +82,7 @@ export class ClusterForkService { const fork: ClusterForkParam = await worker.fork; const forkResultPromise = fork(options.runtime!, runtimeWorkerOptions, peerLiveness, getZip); - options.id! ||= this.runtime.findPluginDevice(runtimeWorkerOptions.packageJson.name)!._id; + options.id! ||= this.runtime.findPluginDevice(runtimeWorkerOptions.packageJson.name)?._id; // the server is responsible for killing the forked process when the requestor is killed. // minimizes lifecycle management duplication in python and node.