{"record":{"id":"58963b1b623e7d20","repo":"jackwener/OpenCLI","slug":"suno-generate-returned-malformed-json-payload","errorCode":null,"errorMessage":"Suno generate returned malformed JSON payload.","messagePattern":"Suno generate returned malformed JSON payload\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/suno/utils.js","lineNumber":341,"sourceCode":"        let parsed = null;\n        try { parsed = JSON.parse(text); } catch {}\n        return { status: res.status, ok: res.ok, body: parsed, raw: parsed ? null : text.slice(0, 600) };\n    })()`));\n\n    if (!result || !result.ok) {\n        const status = result?.status || 'unknown';\n        const detail = result?.body?.detail || result?.raw || JSON.stringify(result?.body || {}).slice(0, 500);\n        if (status === 401 || status === 403) {\n            throw new AuthRequiredError(SUNO_DOMAIN, `Suno API rejected request (HTTP ${status}). Re-login on suno.com.`);\n        }\n        if (status === 402) {\n            throw new CommandExecutionError(`Suno API: insufficient credits (HTTP 402). ${detail}`);\n        }\n        throw new CommandExecutionError(`Suno generate failed (HTTP ${status}): ${detail}`);\n    }\n\n    if (!result.body || typeof result.body !== 'object' || Array.isArray(result.body)) {\n        throw new CommandExecutionError('Suno generate returned malformed JSON payload.');\n    }\n    const clips = result.body?.clips || [];\n    if (!clips.length) {\n        throw new EmptyResultError('suno generate', `Submission accepted but Suno returned no clip ids. Raw: ${JSON.stringify(result.body).slice(0, 300)}`);\n    }\n    return result.body;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Poll /api/feed/v3 (cookie auth, no Bearer).\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport async function pollSunoClips(page, clipIds, timeoutSeconds, deviceId, pollSeconds = 5, onProgress = null) {\n    const deadline = Date.now() + timeoutSeconds * 1000;\n    const targetSet = new Set(clipIds);\n    const idsJson = JSON.stringify(clipIds);\n\n    while (Date.now() < deadline) {","sourceCodeStart":323,"sourceCodeEnd":359,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/suno/utils.js#L323-L359","documentation":"submitSunoGeneration POSTs to Suno's /api/generate/v2-web/ endpoint inside the browser page. If the HTTP status is 2xx but the response body could not be parsed into a JSON object (null, non-object, or array), this CommandExecutionError is thrown. It guards the contract that the response is an object containing a clips array.","triggerScenarios":"The /api/generate/v2-web/ fetch returns res.ok true but JSON.parse fails (parsed stays null so result.body is null), or the body parses to an array or primitive instead of an object. Happens when Suno returns HTML/empty/error text with a 2xx status, or an API shape change.","commonSituations":"Suno deploys an API change returning a different envelope; Cloudflare or a proxy interposes a 2xx HTML page; session/CDN edge cases returning empty bodies; running against a staged/changed Suno endpoint; response truncated by network issues.","solutions":["Re-run the command once — transient proxy/CDN corruption can produce an unparseable body.","Re-authenticate with Suno (`suno auth login`) and retry, since a stale session can yield unexpected responses.","Check the Raw/detail output or log the response text to see what the server actually returned.","Update the opencli suno CLI in case Suno changed the /api/generate/v2-web/ response shape."],"exampleFix":"// before: blindly trusting body shape\nconst clips = result.body.clips;\n// after: guard before use\nif (!result.body || typeof result.body !== 'object' || Array.isArray(result.body)) {\n  throw new CommandExecutionError('Suno generate returned malformed JSON payload.');\n}\nconst clips = result.body?.clips || [];","handlingStrategy":"type-guard","validationCode":"// check last response shape before trusting it\nconst ok = result && result.ok && result.body && typeof result.body === 'object' && !Array.isArray(result.body);","typeGuard":"function isSunoGenerateBody(b) {\n  return !!b && typeof b === 'object' && !Array.isArray(b) && Array.isArray(b.clips);\n}","tryCatchPattern":"try {\n  const body = await submitSunoGeneration(page, payload);\n} catch (e) {\n  if (e instanceof CommandExecutionError && /malformed JSON payload/.test(e.message)) {\n    // log raw response and retry once after re-checking session\n  } else throw e;\n}","preventionTips":["Keep the Suno CLI updated for API shape changes","Re-authenticate before long-running generation jobs","Log raw response text on failure for diagnosis"],"tags":["api","json","suno","response-shape"],"backgroundTag":"malformed-json-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}