jackwener/OpenCLI · critical · CommandExecutionError

获取抖音上传授权失败: ${JSON.stringify(result)}

Error message

获取抖音上传授权失败: ${JSON.stringify(result)}

What it means

getUploadAuthV5Credentials fetches Douyin's v5 upload auth endpoint in the page context and unwraps the JSON; this CommandExecutionError is thrown when the result is not a plain object (null, array, or non-object), meaning the fetch/evaluate did not return usable credentials JSON.

Source

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

  try {
    const raw = sessionToken.startsWith('STS2') ? sessionToken.slice(4) : sessionToken;
    const decoded = JSON.parse(Buffer.from(raw, 'base64').toString('utf8'));
    const policy = JSON.parse(decoded.PolicyString || '{}');
    const condition = policy?.Statement?.[0]?.Condition;
    if (typeof condition === 'string') {
      const parsedCondition = JSON.parse(condition);
      return parsedCondition.UserId || '';
    }
  } catch {
    return '';
  }
  return '';
}

export async function getUploadAuthV5Credentials(page) {
  const result = unwrapEvaluateResult(await page.evaluate(`fetch(${JSON.stringify(AUTH_V5_URL)}, { credentials: 'include' }).then(r => r.json())`));
  if (!result || Array.isArray(result) || typeof result !== 'object') {
    throw new CommandExecutionError(`获取抖音上传授权失败: ${JSON.stringify(result)}`);
  }
  if (result.status_code !== 0) {
    const message = result.status_msg ?? result.message ?? 'unknown error';
    if (result.status_code === 401 || result.status_code === 403 || /login|cookie|auth|captcha|verify|forbidden|permission|登录|登陆|权限|验证|验证码/i.test(String(message))) {
      throw new AuthRequiredError('creator.douyin.com', `获取抖音上传授权失败: ${message}`);
    }
    throw new CommandExecutionError(`获取抖音上传授权失败: ${JSON.stringify(result)}`);
  }
  if (!result.auth) {
    throw new CommandExecutionError(`获取抖音上传授权失败: ${JSON.stringify(result)}`);
  }
  let auth;
  try {
    auth = JSON.parse(result.auth);
  } catch (error) {
    throw new CommandExecutionError(`解析抖音上传授权失败: ${error instanceof Error ? error.message : String(error)}`);
  }
  if (!auth.AccessKeyID || !auth.SecretAccessKey || !auth.SessionToken) {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Ensure the page is logged in to creator.douyin.com and reload before fetching
  2. Navigate to creator.douyin.com explicitly so cookies/origin match AUTH_V5_URL
  3. Log JSON.stringify(result) from the error to inspect what was actually returned
  4. Check whether unwrapEvaluateResult is dropping the payload (evaluate serialization issues)

Example fix

// before
const creds = await getUploadAuthV5Credentials(anyPage);
// after
if (!page.url().includes('creator.douyin.com')) {
  await page.goto('https://creator.douyin.com');
}
const creds = await getUploadAuthV5Credentials(page);
Defensive patterns

Strategy: type-guard

Validate before calling

if (!page.url().includes('creator.douyin.com')) {
  await page.goto('https://creator.douyin.com', { waitUntil: 'domcontentloaded' });
}

Type guard

function isUploadAuthResult(r) {
  return !!r && typeof r === 'object' && !Array.isArray(r) && 'status_code' in r;
}

Try / catch

try { const creds = await getUploadAuthV5Credentials(page); }
catch (e) {
  if (String(e.message).startsWith('获取抖音上传授权失败')) {
    await page.goto('https://creator.douyin.com');
    const creds = await getUploadAuthV5Credentials(page);
  } else throw e;
}

Prevention

When it happens

Trigger: page.evaluate fetch returns null/undefined (network failure, JSON parse fail inside page), an array, or a non-object — e.g. the endpoint returned an HTML login page instead of JSON.

Common situations: Logged-out or expired session so the endpoint redirects; wrong page (not creator.douyin.com); evaluate sandbox blocked by CSP; endpoint schema change.

Related errors


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