jackwener/OpenCLI · error · CommandExecutionError

Failed to get user url_token from Zhihu

Error message

Failed to get user url_token from Zhihu

What it means

The collections command throws CommandExecutionError when the /api/v4/me?include=url_token response contains no url_token. url_token is required to enumerate the current user's collections, so a missing value is treated as a hard failure; the remedy hint points at login state.

Source

Thrown at clis/zhihu/collections.js:62

  domain: 'www.zhihu.com',
  strategy: Strategy.COOKIE,
  browser: true,
  args: [
    { name: 'limit', type: 'int', default: 20, help: '每页数量(最大 20)' },
  ],
  columns: ['rank', 'title', 'item_count', 'description', 'collection_id'],
  func: async (page, kwargs) => {
    const { limit = 20 } = kwargs;
    const requestedLimit = validatePositiveInt(limit, 'limit');

    // 先访问知乎主页建立 session
    await page.goto('https://www.zhihu.com');
    // 获取当前用户的 url_token
    const meData = await fetchJson(page, 'https://www.zhihu.com/api/v4/me?include=url_token', 'Zhihu user info request');

    const urlToken = meData.url_token;
    if (!urlToken) {
      throw new CommandExecutionError('Failed to get user url_token from Zhihu', 'Please ensure you are logged in.');
    }

    const collected = [];
    const seen = new Set();
    let totals = 0;
    let offset = 0;
    const pageLimit = Math.min(requestedLimit, 20);
    const maxPages = Math.ceil(requestedLimit / pageLimit) + 2;

    for (let pageIndex = 0; pageIndex < maxPages && collected.length < requestedLimit; pageIndex += 1) {
      const currentFetchLimit = Math.min(pageLimit, requestedLimit - collected.length);
      const url = `https://www.zhihu.com/api/v4/people/${urlToken}/collections?include=data%5B*%5D.updated_time&offset=${offset}&limit=${currentFetchLimit}`;
      const data = await fetchJson(page, url, 'Zhihu favorite collections request');
      const items = Array.isArray(data.data) ? data.data : [];
      const paging = data.paging || {};
      totals = Number(paging.totals || totals || 0);

      for (const item of items) {

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Ensure you are fully logged in to zhihu.com in the CLI browser session, then rerun
  2. Log out and back in to force a fresh authenticated session
  3. Update the CLI in case Zhihu changed the /api/v4/me response shape
  4. Inspect the raw me response with -v and report schema drift if url_token is gone while logged in

Example fix

// before
const urlToken = meData.url_token;
// after
const urlToken = meData?.url_token ?? meData?.data?.url_token; // tolerate shape change, still throw if absent
Defensive patterns

Strategy: validation

Validate before calling

const me = await fetch('https://www.zhihu.com/api/v4/me?include=url_token', { credentials: 'include' }).then(r => r.json());
if (!me?.url_token) throw new Error('no url_token: log in to zhihu.com before listing collections');

Type guard

function hasUrlToken(me) {
  return me != null && typeof me === 'object' && typeof me.url_token === 'string' && me.url_token.length > 0;
}

Try / catch

try {
  return await zhihuCollections();
} catch (e) {
  if (e.message.includes('url_token')) { await loginToZhihu(); return retryWithBackoff(zhihuCollections, 1); }
  throw e;
}

Prevention

When it happens

Trigger: The me endpoint returned 200 but with no url_token field — typically because the page is not actually logged in (Zhihu returns an anonymous profile shape), the payload shape changed, or a degraded/blocked response omitted the field.

Common situations: Half-logged-in state where the homepage loads but the API session is anonymous; Zhihu A/B payload changes removing/renaming url_token; scraping protection returning sanitized data to suspicious clients.

Related errors


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