{"record":{"id":"428b977c469634bf","repo":"DIYgod/RSSHub","slug":"invalid-user-id-type","errorCode":null,"errorMessage":"Invalid user ID type","messagePattern":"Invalid user ID type","errorType":"exception","errorClass":"TypeError","httpStatus":503,"severity":"error","filePath":"lib/routes/threads/utils.ts","lineNumber":90,"sourceCode":"                    return data[0];\n                }\n            } catch {\n                // Skip invalid JSON\n            }\n        }\n\n        throw new NotFoundError('User ID not found');\n    });\n\n    if (result) {\n        if (typeof result === 'string') {\n            return result;\n        }\n        if (typeof result === 'number') {\n            return result.toString();\n        }\n    }\n    throw new TypeError('Invalid user ID type');\n};\n\nconst hasMedia = (post) => post.image_versions2 || post.carousel_media || post.video_versions;\n\nconst buildMedia = (post) => {\n    let html = '';\n\n    if (post.carousel_media) {\n        for (const media of post.carousel_media) {\n            const firstImage = media.image_versions2?.candidates[0];\n            const firstVideo = media.video_versions?.[0];\n            html += firstVideo ? `<video controls autoplay loop poster=\"${firstImage.url}\"><source src=\"${firstVideo.url}\"/></video>` : `<img src=\"${firstImage.url}\"/>`;\n        }\n    } else {\n        const mainImage = post.image_versions2?.candidates?.[0];\n        const mainVideo = post.video_versions?.[0];\n        if (mainImage) {\n            html += mainVideo ? `<video controls autoplay loop poster=\"${mainImage.url}\"><source src=\"${mainVideo.url}\"/></video>` : `<img src=\"${mainImage.url}\"/>`;","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/threads/utils.ts#L72-L108","documentation":"Defensive TypeError from getUserId: the cached value for `threads:userId:{user}` is truthy but neither a string nor a number. The narrowing on lines 83-88 does not match, so execution falls through to the TypeError on line 90.","triggerScenarios":"A prior cache write stored an object/array (e.g. the raw JSONPath result array instead of `data[0]`), and on a subsequent read getUserId receives that non-primitive and cannot coerce it. The cache contract was violated by a code change or manual cache entry.","commonSituations":"Refactor that returned the whole JSONPath array instead of the first element; Redis cache polluted by an older incompatible build; the value was manually injected for testing.","solutions":["Flush the cache key `threads:userId:{user}` so it recomputes with current code.","Audit getUserId's return path to ensure it only ever stores a string/number primitive.","Coerce defensively before the type check: if Array.isArray(result) take result[0]."],"exampleFix":"// before\nif (result) {\n    if (typeof result === 'string') return result;\n    if (typeof result === 'number') return result.toString();\n}\nthrow new TypeError('Invalid user ID type');\n// after\nconst id = Array.isArray(result) ? result[0] : result;\nif (typeof id === 'string') return id;\nif (typeof id === 'number') return id.toString();\nthrow new TypeError('Invalid user ID type');","handlingStrategy":"validation","validationCode":"const cached = await cache.get(`threads:userId:${user}`);\nif (cached !== undefined && typeof cached !== 'string' && typeof cached !== 'number') {\n    await cache.set(`threads:userId:${user}`, undefined); // purge bad entry\n}","typeGuard":"const isPrimitiveId = (v: unknown): v is string | number => typeof v === 'string' || typeof v === 'number';","tryCatchPattern":"try { return await getUserId(user); }\ncatch (e) { if (e instanceof TypeError) { await cache.set(`threads:userId:${user}`, undefined); return getUserId(user); } throw e; }","preventionTips":["Never store arrays/objects in the userId cache slot","Purge cache after code changes that alter the stored shape","Add an assertion at the write site"],"tags":["cache","type-coercion","typeerror","threads","defensive"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}