pear-devs/pear-desktop · error · Error

Expected an array, instead got ${typeof data}

Error message

Expected an array, instead got ${typeof data}

What it means

After a successful first LRCLib search request, the provider asserts the JSON body is an array (the LRCLib search API returns a list of matches). If the body is an object, string, null, or the response is an HTML error page parsed as JSON into a non-array, it throws this TypeError-style error.

Source

Thrown at src/plugins/synced-lyrics/providers/LRCLib.ts:39

      artist_name: artist,
      track_name: title,
    });

    query.set('album_name', album!);
    if (query.get('album_name') === 'undefined') {
      query.delete('album_name');
    }

    let url = `${this.baseUrl}/api/search?${query.toString()}`;
    let response = await fetch(url);

    if (!response.ok) {
      throw new Error(`bad HTTPStatus(${response.statusText})`);
    }

    let data = (await response.json()) as LRCLIBSearchResponse;
    if (!data || !Array.isArray(data)) {
      throw new Error(`Expected an array, instead got ${typeof data}`);
    }

    if (data.length === 0) {
      if (!config()?.showLyricsEvenIfInexact) {
        return null;
      }

      // Try to search with the alternative title (original language)
      const trackName = alternativeTitle || title;
      query = new URLSearchParams({ q: `${trackName}` });
      url = `${this.baseUrl}/api/search?${query.toString()}`;

      response = await fetch(url);
      if (!response.ok) {
        throw new Error(`bad HTTPStatus(${response.statusText})`);
      }

      data = (await response.json()) as LRCLIBSearchResponse;

View on GitHub (pinned to 1e2aac5706)

Solutions

  1. Update the synced-lyrics plugin in case the API contract changed
  2. Fall back to another lyrics provider when this error occurs
  3. If behind a captive portal/proxy, fix connectivity first
  4. Cache prior results so schema failures don't repeat for the same track

Example fix

// before
const lyrics = await lrclib.search(info);

// after
try { return await lrclib.search(info); }
catch (e) {
  if (!/Expected an array/.test(e.message)) throw e;
  return await megalobiz.search(info);
}
Defensive patterns

Strategy: fallback

Type guard

const isSearchArray = (d: unknown): d is LRCLIBSearchResponse => Array.isArray(d);

Try / catch

try { return await lrclib.search(info); } catch (e) { if (!/Expected an array/.test(e.message)) throw e; return await megalobiz.search(info); }

Prevention

When it happens

Trigger: LRCLib returning an error object instead of a list (e.g. {message: '...'} on bad parameters); an HTML page served with 200 (captive portal/proxy); API contract changes returning {results: [...]}; response.json() yielding null for an empty body.

Common situations: Captive portals and 'accepted connection' interstitials on public Wi-Fi; API version changes on lrclib.net; middleboxes injecting content; album_name query edge cases returning a structured error.

Related errors


AI-assisted analysis of pear-devs/pear-desktop@1e2aac5706 (2026-08-27). Data as JSON: /api/errors/dbb1af06d909afc0. Report an issue: GitHub.