{"record":{"id":"c90117b2fe17da63","repo":"janhq/jan","slug":"failed-to-parse-update-response-0","errorCode":null,"errorMessage":"Failed to parse update response: {0}","messagePattern":"Failed to parse update response: (.+?)","errorType":"error_code","errorClass":"UpdateError","httpStatus":null,"severity":"error","filePath":"src-tauri/src/core/updater/custom_updater.rs","lineNumber":34,"sourceCode":"use thiserror::Error;\n\n/// Secret key for HMAC signature\n/// - In CI: Set JAN_SIGNING_KEY environment variable at build time\n/// - In local dev: Falls back to a test key\npub(crate) const SECRET_KEY: &str = match option_env!(\"JAN_SIGNING_KEY\") {\n    Some(key) => key,\n    None => \"local-dev-test-key-not-for-production\",\n};\n\n/// Timeout for HTTP requests\nconst REQUEST_TIMEOUT_SECS: u64 = 30;\n\n#[derive(Debug, Error)]\npub enum UpdateError {\n    #[error(\"HTTP request failed: {0}\")]\n    RequestFailed(#[from] reqwest::Error),\n\n    #[error(\"Failed to parse update response: {0}\")]\n    ParseError(String),\n\n    #[error(\"All endpoints failed\")]\n    AllEndpointsFailed,\n\n    #[error(\"Invalid response from server: {0}\")]\n    InvalidResponse(String),\n\n    #[error(\"No endpoints configured\")]\n    NoEndpointsConfigured,\n}\n\n/// Update information returned by the update check endpoint\n/// Compatible with Tauri's updater format\n#[derive(Debug, Clone, Serialize, Deserialize)]\npub struct UpdateInfo {\n    pub version: String,\n    #[serde(default)]","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/janhq/jan/blob/7205d770c1e097c3daf35a911176410e93bc5564/src-tauri/src/core/updater/custom_updater.rs#L16-L52","documentation":"UpdateError::ParseError is returned when the updater receives a response but cannot deserialize it into the expected update-information structure. The server's payload is converted to a string and embedded in 'Failed to parse update response: <detail>'.","triggerScenarios":"An update endpoint returns HTML (error page), truncated JSON, or JSON whose shape no longer matches the Tauri-compatible updater struct expected by the deserializer.","commonSituations":"Update server behind a misconfigured reverse proxy returning an HTML 404/502 page; backend changed the manifest format; CDN serving a captive-portal or maintenance page.","solutions":["Read the embedded detail to see the exact deserialization failure and the raw body","Fetch the endpoint manually (curl) to inspect what is actually returned","Confirm the endpoint serves the Tauri updater JSON schema (version, notes, pub_date, platforms)","Update the response struct fields to match any backend format changes"],"exampleFix":"// before\nlet info: UpdateInfo = serde_json::from_str(&body).map_err(|e| UpdateError::ParseError(e.to_string()))?;\n// after\nlet info: UpdateInfo = serde_json::from_str(&body)\n    .map_err(|e| UpdateError::ParseError(format!(\"{} :: body={}\", e, &body[..body.len().min(200]))))?; // log body for diagnosis","handlingStrategy":"validation","validationCode":"const res = await fetch(endpoint);\nconst ct = res.headers.get('content-type') || '';\nif (!ct.includes('application/json')) throw new Error(`unexpected content-type: ${ct}`);","typeGuard":"function looksLikeUpdateInfo(x: unknown): x is { version: string; notes?: string; pub_date?: string } {\n  return typeof x === 'object' && x !== null && 'version' in x && typeof (x as any).version === 'string';\n}","tryCatchPattern":null,"preventionTips":["Validate content-type is JSON before parsing","curl the endpoint and compare against the expected updater schema after any server change","Include a body snippet in ParseError for diagnosability","Add a schema/version field to the manifest so format changes are detectable"],"tags":["json","parsing","updater","rust"],"backgroundTag":"json-parse-error","analyzedSha":"7205d770c1e097c3daf35a911176410e93bc5564","analyzedAt":"2026-09-17T14:27:30.100Z","contentChangedAt":"2026-09-17T14:27:30.100Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}