jackwener/OpenCLI · error · CommandExecutionError

Bilibili relation query returned a malformed attribute

Error message

Bilibili relation query returned a malformed attribute

What it means

fetchRelationAttribute expects payload.data.attribute to be a number (Bilibili's relation attribute byte). After requireOkPayload passes (code===0), a missing or non-numeric attribute means the response shape is not what the library supports, so it throws this CommandExecutionError to prevent acting on garbage data.

Solutions

  1. Log the raw payload when this fires to confirm the shape; if Bilibili changed the schema, update fetchRelationAttribute.
  2. Retry — transient anomalous responses can occur; waitForRelation callers can tolerate via predicate retries.
  3. Skip accounts whose payload lacks attribute in batch automation.
  4. Pin/verify no proxy or response-mocking middleware is mangling the body.

Example fix

// before
const attribute = payload?.data?.attribute;
if (typeof attribute !== 'number') throw new CommandExecutionError('Bilibili relation query returned a malformed attribute');
// after
const attribute = payload?.data?.attribute;
if (typeof attribute !== 'number') {
  console.error('relation payload:', JSON.stringify(payload));
  throw new CommandExecutionError('Bilibili relation query returned a malformed attribute');
}
Defensive patterns

Strategy: type-guard

Validate before calling

const payload = await fetchJson(page, `https://api.bilibili.com/x/relation?fid=${mid}`);
if (payload?.code !== 0 || typeof payload?.data?.attribute !== 'number') {
  throw new Error(`unexpected relation payload for ${mid}: ${JSON.stringify(payload).slice(0, 200)}`);
}

Type guard

function hasNumericAttribute(p) {
  return typeof p?.data?.attribute === 'number';
}

Try / catch

try {
  const attr = await fetchRelationAttribute(page, mid);
} catch (e) {
  if (String(e.message).includes('malformed attribute')) {
    console.warn(`relation data unavailable for ${mid}; skipping`);
    return null;
  }
  throw e;
}

Prevention

When it happens

Trigger: Bilibili returning code 0 but data lacking attribute (e.g. special account types, API schema changes, soft-deactivated accounts), or an interceptor/proxy rewriting the JSON body.

Common situations: Bilibili A/B-testing new relation API responses, querying suspended/deactivated users whose data payload omits attribute, or a mock/test server with an incomplete response.

Understand the failure class

Related errors


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

Appendix: source

Thrown at clis/bilibili/relation.js:27

    if (!trimmed) return '';
    const candidate = /^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
    let parsed;
    try {
        parsed = new URL(candidate);
    } catch {
        return '';
    }
    if (parsed.hostname.toLowerCase() !== 'space.bilibili.com') return '';
    const match = parsed.pathname.match(/^\/(\d+)\/?$/);
    return match ? match[1] : '';
}

export async function fetchRelationAttribute(page, mid) {
    const payload = await fetchJson(page, `https://api.bilibili.com/x/relation?fid=${mid}`);
    requireOkPayload(payload, 'relation query');
    const attribute = payload?.data?.attribute;
    if (typeof attribute !== 'number') {
        throw new CommandExecutionError('Bilibili relation query returned a malformed attribute');
    }
    return attribute;
}

export async function waitForRelation(page, mid, predicate, expectedLabel) {
    const deadline = Date.now() + RELATION_VERIFY_TIMEOUT_MS;
    let lastAttribute;
    while (Date.now() <= deadline) {
        lastAttribute = await fetchRelationAttribute(page, mid);
        if (predicate(lastAttribute)) return lastAttribute;
        if (typeof page.wait !== 'function') break;
        await page.wait({ time: RELATION_VERIFY_POLL_MS / 1000 });
    }
    throw new CommandExecutionError(
        `Bilibili relation modify did not verify ${expectedLabel}; last attribute=${lastAttribute}`,
    );
}

View on GitHub (pinned to 49907e53dc)