{"record":{"id":"600b07850886acc5","repo":"jackwener/OpenCLI","slug":"uploadhost-storeuri-json-stringi","errorCode":null,"errorMessage":"抖音封面申请上传地址响应缺少 UploadHost/StoreUri: ${JSON.stringify(applyRes).slice(0, 500)}","messagePattern":"抖音封面申请上传地址响应缺少 UploadHost/StoreUri: (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/douyin/publish.js","lineNumber":176,"sourceCode":"        const committedVideo = await commitVideoUploadInner(tosUploadInfo, credentials);\n        const videoId = committedVideo.video_id;\n        process.stderr.write(`  上传已提交: ${videoId}\\n`);\n        coverWidth = committedVideo.width || coverWidth;\n        coverHeight = committedVideo.height || coverHeight;\n        if (!coverUri && committedVideo.poster_uri) {\n            coverUri = committedVideo.poster_uri;\n        }\n        // ── Phase 4: Cover upload (optional) ────────────────────────────────\n        if (kwargs.cover) {\n            const resolvedCoverPath = path.resolve(kwargs.cover);\n            // 4A: Apply ImageX upload\n            const applyUrl = `${IMAGEX_BASE}/?Action=ApplyImageUpload&ServiceId=${IMAGEX_SERVICE_ID}&Version=2018-08-01&UploadNum=1`;\n            const applyJs = `fetch(${JSON.stringify(applyUrl)}, { credentials: 'include' }).then(r => r.json())`;\n            const applyRes = requireObjectEvaluateResult(await page.evaluate(applyJs), '抖音封面申请上传地址响应异常');\n            throwIfImagexError('抖音封面申请上传地址', applyRes);\n            const imgStoreInfo = applyRes.Result?.UploadAddress?.StoreInfos?.[0];\n            if (!imgStoreInfo?.UploadHost || !imgStoreInfo?.StoreUri) {\n                throw new CommandExecutionError(`抖音封面申请上传地址响应缺少 UploadHost/StoreUri: ${JSON.stringify(applyRes).slice(0, 500)}`);\n            }\n            const imgUploadUrl = `https://${imgStoreInfo.UploadHost}/${imgStoreInfo.StoreUri}`;\n            // 4B: Upload image\n            const coverStoreUri = await imagexUpload(resolvedCoverPath, {\n                upload_url: imgUploadUrl,\n                store_uri: imgStoreInfo.StoreUri,\n            });\n            // 4C: Commit ImageX upload\n            const commitUrl = `${IMAGEX_BASE}/?Action=CommitImageUpload&ServiceId=${IMAGEX_SERVICE_ID}&Version=2018-08-01`;\n            const commitBody = JSON.stringify({ SuccessObjKeys: [coverStoreUri] });\n            const commitJs = `\n        fetch(${JSON.stringify(commitUrl)}, {\n          method: 'POST',\n          credentials: 'include',\n          headers: { 'Content-Type': 'application/json' },\n          body: ${JSON.stringify(commitBody)}\n        }).then(r => r.json())\n      `;","sourceCodeStart":158,"sourceCodeEnd":194,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/douyin/publish.js#L158-L194","documentation":"The cover upload flow calls Douyin's ImageX ApplyImageUpload endpoint inside the page to get an upload host and store URI. If the response's Result.UploadAddress.StoreInfos[0] lacks UploadHost or StoreUri, publish.js throws this CommandExecutionError including the first 500 chars of the raw response for debugging. It indicates the ImageX service returned an unexpected/malformed payload rather than usable upload credentials.","triggerScenarios":"page.evaluate of the ApplyImageUpload fetch returns JSON whose Result.UploadAddress.StoreInfos is empty or its first entry is missing UploadHost/StoreUri — typically after throwIfImagexError found no explicit error code in the response.","commonSituations":"Expired or insufficient Douyin login session causing ImageX to return an empty/soft-error payload; ImageX API contract changed (new field layout or ServiceId no longer valid); regional/service outage returning partial JSON; response shaped as an error object not recognized by throwIfImagexError.","solutions":["Inspect the truncated response JSON in the message to see the actual payload shape","Re-login to Douyin in the automation browser to refresh the session/cookies used for the ImageX call","Retry later if it's a transient ImageX outage; compare against a manual browser upload","If the API shape changed, update the parsing path (Result.UploadAddress.StoreInfos[0]) in publish.js"],"exampleFix":"// before (fragile assumption)\nconst imgStoreInfo = applyRes.Result?.UploadAddress?.StoreInfos?.[0];\n// after (explicit diagnosis)\nconst imgStoreInfo = applyRes.Result?.UploadAddress?.StoreInfos?.[0];\nif (!imgStoreInfo) {\n  console.error('ImageX response:', JSON.stringify(applyRes));\n  throw new Error('Refresh Douyin login and retry ApplyImageUpload');\n}","handlingStrategy":"retry","validationCode":"// No caller-side pre-check possible; validate session health instead\nconst loggedIn = await isLoggedIn(page); // probe a Douyin endpoint\nif (!loggedIn) throw new Error('Refresh Douyin session before publishing');","typeGuard":"function hasUploadAddress(res) {\n  const info = res?.Result?.UploadAddress?.StoreInfos?.[0];\n  return typeof info?.UploadHost === 'string' && typeof info?.StoreUri === 'string' && info.UploadHost.length > 0;\n}","tryCatchPattern":"try {\n  await douyin.publish({ cover });\n} catch (e) {\n  if (e instanceof CommandExecutionError && /UploadHost\\/StoreUri/.test(e.message)) {\n    console.error('ImageX payload:', e.message); // inspect truncated JSON\n    // refresh login, then retry once\n  } else throw e;\n}","preventionTips":["Keep the automation browser's Douyin session fresh","Retry transient ImageX failures with backoff","Log full ImageX responses when debugging payload-shape drift","Track Douyin/ImageX API changelog for field renames"],"tags":["network","api-response","imagex","upload"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}