halo-dev/halo · error · Error

Invalid attachment

Error message

Invalid attachment

What it means

Thrown by PluginServiceImpl.checkDependencies during plugin install() or upgrade() when pf4j's DependencyResolver detects a cycle in the dependency graph formed by the candidate plugin plus every currently resolved plugin. It is a PluginDependencyException.CyclicException (subclass of ServerWebInputException, HTTP 400) and sets the RFC 7807 problem type https://halo.run/probs/plugin-cyclic-dependency.

Source

Thrown at ui/packages/shared/src/utils/attachment.ts:113

   * utils.attachment.getUrl(attachmentObject);
   * // Returns: attachmentObject.status?.permalink
   *
   * // AttachmentSimple object
   * utils.attachment.getUrl({ url: "https://example.com/image.jpg" });
   * // Returns: "https://example.com/image.jpg"
   * ```
   */
  getUrl(attachment: AttachmentLike) {
    if (typeof attachment === "string") {
      return attachment;
    }
    if ("spec" in attachment) {
      return attachment.status?.permalink;
    }
    if ("url" in attachment) {
      return attachment.url;
    }
    throw new Error("Invalid attachment");
  }

  /**
   * Converts an attachment-like object to a simplified attachment format
   *
   * @param attachment - The attachment object to convert (can be a string URL, Attachment object, or AttachmentSimple)
   * @returns A simplified attachment object with url, alt, and mediaType properties, or undefined
   * @throws {Error} When the attachment type is invalid or unrecognized
   *
   * @remarks
   * This method normalizes different attachment formats into a consistent AttachmentSimple structure:
   * 1. String: Converts to object with only url property
   * 2. Attachment object (with "spec"): Extracts permalink, displayName, and mediaType
   * 3. AttachmentSimple (with "url"): Returns as-is since it's already in the correct format
   *
   * @example
   * ```ts
   * import { utils } from "@halo-dev/ui-shared"

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Open the failing plugin's plugin.yaml and remove the dependency edge that closes the cycle so the graph is a DAG.
  2. Make dependency direction one-way: if A declares B in pluginDependencies, ensure B does not declare A.
  3. Install leaf dependencies first and dependents last, in topological order.
  4. Inspect the resolved-plugin set too — the cycle may involve an already-installed plugin, not just the new one, so uninstall/repackage the offending side.

Example fix

// plugin.yaml (before) — cycle
spec:
  pluginDependencies:
    plugin-b: "1.0.0"
// plugin.yaml (after) — acyclic
spec:
  pluginDependencies: {}
Defensive patterns

Strategy: validation

Validate before calling

// Before install/upgrade, build the same descriptor set Halo uses and pre-check for cycles.
List<PluginDescriptor> descriptors = new ArrayList<>();
pluginManager.getResolvedPlugins().forEach(p -> descriptors.add(p.getDescriptor()));
descriptors.add(YamlPluginDescriptorFinder.convert(candidate));
DependencyResolver resolver = new DependencyResolver(pluginManager.getVersionManager());
DependencyResolver.Result r = resolver.resolve(descriptors);
if (r.hasCyclicDependency()) {
    // refuse to upload; report which plugins form the cycle
}

Try / catch

// install() returns Mono<Plugin>; react to PluginDependencyException.CyclicException
pluginService.install(path)
    .onErrorResume(PluginDependencyException.CyclicException.class,
        e -> Mono.fromRunnable(() -> log.warn("Refuse upload: cyclic dependency"))
                 .then(Mono.error(e)))

Prevention

When it happens

Trigger: Calling PluginService.install(Path) or PluginService.upgrade(name, Path) with a plugin whose spec.pluginDependencies, combined with the already-installed plugins, closes a loop (e.g. A depends on B and B depends on A, or a plugin lists itself).

Common situations: Two plugins that mutually reference each other; refactoring a plugin to depend on a downstream plugin that already depends on it; installing a plugin whose dependency is enabled but transitively points back.

Related errors


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