jackwener/OpenCLI · error · CommandExecutionError

Zhihu answer navigation changed identity for answer ${target

Error message

Zhihu answer navigation changed identity for answer ${target.answerId}

What it means

After page.goto, extractAnswer reads the current URL and re-parses it with parseAnswerTarget to verify Zhihu actually landed on the requested answer. If the parsed URL lacks an answerId, has a different answerId, has no questionId, or the questionId mismatches the requested target, this error is thrown. It guards against Zhihu silently redirecting to a login page, captcha, question page, or a different answer, which would otherwise produce scraped data for the wrong content.

Source

Thrown at clis/zhihu/download-helpers.js:159

    });
    return requireArticle(raw);
}

export async function extractAnswer(page, target) {
    try {
        await page.goto(`https://www.zhihu.com/answer/${target.answerId}`);
    }
    catch (error) {
        throw new CommandExecutionError(
            `Failed to open Zhihu answer ${target.answerId}: ${error instanceof Error ? error.message : String(error)}`,
            'Open the answer URL in Chrome and retry after the page is reachable.',
        );
    }
    const currentUrl = typeof page.getCurrentUrl === 'function' ? await page.getCurrentUrl().catch(() => '') : '';
    const currentTarget = parseAnswerTarget(currentUrl);
    if (!currentTarget || currentTarget.answerId !== target.answerId || !currentTarget.questionId
        || (target.questionId && currentTarget.questionId && target.questionId !== currentTarget.questionId)) {
        throw new CommandExecutionError(`Zhihu answer navigation changed identity for answer ${target.answerId}`);
    }

    const apiUrl = `https://www.zhihu.com/api/v4/answers/${target.answerId}?include=content,author,created_time,question,url`;
    const normalize = `(${normalizeContentImages.toString()})`;
    const raw = await page.evaluate(`
      (async () => {
        let response;
        try {
          response = await fetch(${JSON.stringify(apiUrl)}, { credentials: 'include' });
        } catch (error) {
          return { fetchError: error instanceof Error ? error.message : String(error) };
        }
        let payload;
        try {
          payload = await response.json();
        } catch {
          return { status: response.status, malformed: true };
        }

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Log in to www.zhihu.com in the connected Chrome profile so navigation is not redirected to sign-in.
  2. Open the answer URL manually and check whether it was deleted or redirects; use the current canonical URL to rebuild the target.
  3. Print/inspect page.getCurrentUrl() after the failure to see where the navigation actually landed.
  4. Retry later if a captcha/risk-control wall redirected you; verify the answerId/questionId target values are correct and current.

Example fix

// before
await extractAnswer(page, { answerId: oldId, questionId: staleQuestionId });
// after
// confirm the canonical URL first, then use matching ids
const canonical = 'https://www.zhihu.com/question/999/answer/12345';
const t = parseDownloadTarget(canonical); // { kind:'answer', answerId:'12345', questionId:'999' }
await extractAnswer(page, t);
Defensive patterns

Strategy: validation

Validate before calling

const target = parseDownloadTarget(input);
if (!target || target.kind !== 'answer' || !/^\d+$/.test(target.answerId)) {
  throw new Error('invalid answer target');
}
// Prefer a fresh canonical URL from Zhihu over a stale stored questionId

Type guard

function isAnswerTarget(t) {
  return !!t && typeof t === 'object' && t.kind === 'answer'
    && typeof t.answerId === 'string' && /^\d+$/.test(t.answerId)
    && (t.questionId === undefined || /^\d*$/.test(t.questionId || ''));
}

Try / catch

try {
  await extractAnswer(page, target);
} catch (err) {
  if (String(err.message).includes('changed identity')) {
    // likely login/captcha redirect or deleted answer — inspect actual URL
    const landed = await page.getCurrentUrl();
    console.error('redirected to:', landed, '- log in or refresh the target');
  }
  throw err;
}

Prevention

When it happens

Trigger: Calling extractAnswer when, after navigation, the final URL is not /question/<qid>/answer/<aid>: redirect to sign-in/captcha wall, answer deleted or collapsed redirecting to the question page, Zhihu redirecting to a normalized/different answer URL, or the bridge driver not exposing getCurrentUrl (empty string fails the check).

Common situations: Not logged in and Zhihu forces a login redirect; anti-bot risk control redirects to a verification page; the answer was deleted so Zhihu bounces to the question; a mobile-app-style link redirects to a different canonical answer; target.questionId came from an outdated URL that no longer matches after Zhihu re-mapped the question.

Related errors


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