DIYgod/RSSHub · error · Error

Failed to fetch data from Kemono: ${error instanceof Error ?

Error message

Failed to fetch data from Kemono: ${error instanceof Error ? error.message : 'Unknown error'}

What it means

The top-level catch-all in the Kemono handler. Every error thrown inside the try block (network failures, the buildApiUrl/buildFrontendUrl validation errors, JSON parse errors, non-200 responses from got) is caught and re-wrapped as 'Failed to fetch data from Kemono: <original message>' with the original attached via { cause }. This obscures the original error type and makes upstream handling harder.

Source

Thrown at lib/routes/kemono/index.tsx:418

            title = `Announcements of ${authorName} from ${source} | Kemono`;
            items = processAnnouncements(response.data, authorName, source, userId, limit);
        } else if (contentType === 'fancards') {
            title = `Fancards of ${authorName} from ${source} | Kemono`;
            items = processFancards(response.data, authorName, source, userId, limit);
        } else {
            title = isPostsMode ? 'Kemono Posts' : `Posts of ${authorName} from ${source} | Kemono`;
            const posts = isPostsMode ? response.data.posts : response.data;
            items = processPosts(posts, authorName, limit);
        }

        return {
            title,
            image: iconUrl,
            link: frontendUrl,
            item: items,
        };
    } catch (error) {
        throw new Error(`Failed to fetch data from Kemono: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
    }
}

View on GitHub (pinned to bed535e087)

Solutions

  1. Do not blanket-wrap — let typed errors (InvalidParameterError, ConfigNotFoundError) propagate unchanged so callers can distinguish bad input from server faults.
  2. Only wrap unexpected/network errors, and preserve the HTTP status code in the message or error properties.
  3. Add status-code-specific handling (404 → 'creator not found', 429 → 'rate limited, retry later', 5xx → 'Kemono server error').
  4. Keep the { cause } chain (good practice) but stop stringifying the inner error which can lose structured info.

Example fix

// before
} catch (error) {
    throw new Error(`Failed to fetch data from Kemono: ${error instanceof Error ? error.message : 'Unknown error'}`, { cause: error });
}

// after — let expected errors through, wrap only unexpected ones
} catch (error) {
    if (error instanceof InvalidParameterError || error instanceof ConfigNotFoundError) throw error;
    const status = (error as any)?.response?.statusCode;
    if (status === 404) throw new Error(`Kemono creator/channel not found for source=${source} userId=${userId}`, { cause: error });
    if (status === 429) throw new Error('Kemono rate limit reached; retry later', { cause: error });
    throw new Error(`Failed to fetch data from Kemono (status ${status ?? 'n/a'}): ${error instanceof Error ? error.message : error}`, { cause: error });
}
Defensive patterns

Strategy: try-catch

Type guard

function isHttpError(e: unknown): e is { response?: { statusCode: number }; message: string } {
    return typeof e === 'object' && e !== null && 'response' in e;
}

Try / catch

} catch (error) {
    // Let validation/config errors propagate unchanged
    if (error instanceof InvalidParameterError || error instanceof ConfigNotFoundError) throw error;
    const status = (error as any)?.response?.statusCode;
    if (status === 404) throw new Error(`Kemono resource not found (source=${source}, userId=${userId})`, { cause: error });
    if (status === 429) throw new Error('Kemono rate limit; retry later', { cause: error });
    throw new Error(`Kemono fetch failed (status ${status ?? 'n/a'}): ${error instanceof Error ? error.message : error}`, { cause: error });
}

Prevention

When it happens

Trigger: Any uncaught error within the handler: Kemono API returns 404 for a nonexistent creator, 5xx during an outage, network timeout, the 'User ID is required' validation from buildApiUrl, a JSON shape change causing processPosts to throw, or the profile fetch failing.

Common situations: Kemono is temporarily down or rate-limiting. A creator deleted their account (404). The API response schema changed and downstream destructuring throws. The catch-all conflates these distinct conditions into one generic message, making monitoring and retries harder.

Related errors


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