{"record":{"id":"f9fd0c655cf1afae","repo":"DIYgod/RSSHub","slug":"tagresponse-msg","errorCode":null,"errorMessage":"${tagResponse.msg}","messagePattern":"\\$\\{tagResponse\\.msg\\}","errorType":"exception","errorClass":"Error","httpStatus":503,"severity":"error","filePath":"lib/routes/followin/tag.ts","lineNumber":56,"sourceCode":"        const { base_info: tagInfo } = queries.find((q) => q.queryKey[0] === '/tag/info/v2').state.data;\n        return tagInfo;\n    });\n\n    const gToken = await getGToken();\n    const bParam = getBParam(lang);\n    const { data: tagResponse } = await got.post(`${apiUrl}/feed/list/tag`, {\n        headers: {\n            'x-bparam': JSON.stringify(bParam),\n            'x-gtoken': gToken,\n        },\n        json: {\n            count: limit,\n            id: Number.parseInt(tagId),\n            type: 'tag_discussion_feed',\n        },\n    });\n    if (tagResponse.code !== 2000) {\n        throw new Error(tagResponse.msg);\n    }\n\n    const list = parseList(tagResponse.data.list.slice(0, limit), lang, buildId);\n    const items = await Promise.all(list.map((item) => parseItem(item)));\n\n    return {\n        title: `${tagInfo.name} - Followin`,\n        description: tagInfo.description,\n        link: `${baseUrl}/${lang}/tag/${tagId}`,\n        image: tagInfo.logo,\n        language: lang,\n        item: items,\n    };\n}\n","sourceCodeStart":38,"sourceCodeEnd":71,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/followin/tag.ts#L38-L71","documentation":"Followin's internal API at /feed/list/tag returns a JSON envelope where success is signaled by code === 2000. Any other code indicates the request was rejected (invalid/expired anti-bot tokens, bad tagId, rate limiting) and the API's msg field carries the human-readable reason, which is re-thrown verbatim. The endpoint is anti-crawler protected and depends on x-bparam and x-gtoken headers derived from the site's build session.","triggerScenarios":"Posting to apiUrl/feed/list/tag with an expired or stale gToken/bParam, a non-numeric or deleted tagId (Number.parseInt yields NaN), or when Followin's anti-bot layer flags the request. Also triggered if the tag exists but the feed type 'tag_discussion_feed' yields no data and the API signals it with a non-2000 code.","commonSituations":"Cached buildId/gToken going stale between the getBuildId/getGToken calls and the feed request; deploying without a reachable Followin frontend so token bootstrap fails silently; passing a tagId copied from a URL that is actually a slug rather than a numeric ID.","solutions":["Verify tagId is purely numeric (e.g. 177008) before calling the API, since Number.parseInt(tagId) silently produces NaN for slugs.","Ensure getBuildId() and getGToken() return fresh values — short-cache or re-fetch them if the feed call rejects with an auth-related msg.","Inspect the thrown tagResponse.msg: a token/anti-bot message means regenerate bParam/gToken; a 'not found' message means the tagId is wrong.","Add a retry that re-bootstraps gToken/bParam once before surfacing the error to the user."],"exampleFix":"// before\nconst gToken = await getGToken();\nconst bParam = getBParam(lang);\nconst { data: tagResponse } = await got.post(`${apiUrl}/feed/list/tag`, { ... });\nif (tagResponse.code !== 2000) {\n    throw new Error(tagResponse.msg);\n}\n\n// after\nif (!/^\\d+$/.test(tagId)) {\n    throw new InvalidParameterError(`tagId must be numeric, got: ${tagId}`);\n}\nlet tagResponse;\nfor (const attempt of [0, 1]) {\n    const gToken = await getGToken(attempt === 1);\n    const bParam = getBParam(lang);\n    ({ data: tagResponse } = await got.post(`${apiUrl}/feed/list/tag`, {\n        headers: { 'x-bparam': JSON.stringify(bParam), 'x-gtoken': gToken },\n        json: { count: limit, id: Number.parseInt(tagId), type: 'tag_discussion_feed' },\n    }));\n    if (tagResponse.code === 2000) break;\n}","handlingStrategy":"validation","validationCode":"if (!/^\\d+$/.test(tagId)) {\n  throw new InvalidParameterError('tagId must be numeric');\n}","typeGuard":"const isNumericId = (v: string): v is string => /^\\d+$/.test(v);","tryCatchPattern":"try {\n  const { data: tagResponse } = await got.post(`${apiUrl}/feed/list/tag`, { ... });\n} catch (e) {\n  // tagResponse.code !== 2000 surfaces as a thrown Error with the API msg\n  if (/token|auth|gtoken|bparam/i.test((e as Error).message)) {\n    // re-bootstrap tokens once and retry\n  }\n  throw e;\n}","preventionTips":["Validate tagId is numeric before Number.parseInt.","Keep gToken/bParam cache TTL short so they refresh before expiry.","Log tagResponse.code alongside msg to triage auth vs not-found."],"tags":["api","anti-crawler","authentication","validation"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}