{"record":{"id":"0604cdebe3cb3c0c","repo":"halo-dev/halo","slug":"invalid-attachment","errorCode":null,"errorMessage":"Invalid attachment","messagePattern":"Invalid attachment","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ui/packages/shared/src/utils/attachment.ts","lineNumber":113,"sourceCode":"   * utils.attachment.getUrl(attachmentObject);\n   * // Returns: attachmentObject.status?.permalink\n   *\n   * // AttachmentSimple object\n   * utils.attachment.getUrl({ url: \"https://example.com/image.jpg\" });\n   * // Returns: \"https://example.com/image.jpg\"\n   * ```\n   */\n  getUrl(attachment: AttachmentLike) {\n    if (typeof attachment === \"string\") {\n      return attachment;\n    }\n    if (\"spec\" in attachment) {\n      return attachment.status?.permalink;\n    }\n    if (\"url\" in attachment) {\n      return attachment.url;\n    }\n    throw new Error(\"Invalid attachment\");\n  }\n\n  /**\n   * Converts an attachment-like object to a simplified attachment format\n   *\n   * @param attachment - The attachment object to convert (can be a string URL, Attachment object, or AttachmentSimple)\n   * @returns A simplified attachment object with url, alt, and mediaType properties, or undefined\n   * @throws {Error} When the attachment type is invalid or unrecognized\n   *\n   * @remarks\n   * This method normalizes different attachment formats into a consistent AttachmentSimple structure:\n   * 1. String: Converts to object with only url property\n   * 2. Attachment object (with \"spec\"): Extracts permalink, displayName, and mediaType\n   * 3. AttachmentSimple (with \"url\"): Returns as-is since it's already in the correct format\n   *\n   * @example\n   * ```ts\n   * import { utils } from \"@halo-dev/ui-shared\"","sourceCodeStart":95,"sourceCodeEnd":131,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/ui/packages/shared/src/utils/attachment.ts#L95-L131","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Open the failing plugin's plugin.yaml and remove the dependency edge that closes the cycle so the graph is a DAG.","Make dependency direction one-way: if A declares B in pluginDependencies, ensure B does not declare A.","Install leaf dependencies first and dependents last, in topological order.","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."],"exampleFix":"// plugin.yaml (before) — cycle\nspec:\n  pluginDependencies:\n    plugin-b: \"1.0.0\"\n// plugin.yaml (after) — acyclic\nspec:\n  pluginDependencies: {}","handlingStrategy":"validation","validationCode":"// Before install/upgrade, build the same descriptor set Halo uses and pre-check for cycles.\nList<PluginDescriptor> descriptors = new ArrayList<>();\npluginManager.getResolvedPlugins().forEach(p -> descriptors.add(p.getDescriptor()));\ndescriptors.add(YamlPluginDescriptorFinder.convert(candidate));\nDependencyResolver resolver = new DependencyResolver(pluginManager.getVersionManager());\nDependencyResolver.Result r = resolver.resolve(descriptors);\nif (r.hasCyclicDependency()) {\n    // refuse to upload; report which plugins form the cycle\n}","typeGuard":null,"tryCatchPattern":"// install() returns Mono<Plugin>; react to PluginDependencyException.CyclicException\npluginService.install(path)\n    .onErrorResume(PluginDependencyException.CyclicException.class,\n        e -> Mono.fromRunnable(() -> log.warn(\"Refuse upload: cyclic dependency\"))\n                 .then(Mono.error(e)))","preventionTips":["Keep pluginDependencies a strict DAG in every published plugin.yaml.","Run a dependency-cycle check in your plugin release CI before publishing.","Document each dependency's metadata.name exactly to avoid edges to the wrong plugin."],"tags":["plugin","dependencies","install","validation","pf4j"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}