janhq/jan · error

${res.status}

Error message

${res.status}

What it means

CoworkPreviewPanel loads a preview asset URL with fetch; if the response is not ok it throws new Error(String(res.status)) so the status code becomes the error message (e.g. '404', '403'). The catch sets the panel to an error state showing that status.

Solutions

  1. Confirm the file exists at the given path before opening the preview.
  2. Check assetProtocol scope configuration for the file's directory.
  3. Refresh the file listing so the preview uses a current path.
  4. Improve the error message to include statusText and the URL for debugging.

Example fix

// before
if (!res.ok) throw new Error(String(res.status))
// after
if (!res.ok) throw new Error(`Preview fetch failed: ${res.status} ${res.statusText} for ${url}`)
Defensive patterns

Strategy: try-catch

Validate before calling

const head = await fetch(url, { method: 'HEAD' })
if (!head.ok) throw new Error(`Preview asset unavailable: ${head.status}`)

Type guard

const isOkResponse = (r: Response): r is Response & { ok: true } => r.ok

Try / catch

try {
  const res = await fetch(url)
  if (!res.ok) throw new Error(String(res.status))
  // ...load preview
} catch (err) {
  if (alive) setState({ status: 'error', message: `Preview failed (${err.message})` })
}

Prevention

When it happens

Trigger: The asset URL for a file preview resolves to 404 (file removed), 403 (asset scope/permission), or another 4xx/5xx from the local asset server.

Common situations: Previewing a file that was deleted or renamed after listing; Tauri asset protocol scope gaps; stale workspace file index pointing at old paths.

Related errors


AI-assisted analysis of janhq/jan@7205d770c1 (2026-09-17). Data as JSON: /api/errors/f5e2df86b3082d45. Report an issue: GitHub.

Appendix: source

Thrown at web-app/src/containers/CoworkPreviewPanel.tsx:107

      return
    }
    if (kind === 'file') {
      setState({ status: 'unsupported', path })
      return
    }
    setState({ status: 'loading', path })

    void (async () => {
      const url = getServiceHub().core().convertFileSrc(abs)
      if (isAssetKind(kind)) {
        // The element streams the file itself, so there is nothing to fetch and
        // a large video costs nothing to "open".
        if (alive) setState({ status: 'ready', path, kind, assetUrl: url })
        return
      }
      try {
        const res = await fetch(url)
        if (!res.ok) throw new Error(String(res.status))
        const size = Number(res.headers.get('content-length') ?? 0)
        if (size > MAX_PREVIEW_BYTES) throw new Error(TOO_LARGE)
        const content = await res.text()
        if (content.length > MAX_PREVIEW_BYTES) throw new Error(TOO_LARGE)
        if (!alive) return
        setState({
          status: 'ready',
          path,
          kind,
          content,
          unresolvedRefs: kind === 'html' ? unresolvedRefs(content) : undefined,
          externalRefs: kind === 'html' ? externalRefs(content) : undefined,
        })
      } catch (e) {
        if (!alive) return
        setState({
          status: 'failed',
          path,

View on GitHub (pinned to 7205d770c1)