chatboxai/chatbox · error · Error

pdf_password_protected

pdf_password_protected

Error message

pdf_password_protected

What it means

Thrown by CustomClaude.listModels() when GET `${apiHost}/models?limit=990` returns a body without a top-level `data` array. Identical shape-guard to the built-in Claude provider, but here the host is a user-supplied `apiHost` (a custom Anthropic-compatible endpoint) and the request is sent through the optional proxy with a fixed `x-api-key` header. The missing `data` field means the custom endpoint is not speaking the Anthropic `/v1/models` schema.

Source

Thrown at src/main/file-parser.ts:243

      }
    }

    if (pageTexts.every((text) => text === '')) {
      // No extractable text (e.g. a scanned PDF) — keep returning an empty string
      // instead of a list of bare page markers.
      return ''
    }

    log.info(`Parsed PDF ${filePath}: ${document.numPages} pages`)
    return pageTexts.map((text, i) => `${formatPdfPageMarker(i + 1)}\n\n${text}`).join('\n\n')
  } catch (error) {
    // pdfjs throws a typed PasswordException for encrypted PDFs. Surface it as a
    // distinct code so the user is told the PDF needs a password instead of the
    // generic "unsupported file" message (and so the cloud fallback, which also
    // cannot read it, is skipped). Per-page failures are handled inside the loop
    // and never reach here.
    if (error instanceof Error && error.name === 'PasswordException') {
      throw new Error(LOCAL_PARSER_PDF_PASSWORD_PROTECTED_ERROR)
    }
    throw error
  } finally {
    await loadingTask.destroy()
  }
}

export async function parseEpub(filePath: string): Promise<string> {
  return new Promise((resolve, reject) => {
    const epub = new Epub(filePath)

    epub.on('error', (error) => {
      log.error('EPUB parsing error:', error)
      reject(error)
    })

    epub.on('end', async () => {
      try {

View on GitHub (pinned to 81571269ad)

Solutions

  1. Confirm the custom host actually implements the Anthropic `/v1/models` endpoint with `{ data: [{ id, type }] }` — many Claude relays only proxy chat.
  2. Read the JSON.stringify'd payload inside the ApiError message: an `error.type` of `authentication_error` means bad key; an `overloaded_error` is transient; an object with `models` instead of `data` means wrong schema.
  3. Set `apiHost` to the base origin only (e.g. `https://relay.example.com`), no `/v1` suffix — normalizeClaudeHost appends the version path.
  4. If the relay is OpenAI-style, switch the provider type from 'Custom Claude' to a generic Custom OpenAI provider.

Example fix

// before: relay returns { models: [...] }
// after: either expose /v1/models with Anthropic schema, or define models manually
async listModels() {
  try { return await super.listModels() }
  catch (e) {
    if (e instanceof ApiError) return [{ modelId: this.options.model.modelId, type: 'chat' }]
    throw e
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

async function probeCustomClaudeModels(host: string, key: string): Promise<boolean> {
  const res = await fetch(`${host}/models?limit=990`, {
    headers: { 'anthropic-version': '2023-06-01', 'x-api-key': key }
  })
  const json = await res.json().catch(() => ({}))
  return Array.isArray((json as any)?.data)
}

Type guard

function isAnthropicModelsResponse(json: unknown): json is { data: { id: string; type: string }[] } {
  return typeof json === 'object' && json !== null && Array.isArray((json as any).data)
}

Try / catch

try { return await customClaude.listModels() }
catch (e) {
  if (e instanceof ApiError) return [{ modelId: customClaude.options.model.modelId, type: 'chat' }]
  throw e
}

Prevention

When it happens

Trigger: A custom provider configured with an OpenAI-compatible base URL mistaken for an Anthropic-compatible one (returns `{ data: [...] }` is actually correct for Anthropic, but an OpenAI proxy returning `{ models: [...] }` fails); `apiHost` includes a trailing `/v1` or a path segment so the constructed URL is malformed; `apiKey` is wrong and the upstream returns `{ type: 'error', error: { ... } }`; the proxy rewrites the response into HTML or a generic error envelope.

Common situations: User adds a 'Custom Claude' provider pointing at a relay that only implements `/v1/messages` (chat) but not `/v1/models`; apiHost pasted with a trailing slash or `/v1` suffix that normalizeClaudeHost then double-prefixes; key from a different vendor reused; reverse proxy (nginx/cloudflare) returns a 502 HTML page that res.json() parses to `{}` or throws.

Related errors


AI-assisted analysis of chatboxai/chatbox@81571269ad (2026-08-12). Data as JSON: /api/errors/1621ee5c945bb58e. Report an issue: GitHub.