{"record":{"id":"4353349c777f639f","repo":"halo-dev/halo","slug":"no-esm-host-runtime-snapshot-is-available-for-halo","errorCode":null,"errorMessage":"No ESM host runtime snapshot is available for Halo ${targetHaloVersion}. Update @halo-dev/ui-plugin-bundler-kit or select IIFE output.","messagePattern":"No ESM host runtime snapshot is available for Halo (.+?)\\. Update @halo-dev/ui-plugin-bundler-kit or select IIFE output\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"ui/packages/ui-plugin-bundler-kit/src/runtime-snapshot.ts","lineNumber":99,"sourceCode":"  snapshots: readonly HaloHostRuntimeSnapshot[] = HALO_HOST_RUNTIME_SNAPSHOTS\n) {\n  const target = parse(targetHaloVersion);\n  if (!target) {\n    throw new Error(`Invalid target Halo version: ${targetHaloVersion}.`);\n  }\n\n  const eligible = snapshots\n    .filter(\n      (snapshot) =>\n        lte(snapshot.haloVersion, target.version) ||\n        (target.prerelease.length > 0 &&\n          snapshot.haloVersion ===\n            `${target.major}.${target.minor}.${target.patch}`)\n    )\n    .sort((left, right) => compare(right.haloVersion, left.haloVersion));\n  const snapshot = eligible[0];\n  if (!snapshot) {\n    throw new Error(\n      `No ESM host runtime snapshot is available for Halo ${targetHaloVersion}. ` +\n        \"Update @halo-dev/ui-plugin-bundler-kit or select IIFE output.\"\n    );\n  }\n\n  return {\n    snapshot,\n    reusedOlderSnapshot:\n      snapshot.haloVersion !==\n      `${target.major}.${target.minor}.${target.patch}`,\n  };\n}\n\nexport async function resolveSharedPackage(\n  root: SharedPackageRoot,\n  providerRoot: string,\n  sourceId?: string\n) {","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/ui/packages/ui-plugin-bundler-kit/src/runtime-snapshot.ts#L81-L117","documentation":"ReverseProxyRouterFunctionFactory.getResourceLoader throws NotFoundException (HTTP 404) when BundleResourceUtils.getResourceLoader returns null for the requested plugin name — i.e. no resource loader/bundle is registered for that plugin. It occurs while serving a plugin asset route.","triggerScenarios":"A GET to a plugin reverse-proxy asset route (e.g. /plugins/{name}/assets/...) where {name} has no loaded bundle (plugin not installed, not started, or misspelled).","commonSituations":"Requesting assets for a plugin that failed to load; typo in the plugin name in the URL; accessing assets before the plugin is started; plugin uninstalled while browsers still cache its asset URLs.","solutions":["Verify the plugin is installed and started; check its status phase is STARTED.","Correct the plugin name in the request URL to match metadata.name.","Reload/restart the plugin so its bundle resource loader is registered.","Return a 404-friendly fallback in the UI instead of surfacing the raw error."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Before linking a console asset URL, confirm a bundle loader exists.\nDefaultResourceLoader loader = BundleResourceUtils.getResourceLoader(pluginManager, pluginName);\nif (loader == null) {\n    // plugin not started; don't emit asset links yet\n}","typeGuard":null,"tryCatchPattern":"// Asset route handler — return 404 instead of letting NotFoundException propagate raw.\nrouter.route(GET(\"/plugins/{name}/assets/{*resource}\"), request)\n    .onErrorResume(NotFoundException.class, e -> ServerResponse.notFound().build())","preventionTips":["Only render plugin asset URLs when the plugin's status phase is STARTED.","Validate the plugin name against installed plugins before building URLs.","Handle 404s gracefully in the UI to avoid confusing users."],"tags":["plugin","reverse-proxy","assets","not-found","http-404"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}