{"record":{"id":"6d501740b73d0344","repo":"DIYgod/RSSHub","slug":"unable-to-decode-the-locals-server-response","errorCode":null,"errorMessage":"Unable to decode the Locals server response.","messagePattern":"Unable to decode the Locals server response\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/routes/locals/feed.ts","lineNumber":223,"sourceCode":"function parseUnknownResponse<T>(body: string, instanceId: string): T {\n    const sandbox = {\n        $R: {},\n        self: {},\n    } as {\n        $R: Record<string, unknown[]>;\n        self: {\n            $R?: Record<string, unknown[]>;\n        };\n    };\n\n    sandbox.self.$R = sandbox.$R;\n\n    vm.createContext(sandbox);\n    vm.runInContext(body, sandbox, { timeout: 5000 });\n\n    const value = sandbox.self.$R?.[instanceId]?.[0] as T | undefined;\n    if (!value) {\n        throw new Error('Unable to decode the Locals server response.');\n    }\n\n    return value;\n}\n\nfunction extractCommunityInfo(html: string, community: string) {\n    const markerIndex = html.indexOf(`hashtag:\"${community}\"`);\n    if (markerIndex === -1) {\n        throw new Error('Unable to resolve the Locals community metadata.');\n    }\n\n    const context = html.slice(Math.max(0, markerIndex - 200), markerIndex + 1200);\n    const idMatch = context.match(/id:(\\d+)/);\n    const titleMatch = context.match(/title:\"([^\"]+)\"/);\n    const descriptionMatch = context.match(/description:\"([^\"]*)\"/);\n    const imageMatch = context.match(/image:\\$R\\[\\d+\\]=\\{big:\"([^\"]*)\",thumb:\"([^\"]*)\"/);\n\n    if (!idMatch || !titleMatch) {","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/locals/feed.ts#L205-L241","documentation":"Thrown inside `parseUnknownResponse` after RSSHub executes Locals' obfuscated server-side JavaScript in a `node:vm` sandbox (lib/routes/locals/feed.ts:205). The sandboxed script is expected to populate `self.$R[instanceId][0]` with the decoded payload; if that slot is missing or falsy, the response shape has changed or the wrong `instanceId` was used. This is an upstream-format breakage, not a user input error.","triggerScenarios":"Locals ships a new bundle that changes its `$R` serialization format, renames the `self.$R` global, or shifts the instance id mapping; the cached `serverId`/`key` passed as `instanceId` no longer matches the live response; the `/_server` endpoint returns an error body that still executes but writes nothing to `$R`.","commonSituations":"Site redesign or bundler output change on locals.com; stale cached action-id that mismatches the current bundle; A/B test variant served to RSSHub's IP/User-Agent.","solutions":["Invalidate the Locals caches (`locals:action-ids:*`, `locals:content-page:*`) so a fresh bundle and server id are resolved.","Inspect a raw `/_server` response in a browser devtools session and diff the `$R` assignment shape against `parseUnknownResponse`.","Update the sandbox field access (`sandbox.self.$R?.[instanceId]?.[0]`) to match the new Locals serialization, or harden it to scan all `$R` entries.","File an issue / open a PR against the `locals` route once the new format is confirmed."],"exampleFix":"// before\nconst value = sandbox.self.$R?.[instanceId]?.[0] as T | undefined;\nif (!value) {\n    throw new Error('Unable to decode the Locals server response.');\n}\n\n// after: scan every $R bucket for the first non-empty array entry\nconst allBuckets = Object.values(sandbox.self.$R ?? {});\nconst value = allBuckets.flatMap((b) => b).find(Boolean) as T | undefined;\nif (!value) {\n    throw new Error('Unable to decode the Locals server response.');\n}","handlingStrategy":"try-catch","validationCode":"// Cannot fully validate without executing the payload; pre-check the bundle freshness instead.\nasync function ensureFreshLocalsBundle(community: string): Promise<void> {\n    const page = await fetchContentPage(community, config.locals.session);\n    if (!/serverApi-/.test(page)) {\n        throw new Error('Locals bundle looks stale; refusing to call /_server');\n    }\n}","typeGuard":"function hasDecodedLocalsValue<T>(value: T | undefined): value is T {\n    return value !== undefined && value !== null;\n}","tryCatchPattern":"try {\n    return await requestServerFunction<T>(session, id, key, args);\n} catch (e) {\n    if (e instanceof Error && /Unable to decode the Locals server response/.test(e.message)) {\n        await cache.delete('locals:action-ids:' + community);\n        // one retry with a freshly resolved bundle\n        return await requestServerFunction<T>(session, id, key, args);\n    }\n    throw e;\n}","preventionTips":["Pin the bundle resolution behind a short-TTL cache so format drift surfaces fast.","Log a snippet of the raw /_server body when decoding fails for faster triage.","Subscribe to / watch the locals.com frontend releases; update the parser proactively."],"tags":["upstream-change","vm-sandbox","parsing","locals"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}