paperclipai/paperclip · error

Failed to serve file

Error message

Failed to serve file

What it means

Returned as HTTP 500 by GET /_plugins/:pluginId/ui/* (server/src/routes/plugin-ui-static.ts:503) when res.sendFile's completion callback reports an error — the stat/symlink/containment checks all passed but the actual stream failed (EACCES, EMFILE, ENOSPC, or the file vanished between stat and send). If headers were already sent the status cannot be changed, so the connection is destroyed instead.

Source

Thrown at server/src/routes/plugin-ui-static.ts:503

      res.set("Content-Type", contentType);
    }

    // Step 9: Set CORS headers (plugin UI may be loaded from different origin in dev)
    res.set("Access-Control-Allow-Origin", "*");

    // Step 10: Send the file
    // The plugin source can live in Git worktrees (e.g. ".worktrees/...").
    // `send` defaults to dotfiles:"ignore", which treats dot-directories as
    // not found. We already enforce traversal safety above, so allow dot paths.
    res.sendFile(resolvedFilePath, { dotfiles: "allow" }, (err) => {
      if (err) {
        log.error(
          { err, pluginId: plugin.id, filePath: resolvedFilePath },
          "plugin-ui-static: error sending file",
        );
        // Only send error if headers haven't been sent yet
        if (!res.headersSent) {
          res.status(500).json({ error: "Failed to serve file" });
        }
      }
    });
  });

  return router;
}

View on GitHub (pinned to 120ae5428f)

Solutions

  1. Retry the request once — most causes (file swap race, transient EMFILE) are self-healing after the reinstall finishes
  2. If persistent, check read permissions on the file and every parent directory for the server user
  3. Raise the process file-descriptor limit (ulimit -n) if EMFILE appears in server logs
  4. Avoid uninstalling/reinstalling plugins while their UI is being served, or expect transient 500s during that window
Defensive patterns

Strategy: retry

Try / catch

const res = await fetch(url, { cache: "no-store" });
if (res.status === 500) {
  await new Promise((r) => setTimeout(r, 500));
  const retry = await fetch(url, { cache: "no-store" });
  if (retry.ok) return retry;
  throw new Error("Plugin asset serve failed twice — plugin may be mid-reinstall");
}

Prevention

When it happens

Trigger: The plugin package is replaced/deleted on disk between the statSync check and the sendFile stream opening (reinstall in flight); the server process lacks read permission on the file itself (execute-only directory traversal rights); file-descriptor exhaustion (EMFILE) under heavy asset traffic; disk/IO errors mid-stream.

Common situations: Concurrent plugin upgrade + active browser tabs loading assets; hardened filesystems where directory access and file access differ; fd limits hit on busy dev servers; antivirus/quarantine removing files mid-request.

Related errors


AI-assisted analysis of paperclipai/paperclip@120ae5428f (2026-08-18). Data as JSON: /api/errors/bd93e469bf792cd0. Report an issue: GitHub.