{"record":{"id":"53cb4e931c8f6aaa","repo":"jackwener/OpenCLI","slug":"suno-feed-api-returned-malformed-json-while-pollin","errorCode":null,"errorMessage":"Suno feed API returned malformed JSON while polling clips","messagePattern":"Suno feed API returned malformed JSON while polling clips","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/suno/utils.js","lineNumber":381,"sourceCode":"                headers: ${sunoHeadersJs(deviceId, { 'Content-Type': 'application/json' })},\n                body: JSON.stringify({ clip_ids: ${idsJson} }),\n            });\n            const body = await res.json().catch(() => null);\n            return { status: res.status, body };\n        })()`));\n\n        if (!result) {\n            await page.wait(pollSeconds);\n            continue;\n        }\n        if (result.status === 401 || result.status === 403) {\n            throw new AuthRequiredError(SUNO_DOMAIN, `Suno feed API rejected (HTTP ${result.status}). Re-login.`);\n        }\n        if (result.status < 200 || result.status >= 300) {\n            throw new CommandExecutionError(`Suno feed API failed while polling clips (HTTP ${result.status || '?'})`);\n        }\n        if (!result.body || typeof result.body !== 'object' || Array.isArray(result.body)) {\n            throw new CommandExecutionError('Suno feed API returned malformed JSON while polling clips');\n        }\n\n        const allClips = result.body.clips || [];\n        if (!Array.isArray(allClips)) {\n            throw new CommandExecutionError('Suno feed API returned malformed clips payload');\n        }\n        const ourClips = allClips.filter(c => targetSet.has(c.id));\n        const finished = ourClips.filter(c => c.status === 'complete' || c.status === 'error');\n\n        if (typeof onProgress === 'function') {\n            onProgress({ total: clipIds.length, done: finished.length, statuses: ourClips.map(c => `${c.id.slice(0,8)}:${c.status}`) });\n        }\n\n        if (finished.length === clipIds.length) return ourClips;\n        await page.wait(pollSeconds);\n    }\n\n    throw new TimeoutError(`Suno generation did not complete within ${timeoutSeconds}s. Try --timeout <higher>.`);","sourceCodeStart":363,"sourceCodeEnd":399,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/suno/utils.js#L363-L399","documentation":"pollSunoClips expects /api/feed/v3 to return a JSON object. If res.json() fails (body null), or the parsed body is an array/primitive, this CommandExecutionError is thrown because the poll loop cannot read body.clips.","triggerScenarios":"The feed endpoint returns 2xx with a non-JSON body (HTML error page, empty body), so res.json().catch(() => null) yields null, or the body parses to a non-object value.","commonSituations":"Suno serving a maintenance/Cloudflare challenge page with 2xx; truncated response; API version drift on /api/feed/v3; anti-bot interstitials when the session looks suspicious.","solutions":["Re-run after a pause — challenge pages and truncation are often transient.","Re-login to Suno to clear anti-bot/edge states, then retry.","Inspect the raw response (add logging of res.text()) to identify what was returned.","Update the CLI if Suno changed the /api/feed/v3 response format."],"exampleFix":"// before\nconst body = await res.json().catch(() => null);\n// after: capture raw text for diagnostics when JSON parse fails\nconst text = await res.text();\nlet body = null;\ntry { body = JSON.parse(text); } catch {}\nreturn { status: res.status, body, raw: body ? null : text.slice(0, 300) };","handlingStrategy":"type-guard","validationCode":"// after fetch, check parseability before consuming\nconst text = await res.text();\nlet body = null; try { body = JSON.parse(text); } catch {}\nif (!body || typeof body !== 'object' || Array.isArray(body)) throw new Error('feed returned non-JSON body: ' + text.slice(0, 200));","typeGuard":"function isFeedBody(b) {\n  return !!b && typeof b === 'object' && !Array.isArray(b);\n}","tryCatchPattern":"try {\n  const clips = await pollSunoClips(page, ids, timeout, deviceId);\n} catch (e) {\n  if (e instanceof CommandExecutionError && /malformed JSON/.test(e.message)) {\n    // likely a challenge/maintenance page; pause and retry\n    await sleep(20000);\n    return pollSunoClips(page, ids, timeout, deviceId);\n  }\n  throw e;\n}","preventionTips":["Pause polling during suspected Suno maintenance windows","Re-login if challenge pages appear repeatedly","Capture raw response text for diagnostics"],"tags":["api","json","suno","polling"],"backgroundTag":"malformed-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}