{"record":{"id":"a91eaf5b49259f27","repo":"jackwener/OpenCLI","slug":"bilibili-relation-modify-did-not-verify-expected","errorCode":null,"errorMessage":"Bilibili relation modify did not verify ${expectedLabel}; last attribute=${lastAttribute}","messagePattern":"Bilibili relation modify did not verify (.+?); last attribute=(.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/bilibili/relation.js","lineNumber":41,"sourceCode":"    const payload = await fetchJson(page, `https://api.bilibili.com/x/relation?fid=${mid}`);\n    requireOkPayload(payload, 'relation query');\n    const attribute = payload?.data?.attribute;\n    if (typeof attribute !== 'number') {\n        throw new CommandExecutionError('Bilibili relation query returned a malformed attribute');\n    }\n    return attribute;\n}\n\nexport async function waitForRelation(page, mid, predicate, expectedLabel) {\n    const deadline = Date.now() + RELATION_VERIFY_TIMEOUT_MS;\n    let lastAttribute;\n    while (Date.now() <= deadline) {\n        lastAttribute = await fetchRelationAttribute(page, mid);\n        if (predicate(lastAttribute)) return lastAttribute;\n        if (typeof page.wait !== 'function') break;\n        await page.wait({ time: RELATION_VERIFY_POLL_MS / 1000 });\n    }\n    throw new CommandExecutionError(\n        `Bilibili relation modify did not verify ${expectedLabel}; last attribute=${lastAttribute}`,\n    );\n}\n","sourceCodeStart":23,"sourceCodeEnd":45,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/bilibili/relation.js#L23-L45","documentation":"waitForRelation polls Bilibili's relation API (attribute = your relationship flag with a user, e.g. follow/block states) after a follow/unfollow/block mutation, applying a predicate with a 5s deadline. If the relation attribute never satisfies the predicate within the timeout window (polling every 500ms), it throws CommandExecutionError with the last observed attribute so you can see what state the API actually reports. This means the modify command ran, but the change was not (yet) reflected server-side.","triggerScenarios":"Calling a bilibili relation command (follow/unfollow/block) and then the relation API keeps returning an attribute that fails the predicate for 5+ seconds; also triggered when page.wait is not a function — the loop breaks after a single poll and throws immediately even though the server may just need more time.","commonSituations":"Bilibili server-side lag/replication delay after a follow; the mutation silently failed (invalid mid, risk-control shadow rejection, need 2FA for some operations); an already-followed user being unfollowed but attribute mapping differs; running in an environment where the page object lacks wait() so only one poll happens.","solutions":["Retry the command after a few seconds — often the attribute eventually flips; server lag is the most common cause","Check the lastAttribute value in the message against Bilibili's relation attribute enum to see what state you are actually in (e.g. already-followed vs not-followed)","Verify the mid is correct and the account is not risk-controlled/limited (try the same action manually in a browser)","Ensure the automation environment provides a real browser page with a wait() method so all polls within the 5s window run","If operations legitimately take longer, increase RELATION_VERIFY_TIMEOUT_MS or wrap the command in an application-level retry with backoff"],"exampleFix":"// before\nawait waitForRelation(page, mid, (a) => a === 2, 'followed');\n// after\nfor (let attempt = 1; attempt <= 3; attempt++) {\n  try { await waitForRelation(page, mid, (a) => a === 2, 'followed'); break; }\n  catch (e) { if (attempt === 3) throw e; await new Promise(r => setTimeout(r, 2000)); }\n}","handlingStrategy":"retry","validationCode":"const attr = await fetchRelationAttribute(page, mid);\nif (!predicate(attr)) console.warn('relation not yet in expected state:', attr);","typeGuard":null,"tryCatchPattern":"try {\n  await waitForRelation(page, mid, (a) => a === 2, 'followed');\n} catch (e) {\n  if (String(e.message).includes('did not verify')) {\n    // inspect lastAttribute, back off and retry the whole modify+verify cycle\n  } else throw e;\n}","preventionTips":["Wrap relation mutations in an application-level retry with exponential backoff","Always run against a page object that implements wait() so full polling happens","Log the lastAttribute and compare against Bilibili's relation attribute enum before retrying","Avoid rapid successive follow/unfollow cycles that trip risk control"],"tags":["network","timeout","bilibili","polling"],"backgroundTag":"operation-verification-timeout","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}