jackwener/OpenCLI · error · CommandExecutionError

填写抖音草稿表单失败: visibility-missing

Error message

填写抖音草稿表单失败: visibility-missing

What it means

fillDraftComposer clicks a <label> whose text contains the visibility label ('公开' / '好友可见' / '仅自己可见'). If no matching label is found it throws this CommandExecutionError with reason 'visibility-missing'. The visibility section of the composer was not present or its text changed.

Solutions

  1. Retry once in case the section rendered late
  2. Inspect the composer page and update the visibility label text mapping (VISIBILITY_LABELS in clis/douyin/draft.js:12) or switch from 'label' to a broader selector like '[role="radio"]'
  3. Use the default --visibility public which matches the most stable '公开' label

Example fix

// before
const visibility = Array.from(document.querySelectorAll('label')).find(
  (el) => (el.textContent || '').includes('好友可见')
);
// after: broader match
const visibility = Array.from(document.querySelectorAll('label,[role="radio"]')).find(
  (el) => /好友可见|好友圈/.test(el.textContent || '')
);
Defensive patterns

Strategy: try-catch

Try / catch

try {
  await opencli.douyin.draft({ video, title, visibility: 'friends' });
} catch (e) {
  if (e.message.includes('visibility-missing')) {
    // retry with default visibility 'public'
    await opencli.douyin.draft({ video, title, visibility: 'public' });
  }
}

Prevention

When it happens

Trigger: Douyin composer renders visibility options as radio buttons/custom components without a <label> containing the expected Chinese text; the visibility section is collapsed or virtualized away; the '好友可见' label copy changed (e.g. to '好友圈').

Common situations: Douyin UI update renaming visibility options; new accounts with different default visibility UI; label rendered only after scrolling the page.

Related errors


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

Appendix: source

Thrown at clis/douyin/draft.js:119

      document.execCommand('selectAll', false);
      document.execCommand('insertText', false, ${JSON.stringify(options.caption)});
      editor.dispatchEvent(new Event('input', { bubbles: true }));
      return true;
    }`));
        if (!captionOk) {
            throw new CommandExecutionError('填写抖音草稿表单失败: caption-editor-missing');
        }
    }
    const visibilityOk = (await page.evaluate(`() => {
    const visibility = Array.from(document.querySelectorAll('label')).find(
      (el) => (el.textContent || '').includes(${JSON.stringify(options.visibilityLabel)})
    );
    if (!(visibility instanceof HTMLElement)) return false;
    visibility.click();
    return true;
  }`));
    if (!visibilityOk) {
        throw new CommandExecutionError('填写抖音草稿表单失败: visibility-missing');
    }
}
/**
 * Switch the composer into custom-cover mode and expose the cover input with a
 * stable selector for CDP file injection.
 */
async function prepareCustomCoverInput(page) {
    let lastReason = 'cover-input-missing';
    const baselineCount = (await page.evaluate(`() => Array.from(document.querySelectorAll('input[type="file"]')).length`));
    for (let attempt = 0; attempt < COVER_INPUT_WAIT_ATTEMPTS; attempt += 1) {
        const result = (await page.evaluate(`() => {
      const coverLabel = Array.from(document.querySelectorAll('label')).find(
        (el) => (el.textContent || '').includes('上传新封面')
      );
      if (coverLabel instanceof HTMLElement) {
        coverLabel.click();
      }

View on GitHub (pinned to 49907e53dc)