jackwener/OpenCLI · error · CommandExecutionError

douyin hashtag search: API returned suggestions but none had

Error message

douyin hashtag search: API returned suggestions but none had a stable shape

What it means

Thrown by the douyin hashtag CLI 'search' action when the creator.douyin.com suggestion API returned data, but none of the suggestion entries could be normalized into the expected row shape ({name, id, view_count}). The library treats an all-unusable suggestion list as a failure rather than silently returning empty results.

Source

Thrown at clis/douyin/hashtag.js:77

        const action = kwargs.action;
        if (action === 'search') {
            const keyword = String(kwargs.keyword ?? '').trim();
            // challenge/search answers 200 with an empty body; the creator
            // studio composer reads suggestions from this endpoint instead,
            // which ignores count and returns a fixed-size list (#2205).
            const url = `https://creator.douyin.com/aweme/v1/search/challengesug/?keyword=${encodeURIComponent(keyword)}&source=challenge_create&aid=2906`;
            const res = await browserFetch(page, 'GET', url);
            const list = requireListField(res, 'sug_list', 'search');
            const rows = list.flatMap(c => {
                if (!isPlainObject(c) || typeof c.cha_name !== 'string' || !c.cha_name) return [];
                return [{
                    name: c.cha_name,
                    id: c.cid ?? '',
                    view_count: c.view_count ?? 0,
                }];
            });
            if (list.length > 0 && rows.length === 0) {
                throw new CommandExecutionError('douyin hashtag search: API returned suggestions but none had a stable shape');
            }
            return rows.slice(0, kwargs.limit);
        }
        if (action === 'suggest') {
            const cover = String(kwargs.cover ?? '').trim();
            const url = `https://creator.douyin.com/web/api/media/hashtag/rec/?cover_uri=${encodeURIComponent(cover)}&aid=1128`;
            const res = await browserFetch(page, 'GET', url);
            const list = requireListField(res, 'hashtag_list', 'suggest');
            return list.map(h => ({ name: h?.name ?? '', id: h?.id ?? '', view_count: h?.view_count ?? 0 }));
        }
        if (action === 'hot') {
            const kw = String(kwargs.keyword ?? '').trim();
            const url = `https://creator.douyin.com/aweme/v1/hotspot/recommend/?${kw ? `keyword=${encodeURIComponent(kw)}&` : ''}aid=1128`;
            const res = await browserFetch(page, 'GET', url);
            if (!isPlainObject(res)) {
                throw new CommandExecutionError('douyin hashtag hot: API returned malformed payload');
            }
            const hotspotList = res.hotspot_list;

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Re-run the command; transient payloads (captcha/anti-bot interstitials) often resolve on retry with a warm logged-in session.
  2. Verify the browser session is logged in to creator.douyin.com (cookies fresh, not expired).
  3. Check for a library update — Douyin frequently changes the response schema; a newer version may map the new fields.
  4. Inspect the raw API response manually (same URL in the logged-in browser) to confirm the schema and file an issue if it changed.
  5. Fall back to the 'hot' action or a different keyword if search suggestions are unavailable for the term.

Example fix

// before: assuming every suggestion has usable fields
const rows = list.map(c => [{ name: c.cha_name, id: c.cid ?? '', view_count: c.view_count ?? 0 }]);
// after: log the unexpected shape before throwing, to aid diagnosis
if (list.length > 0 && rows.length === 0) {
    console.error('raw suggestions:', JSON.stringify(list).slice(0, 500));
    throw new CommandExecutionError('douyin hashtag search: API returned suggestions but none had a stable shape');
}
Defensive patterns

Strategy: type-guard

Validate before calling

// filter suggestions to those with a usable shape before calling
const usable = raw.filter(c => c && typeof c.cha_name === 'string' && c.cid != null);
if (usable.length === 0) throw new Error('no well-formed douyin hashtag suggestions for this keyword');

Type guard

function isShapedSuggestion(c) {
  return c != null && typeof c === 'object' && typeof c.cha_name === 'string' && (typeof c.cid === 'string' || typeof c.cid === 'number');
}

Try / catch

try {
  const rows = await hashtag({ action: 'search', keyword: kw, limit: 10 });
} catch (e) {
  if (String(e.message).includes('none had a stable shape')) {
    // fall back to hot list or surface a schema-change warning
    console.warn('douyin suggestion schema changed; falling back');
  } else throw e;
}

Prevention

When it happens

Trigger: The suggestion endpoint responds with JSON where the candidate array exists but every entry lacks recognizable fields (e.g. cha_name/cid), or rows end up empty while the raw list is non-empty, so `list.length > 0 && rows.length === 0` becomes true at clis/douyin/hashtag.js:77.

Common situations: Douyin changed the suggestion response schema (renamed cha_name/cid); the endpoint returned an HTML login page or captcha JSON instead of suggestions; the API is behind an A/B test returning a new shape.

Related errors


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