{"record":{"id":"dbb1af06d909afc0","repo":"pear-devs/pear-desktop","slug":"expected-an-array-instead-got-typeof-data","errorCode":null,"errorMessage":"Expected an array, instead got ${typeof data}","messagePattern":"Expected an array, instead got (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/plugins/synced-lyrics/providers/LRCLib.ts","lineNumber":39,"sourceCode":"      artist_name: artist,\n      track_name: title,\n    });\n\n    query.set('album_name', album!);\n    if (query.get('album_name') === 'undefined') {\n      query.delete('album_name');\n    }\n\n    let url = `${this.baseUrl}/api/search?${query.toString()}`;\n    let response = await fetch(url);\n\n    if (!response.ok) {\n      throw new Error(`bad HTTPStatus(${response.statusText})`);\n    }\n\n    let data = (await response.json()) as LRCLIBSearchResponse;\n    if (!data || !Array.isArray(data)) {\n      throw new Error(`Expected an array, instead got ${typeof data}`);\n    }\n\n    if (data.length === 0) {\n      if (!config()?.showLyricsEvenIfInexact) {\n        return null;\n      }\n\n      // Try to search with the alternative title (original language)\n      const trackName = alternativeTitle || title;\n      query = new URLSearchParams({ q: `${trackName}` });\n      url = `${this.baseUrl}/api/search?${query.toString()}`;\n\n      response = await fetch(url);\n      if (!response.ok) {\n        throw new Error(`bad HTTPStatus(${response.statusText})`);\n      }\n\n      data = (await response.json()) as LRCLIBSearchResponse;","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/pear-devs/pear-desktop/blob/1e2aac5706c870c93ed74ba8727ae8390d3b0fa5/src/plugins/synced-lyrics/providers/LRCLib.ts#L21-L57","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Update the synced-lyrics plugin in case the API contract changed","Fall back to another lyrics provider when this error occurs","If behind a captive portal/proxy, fix connectivity first","Cache prior results so schema failures don't repeat for the same track"],"exampleFix":"// before\nconst lyrics = await lrclib.search(info);\n\n// after\ntry { return await lrclib.search(info); }\ncatch (e) {\n  if (!/Expected an array/.test(e.message)) throw e;\n  return await megalobiz.search(info);\n}","handlingStrategy":"fallback","validationCode":null,"typeGuard":"const isSearchArray = (d: unknown): d is LRCLIBSearchResponse => Array.isArray(d);","tryCatchPattern":"try { return await lrclib.search(info); } catch (e) { if (!/Expected an array/.test(e.message)) throw e; return await megalobiz.search(info); }","preventionTips":["Keep the synced-lyrics plugin updated for LRCLib API changes","Detect captive portals (any HTTP fetch returning HTML) before lyric lookups","Always have a second lyrics provider configured as fallback"],"tags":["lyrics","lrclib","json","schema","api-contract"],"backgroundTag":"unexpected-response-schema","analyzedSha":"1e2aac5706c870c93ed74ba8727ae8390d3b0fa5","analyzedAt":"2026-08-27T20:01:08.614Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}