{"record":{"id":"c23680b4089cdc3a","repo":"jackwener/OpenCLI","slug":"xiaoyuzhou-history-returned-an-invalid-label-e","errorCode":null,"errorMessage":"Xiaoyuzhou history returned an invalid ${label}; expected seconds","messagePattern":"Xiaoyuzhou history returned an invalid (.+?); expected seconds","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/history.js","lineNumber":42,"sourceCode":"\nfunction requiredId(value, label) {\n    if (typeof value !== 'string' || !XIAOYUZHOU_ID.test(value)) {\n        throw new CommandExecutionError(`Xiaoyuzhou history returned an invalid ${label}`);\n    }\n    return value.toLowerCase();\n}\n\nfunction requiredString(value, label) {\n    if (typeof value !== 'string' || !value.trim()) {\n        throw new CommandExecutionError(`Xiaoyuzhou history returned an invalid ${label}`);\n    }\n    return value.trim();\n}\n\nfunction optionalSeconds(value, label, { positive = false } = {}) {\n    if (value === null) return null;\n    if (!Number.isSafeInteger(value) || value < (positive ? 1 : 0)) {\n        throw new CommandExecutionError(`Xiaoyuzhou history returned an invalid ${label}; expected seconds`);\n    }\n    return value;\n}\n\nfunction optionalIsoTime(value, label, { required = false } = {}) {\n    if (value === null) {\n        if (required) throw new CommandExecutionError(`Xiaoyuzhou history returned a missing ${label}`);\n        return null;\n    }\n    if (typeof value !== 'string' || !value.includes('T') || !Number.isFinite(Date.parse(value))) {\n        throw new CommandExecutionError(`Xiaoyuzhou history returned an invalid ${label}`);\n    }\n    return new Date(value).toISOString();\n}\n\nfunction parseHistoryPage(response) {\n    if (!isRecord(response) || !isRecord(response.raw) || response.raw.data !== response.data) {\n        throw new CommandExecutionError('Xiaoyuzhou history returned an unexpected response shape');","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/history.js#L24-L60","documentation":"This CommandExecutionError is thrown by optionalSeconds when a numeric seconds field from the Xiaoyuzhou API (episode.duration, required positive; row.progress, allowed >= 0) is not a safe integer or is below its minimum (duration must be >= 1). The field may legitimately be null, but any present value must be a whole number of seconds.","triggerScenarios":"parseHistoryEpisode calls optionalSeconds(episode.duration, ..., { positive: true }) and parseProgressRows calls optionalSeconds(row.progress, ...); the error fires when the value is a float (e.g. 12.5), a string like \"300\", undefined, negative, or exceeds Number.MAX_SAFE_INTEGER — only literal null passes through as null.","commonSituations":"API version change switching duration from integer seconds to float milliseconds; proxies/mocks returning strings for numeric fields; a new field type (ISO 8601 duration string like PT1H2M3S) replacing raw seconds.","solutions":["Update the library/CLI to a version matching the current API field types.","Inspect the raw payload to see the actual type/format of duration or progress (float? string? ISO-8601 duration?).","If the API now returns milliseconds, convert before parsing (Math.round(ms / 1000)) via a wrapper around requestXiaoyuzhouJson.","Convert string numerics to Number() in a pre-processing pass if a proxy is responsible.","Report the changed payload shape to maintainers."],"exampleFix":"// before (API returns ms floats)\n{ \"episode\": { \"duration\": 3720000.5 } }\n// after (normalize before parse)\n{ \"episode\": { \"duration\": Math.round(raw.duration / 1000) } } // 3720","handlingStrategy":"type-guard","validationCode":"function isSeconds(v, { positive = false } = {}) { return v === null || (Number.isSafeInteger(v) && v >= (positive ? 1 : 0)); }\nif (!isSeconds(ep.duration, { positive: true }) || !isSeconds(row.progress)) throw new Error('Unexpected seconds field');","typeGuard":"const isSafeSeconds = (v, min) => typeof v === 'number' && Number.isSafeInteger(v) && v >= min;","tryCatchPattern":"try { const rows = await fetchHistory(); } catch (e) { if (e instanceof CommandExecutionError && e.message.includes('expected seconds')) { console.error('duration/progress is not integer seconds — check for ms floats or string values'); } else throw e; }","preventionTips":["Confirm field units with the current API docs (seconds vs milliseconds).","Normalize numeric fields (Number(), Math.round) before handing rows to the parser.","Validate with Number.isSafeInteger, not just typeof number.","Test against recorded real API payloads, not hand-written mocks."],"tags":["api-response","schema-validation","type-mismatch"],"backgroundTag":"invalid-api-response-shape","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}