DIYgod/RSSHub · error · Error

Unable to decode the Locals server response.

Error message

Unable to decode the Locals server response.

What it means

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.

Source

Thrown at lib/routes/locals/feed.ts:223

function parseUnknownResponse<T>(body: string, instanceId: string): T {
    const sandbox = {
        $R: {},
        self: {},
    } as {
        $R: Record<string, unknown[]>;
        self: {
            $R?: Record<string, unknown[]>;
        };
    };

    sandbox.self.$R = sandbox.$R;

    vm.createContext(sandbox);
    vm.runInContext(body, sandbox, { timeout: 5000 });

    const value = sandbox.self.$R?.[instanceId]?.[0] as T | undefined;
    if (!value) {
        throw new Error('Unable to decode the Locals server response.');
    }

    return value;
}

function extractCommunityInfo(html: string, community: string) {
    const markerIndex = html.indexOf(`hashtag:"${community}"`);
    if (markerIndex === -1) {
        throw new Error('Unable to resolve the Locals community metadata.');
    }

    const context = html.slice(Math.max(0, markerIndex - 200), markerIndex + 1200);
    const idMatch = context.match(/id:(\d+)/);
    const titleMatch = context.match(/title:"([^"]+)"/);
    const descriptionMatch = context.match(/description:"([^"]*)"/);
    const imageMatch = context.match(/image:\$R\[\d+\]=\{big:"([^"]*)",thumb:"([^"]*)"/);

    if (!idMatch || !titleMatch) {

View on GitHub (pinned to bed535e087)

Solutions

  1. Invalidate the Locals caches (`locals:action-ids:*`, `locals:content-page:*`) so a fresh bundle and server id are resolved.
  2. Inspect a raw `/_server` response in a browser devtools session and diff the `$R` assignment shape against `parseUnknownResponse`.
  3. Update the sandbox field access (`sandbox.self.$R?.[instanceId]?.[0]`) to match the new Locals serialization, or harden it to scan all `$R` entries.
  4. File an issue / open a PR against the `locals` route once the new format is confirmed.

Example fix

// before
const value = sandbox.self.$R?.[instanceId]?.[0] as T | undefined;
if (!value) {
    throw new Error('Unable to decode the Locals server response.');
}

// after: scan every $R bucket for the first non-empty array entry
const allBuckets = Object.values(sandbox.self.$R ?? {});
const value = allBuckets.flatMap((b) => b).find(Boolean) as T | undefined;
if (!value) {
    throw new Error('Unable to decode the Locals server response.');
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Cannot fully validate without executing the payload; pre-check the bundle freshness instead.
async function ensureFreshLocalsBundle(community: string): Promise<void> {
    const page = await fetchContentPage(community, config.locals.session);
    if (!/serverApi-/.test(page)) {
        throw new Error('Locals bundle looks stale; refusing to call /_server');
    }
}

Type guard

function hasDecodedLocalsValue<T>(value: T | undefined): value is T {
    return value !== undefined && value !== null;
}

Try / catch

try {
    return await requestServerFunction<T>(session, id, key, args);
} catch (e) {
    if (e instanceof Error && /Unable to decode the Locals server response/.test(e.message)) {
        await cache.delete('locals:action-ids:' + community);
        // one retry with a freshly resolved bundle
        return await requestServerFunction<T>(session, id, key, args);
    }
    throw e;
}

Prevention

When it happens

Trigger: 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`.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of DIYgod/RSSHub@bed535e087 (2026-08-12). Data as JSON: /api/errors/6d501740b73d0344. Report an issue: GitHub.