jackwener/OpenCLI · error · CommandExecutionError

抖音封面申请上传地址响应缺少 UploadHost/StoreUri: ${JSON.stringify(applyRe

Error message

抖音封面申请上传地址响应缺少 UploadHost/StoreUri: ${JSON.stringify(applyRes).slice(0, 500)}

What it means

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.

Source

Thrown at clis/douyin/publish.js:176

        const committedVideo = await commitVideoUploadInner(tosUploadInfo, credentials);
        const videoId = committedVideo.video_id;
        process.stderr.write(`  上传已提交: ${videoId}\n`);
        coverWidth = committedVideo.width || coverWidth;
        coverHeight = committedVideo.height || coverHeight;
        if (!coverUri && committedVideo.poster_uri) {
            coverUri = committedVideo.poster_uri;
        }
        // ── Phase 4: Cover upload (optional) ────────────────────────────────
        if (kwargs.cover) {
            const resolvedCoverPath = path.resolve(kwargs.cover);
            // 4A: Apply ImageX upload
            const applyUrl = `${IMAGEX_BASE}/?Action=ApplyImageUpload&ServiceId=${IMAGEX_SERVICE_ID}&Version=2018-08-01&UploadNum=1`;
            const applyJs = `fetch(${JSON.stringify(applyUrl)}, { credentials: 'include' }).then(r => r.json())`;
            const applyRes = requireObjectEvaluateResult(await page.evaluate(applyJs), '抖音封面申请上传地址响应异常');
            throwIfImagexError('抖音封面申请上传地址', applyRes);
            const imgStoreInfo = applyRes.Result?.UploadAddress?.StoreInfos?.[0];
            if (!imgStoreInfo?.UploadHost || !imgStoreInfo?.StoreUri) {
                throw new CommandExecutionError(`抖音封面申请上传地址响应缺少 UploadHost/StoreUri: ${JSON.stringify(applyRes).slice(0, 500)}`);
            }
            const imgUploadUrl = `https://${imgStoreInfo.UploadHost}/${imgStoreInfo.StoreUri}`;
            // 4B: Upload image
            const coverStoreUri = await imagexUpload(resolvedCoverPath, {
                upload_url: imgUploadUrl,
                store_uri: imgStoreInfo.StoreUri,
            });
            // 4C: Commit ImageX upload
            const commitUrl = `${IMAGEX_BASE}/?Action=CommitImageUpload&ServiceId=${IMAGEX_SERVICE_ID}&Version=2018-08-01`;
            const commitBody = JSON.stringify({ SuccessObjKeys: [coverStoreUri] });
            const commitJs = `
        fetch(${JSON.stringify(commitUrl)}, {
          method: 'POST',
          credentials: 'include',
          headers: { 'Content-Type': 'application/json' },
          body: ${JSON.stringify(commitBody)}
        }).then(r => r.json())
      `;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the truncated response JSON in the message to see the actual payload shape
  2. Re-login to Douyin in the automation browser to refresh the session/cookies used for the ImageX call
  3. Retry later if it's a transient ImageX outage; compare against a manual browser upload
  4. If the API shape changed, update the parsing path (Result.UploadAddress.StoreInfos[0]) in publish.js

Example fix

// before (fragile assumption)
const imgStoreInfo = applyRes.Result?.UploadAddress?.StoreInfos?.[0];
// after (explicit diagnosis)
const imgStoreInfo = applyRes.Result?.UploadAddress?.StoreInfos?.[0];
if (!imgStoreInfo) {
  console.error('ImageX response:', JSON.stringify(applyRes));
  throw new Error('Refresh Douyin login and retry ApplyImageUpload');
}
Defensive patterns

Strategy: retry

Validate before calling

// No caller-side pre-check possible; validate session health instead
const loggedIn = await isLoggedIn(page); // probe a Douyin endpoint
if (!loggedIn) throw new Error('Refresh Douyin session before publishing');

Type guard

function hasUploadAddress(res) {
  const info = res?.Result?.UploadAddress?.StoreInfos?.[0];
  return typeof info?.UploadHost === 'string' && typeof info?.StoreUri === 'string' && info.UploadHost.length > 0;
}

Try / catch

try {
  await douyin.publish({ cover });
} catch (e) {
  if (e instanceof CommandExecutionError && /UploadHost\/StoreUri/.test(e.message)) {
    console.error('ImageX payload:', e.message); // inspect truncated JSON
    // refresh login, then retry once
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/600b07850886acc5. Report an issue: GitHub.