halo-dev/halo · error · Error

Host runtime snapshot haloVersion must be stable semver.

Error message

Host runtime snapshot haloVersion must be stable semver.

What it means

YamlPluginFinder.readPluginDescriptor throws this pf4j PluginRuntimeException when getManifestPath returns null — which for a jar happens when FileUtils.getPath cannot locate the descriptor entry. It surfaces through findPluginManifest during install/upgrade/reload, failing the operation.

Source

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

  haloVersion: string;
  packages: Record<SharedPackageRoot, HostRuntimeSnapshotEntry>;
}

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(

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Open the jar (`jar tf plugin.jar`) and confirm plugin.yaml is present at the expected location.
  2. Rebuild the plugin ensuring plugin.yaml is packaged at the archive root (or the configured classes directory).
  3. If installing from a directory, ensure plugin.yaml sits under a classes directory (see error 116).
Defensive patterns

Strategy: validation

Validate before calling

// Before install, confirm the jar contains a discoverable descriptor.
try (JarFile jar = new JarFile(jarPath.toFile())) {
    if (jar.getEntry("plugin.yaml") == null) {
        throw new IllegalStateException("jar has no plugin.yaml");
    }
}

Try / catch

pluginService.install(path)
    .onErrorResume(PluginRuntimeException.class,
        e -> Mono.error(new BusinessException("Invalid plugin package: " + e.getMessage())))

Prevention

When it happens

Trigger: Installing, upgrading, or reloading a plugin jar that contains no discoverable plugin.yaml entry, when getPath returns null instead of throwing.

Common situations: Corrupt or empty jar; descriptor stored under an unexpected archive path; jar built without including plugin.yaml.

Related errors


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