halo-dev/halo · error · Error

Host runtime snapshot packages must be an object.

Error message

Host runtime snapshot packages must be an object.

What it means

readPluginDescriptor computes a manifest path but Files.notExists(path) is true, so this PluginRuntimeException is thrown. It indicates the descriptor path was resolved yet does not exist on the filesystem at read time — a race or stale symlink.

Source

Thrown at ui/packages/ui-plugin-bundler-kit/src/runtime-snapshot.ts:54

const sharedPackageRootSet = new Set<string>(SHARED_PACKAGE_ROOTS);

export const HALO_HOST_RUNTIME_SNAPSHOTS = Object.freeze(
  rawHaloHostRuntimeSnapshots.map(validateHaloHostRuntimeSnapshot)
);

export function validateHaloHostRuntimeSnapshot(
  value: unknown
): HaloHostRuntimeSnapshot {
  if (
    !isRecord(value) ||
    typeof value.haloVersion !== "string" ||
    !parseStableVersion(value.haloVersion)
  ) {
    throw new Error("Host runtime snapshot haloVersion must be stable semver.");
  }
  if (!isRecord(value.packages)) {
    throw new Error("Host runtime snapshot packages must be an object.");
  }
  const snapshotPackages = value.packages;

  const packageRoots = Object.keys(value.packages).sort();
  const supportedRoots = [...SHARED_PACKAGE_ROOTS].sort();
  if (
    packageRoots.length !== supportedRoots.length ||
    packageRoots.some((root, index) => root !== supportedRoots[index])
  ) {
    throw new Error(
      `Host runtime snapshot must expose exactly: ${SHARED_PACKAGE_ROOTS.join(", ")}.`
    );
  }

  const packages = Object.fromEntries(
    SHARED_PACKAGE_ROOTS.map((root) => [
      root,
      validateSnapshotEntry(root, snapshotPackages[root]),

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Re-upload or re-place the plugin jar/directory and retry the install.
  2. Ensure the plugins directory is not being garbage-collected or moved concurrently.
  3. Verify the descriptor file is a real, readable file before calling install.
Defensive patterns

Strategy: validation

Validate before calling

Path manifest = getManifestPath(pluginPath, "plugin.yaml");
if (manifest == null || !Files.isReadable(manifest)) {
    throw new IllegalStateException("plugin.yaml missing or unreadable: " + manifest);
}

Try / catch

pluginService.install(path)
    .onErrorResume(PluginRuntimeException.class,
        e -> Mono.error(new BusinessException("Descriptor unreadable; re-upload: " + e.getMessage())))

Prevention

When it happens

Trigger: findPluginManifest resolves a descriptor path that is deleted/moved between resolution and the existence check, or points at a broken symlink.

Common situations: Concurrent cleanup of the plugins directory; partial upload where the file is removed mid-install; symlink to a missing target.

Related errors


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/39aa538f9e09c70a. Report an issue: GitHub.