paperclipai/paperclip · error · Error

Upload did not return a file URL

Error message

Upload did not return a file URL

What it means

In the IssueChatThread composer's image attach flow, when the onAttachImage callback resolves, the code requires attachment.contentPath to be set as the file URL. If the callback returns an attachment without contentPath (or null), it throws "Upload did not return a file URL", aborting the composer attachment and showing the failure to the user.

Solutions

  1. Log the full attachment object returned by onAttachImage to see what the upload actually returned.
  2. Fix the host-supplied onAttachImage implementation to return { contentPath: <url>, ... } on success and reject on failure instead of resolving an empty object.
  3. Verify the upload endpoint's response parsing extracts the URL field correctly (field-name drift after API change).
  4. Check storage configuration so the upload service returns a usable file URL.

Example fix

// before (host integration)
const res = await upload(file);
return { id: attachmentId }; // missing contentPath
// after
const res = await upload(file);
if (!res?.url) throw new Error(`upload failed: ${res?.error ?? "no url"}`);
return { id: attachmentId, contentPath: res.url };
Defensive patterns

Strategy: validation

Validate before calling

const attachment = await onAttachImage(file);
if (!attachment?.contentPath) { showAttachError("upload returned no URL"); return; }

Type guard

function hasContentPath(a: unknown): a is { contentPath: string } & Record<string, unknown> {
  return typeof a === "object" && a !== null && typeof (a as any).contentPath === "string" && (a as any).contentPath.length > 0;
}

Try / catch

try {
  const url = await attachImage(file);
  addComposerAttachment(attachmentId, url);
} catch (e) {
  setComposerError(attachmentId, e.message); // show inline failure, keep draft intact
}

Prevention

When it happens

Trigger: A custom onAttachImage handler (host-app provided upload path) resolves with { contentPath: undefined } or null — e.g. the host integration's upload endpoint returned no URL, or the callback swallowed an upload error and returned an incomplete attachment object.

Common situations: Embedding host app's upload service returns 200 with a body missing the URL field; plugin/host integration not updated to the attachment contract that requires contentPath; storage backend misconfigured so the upload silently produces no public URL; race where the attachment was discarded (attachmentId no longer in composerAttachmentsRef) — though that path returns undefined instead of throwing.

Related errors


AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18). Data as JSON: /api/errors/8cb733fb375fbf4f. Report an issue: GitHub.

Appendix: source

Thrown at ui/src/components/IssueChatThread.tsx:5114

          )
        )
          return undefined;
        const safeName = file.name.replace(/[[\]]/g, "\\$&");
        const markdown = `![${safeName}](${url})`;
        if (insertInline)
          changeBody((prev) => (prev ? `${prev}\n\n${markdown}` : markdown));
        setComposerAttachments((prev) =>
          prev.map((item) =>
            item.id === attachmentId
              ? { ...item, status: "attached", contentPath: url }
              : item,
          ),
        );
        return url;
      } else if (onAttachImage) {
        const attachment = await onAttachImage(file);
        if (!attachment?.contentPath)
          throw new Error("Upload did not return a file URL");
        if (
          !composerAttachmentsRef.current.some(
            (item) => item.id === attachmentId,
          )
        )
          return undefined;
        if (inline && insertInline) {
          const markdown = `![${file.name.replace(/[[\]]/g, "\\$&")}](${attachment.contentPath})`;
          changeBody((prev) => (prev ? `${prev}\n\n${markdown}` : markdown));
        }
        setComposerAttachments((prev) =>
          prev.map((item) =>
            item.id === attachmentId
              ? {
                  ...item,
                  status: "attached",
                  attachmentId: attachment.id,
                  contentPath: attachment?.contentPath,

View on GitHub (pinned to 3f1d897a7c)