koala73/worldmonitor · error · Error
Redis returned malformed JSON
Error message
Redis returned malformed JSON
What it means
Thrown by `parseStoredJson` in api/oauth/_refresh-recovery.ts:221 when the stored string under the refresh key (or the family pointer) is not valid JSON. The recovery protocol stores `JSON.stringify`-ed objects (refresh data, family pointers), so a JSON.parse failure means the stored payload was corrupted or written by a different producer/format.
Source
Thrown at api/oauth/_refresh-recovery.ts:221
},
body: JSON.stringify(['EVAL', script, String(keys.length), ...keys, ...args]),
signal: AbortSignal.timeout(3_000),
});
if (!resp.ok) throw new Error(`Redis HTTP ${resp.status}`);
const data = (await resp.json().catch(() => null)) as { result?: unknown; error?: string } | null;
if (data?.error) throw new Error(`Redis EVAL failed: ${data.error}`);
if (!data || !Object.prototype.hasOwnProperty.call(data, 'result')) {
throw new Error('Redis EVAL returned an invalid response');
}
return data.result;
}
function parseStoredJson(value: unknown): unknown {
if (typeof value !== 'string') throw new Error('Redis returned an invalid stored value');
try {
return JSON.parse(value);
} catch {
throw new Error('Redis returned malformed JSON');
}
}
export async function rawRedisBeginRefreshAttempt(
refreshToken: string,
attemptId: string,
): Promise<RefreshConsumeResult> {
const refreshKey = `oauth:refresh:${refreshToken}`;
const attemptKey = refreshFamilyAttemptKey(refreshToken);
const pointerKey = refreshFamilyPointerKey(refreshToken);
const attemptValue = JSON.stringify({ attempt_id: attemptId });
const attemptMarker = serializeRefreshAttemptMarker(attemptValue);
const script = [
"local value = redis.call('GET', KEYS[1])",
'if value then',
' local ok, decoded = pcall(cjson.decode, value)',
" if ok and type(decoded) == 'table' and decoded.kind == 'refresh_attempt' then",
" return {0, redis.call('EXISTS', KEYS[2]), redis.call('GET', KEYS[3]) or false}",View on GitHub (pinned to a96956387a)
Solutions
- GET the offending key and inspect the raw string; if it is not the expected JSON object, delete it (the token will simply require re-login) so the next refresh writes a fresh value
- If keys come from an old format, write a one-time migration or let TTL (REFRESH_TTL_SECONDS) age them out
- Ensure no other producer writes these keys with a different serialization
Defensive patterns
Strategy: try-catch
Type guard
function isMalformedRedisJson(e: unknown): boolean {
return e instanceof Error && e.message === 'Redis returned malformed JSON';
} Try / catch
try {
await beginRefreshAttempt(token, id);
} catch (e) {
if (isMalformedRedisJson(e)) {
// non-retryable: delete the offending key and force re-login; log the raw value for forensics
} else throw e;
} Prevention
- Use one serializer for all oauth:* values; version the payload format and migrate old keys
- Never hand-edit stored refresh payloads in the Redis console
When it happens
Trigger: `oauth:refresh:<token>` or the family pointer key containing a hand-edited string, truncated data, or a legacy non-JSON format when `rawRedisBeginRefreshAttempt` consumes it and calls `parseStoredJson`.
Common situations: Legacy keys from an older serialization format surviving a deploy; manual console edits; truncation from exceeding value size limits; cross-format version skew after a migration.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Redis returned an invalid stored value
- Redis not configured
- Redis HTTP ${resp.status}
- Redis EVAL failed: ${data.error}
- Redis EVAL returned an invalid response
AI-assisted analysis of koala73/worldmonitor@a96956387a (2026-08-27).
Data as JSON: /api/errors/81c8bc7fcea4b498.
Report an issue: GitHub.