jackwener/OpenCLI · error · CommandExecutionError

申请抖音上传地址失败,非 JSON 响应: HTTP ${res.status} ${text.slice(0, 300

Error message

申请抖音上传地址失败,非 JSON 响应: HTTP ${res.status} ${text.slice(0, 300)}

What it means

applyVideoUploadInner calls the VOD ApplyUploadInner endpoint and expects a JSON body; this error fires when JSON.parse fails on the HTTP response text. The HTTP status and the first 300 characters of the raw body are included so you can see what the server actually returned (HTML error page, empty body, gateway text, etc.).

Source

Thrown at clis/douyin/_shared/vod-upload.js:147

}

export async function applyVideoUploadInner(fileSize, credentials) {
  const params = new URLSearchParams({
    Action: 'ApplyUploadInner',
    Version: '2020-11-19',
    SpaceName: VOD_SPACE_NAME,
    FileType: 'video',
    IsInner: '1',
    FileSize: String(fileSize),
  });
  const url = `${VOD_UPLOAD_HOST}?${params.toString()}`;
  const res = await fetch(url, { headers: computeAws4Headers(url, credentials), signal: AbortSignal.timeout(30000) });
  const text = await res.text();
  let payload;
  try {
    payload = JSON.parse(text);
  } catch {
    throw new CommandExecutionError(`申请抖音上传地址失败,非 JSON 响应: HTTP ${res.status} ${text.slice(0, 300)}`);
  }
  const error = payload?.ResponseMetadata?.Error;
  if (!res.ok || error) {
    throw new CommandExecutionError(`申请抖音上传地址失败: HTTP ${res.status} ${JSON.stringify(error ?? payload)}`);
  }
  const uploadNode = payload?.Result?.InnerUploadAddress?.UploadNodes?.[0];
  const storeInfo = uploadNode?.StoreInfos?.[0];
  const videoId = payload?.Result?.Vid || uploadNode?.Vid;
  const sessionKey = uploadNode?.SessionKey ?? storeInfo?.SessionKey ?? payload?.Result?.SessionKey;
  if (!uploadNode?.UploadHost || !storeInfo?.StoreUri || !storeInfo?.Auth || !videoId || !sessionKey) {
    throw new CommandExecutionError(`申请抖音上传地址响应缺少必要字段: ${JSON.stringify(payload).slice(0, 500)}`);
  }
  return {
    video_id: videoId,
    tos_upload_url: `https://${uploadNode.UploadHost}/${storeInfo.StoreUri}`,
    auth: storeInfo.Auth,
    session_key: sessionKey,
    upload_header: uploadNode.UploadHeader ?? {},

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Inspect the body snippet in the message — HTML indicates a WAF/login/CAPTCHA page; re-login or complete verification and retry.
  2. Refresh the STS credentials via getUploadAuthV5Credentials and retry (expired tokens often trigger edge errors).
  3. Retry with backoff if the status is 5xx — could be a transient gateway/CDN issue.
  4. Disable VPN/proxy or switch networks to rule out middlebox interference.
  5. Verify the request URL/headers are current in case Douyin moved the endpoint.

Example fix

// before
const res = await fetch(url, { headers: computeAws4Headers(url, credentials) });
const text = await res.text();
payload = JSON.parse(text); // throws generic SyntaxError
// after
const res = await fetch(url, { headers: computeAws4Headers(url, credentials) });
const text = await res.text();
try {
  payload = JSON.parse(text);
} catch {
  if (/<html|<HTML/.test(text)) await refreshLoginAndRetry(); // HTML => re-auth path
  throw new CommandExecutionError(`申请抖音上传地址失败,非 JSON 响应: HTTP ${res.status} ${text.slice(0, 300)}`);
}
Defensive patterns

Strategy: retry

Validate before calling

// sanity-check credentials freshness before the call
if (credentials.expired_time && credentials.expired_time * 1000 < Date.now()) {
  throw new Error('STS 凭证已过期,请先刷新');
}

Type guard

null

Try / catch

try {
  return await applyVideoUploadInner(fileSize, creds);
} catch (e) {
  if (/非 JSON 响应/.test(e.message) && /HTTP 5\d\d/.test(e.message)) {
    await sleep(3000);
    return applyVideoUploadInner(fileSize, creds); // transient gateway failure
  }
  if (/<html/i.test(e.message)) throw new AuthRequiredError('creator.douyin.com', e.message);
  throw e;
}

Prevention

When it happens

Trigger: The ApplyUploadInner fetch returns a non-JSON body: an HTML/gateway error page (502/504 from CDN/WAF), an empty response, a CAPTCHA/risk-control HTML page, or a redirect to a login page.

Common situations: Expired STS credentials causing the edge to return an HTML error; WAF/risk-control intercepting the request; network middleboxes (corporate proxy, VPN) rewriting responses; Douyin edge outage returning non-JSON 5xx bodies.

Related errors


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