janhq/jan · error · UpdateError
Failed to parse update response
Error message
Failed to parse update response: {0} What it means
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>'.
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
Example fix
// before
let info: UpdateInfo = serde_json::from_str(&body).map_err(|e| UpdateError::ParseError(e.to_string()))?;
// after
let info: UpdateInfo = serde_json::from_str(&body)
.map_err(|e| UpdateError::ParseError(format!("{} :: body={}", e, &body[..body.len().min(200]))))?; // log body for diagnosis Defensive patterns
Strategy: validation
Validate before calling
const res = await fetch(endpoint);
const ct = res.headers.get('content-type') || '';
if (!ct.includes('application/json')) throw new Error(`unexpected content-type: ${ct}`); Type guard
function looksLikeUpdateInfo(x: unknown): x is { version: string; notes?: string; pub_date?: string } {
return typeof x === 'object' && x !== null && 'version' in x && typeof (x as any).version === 'string';
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- All endpoints failed
- Failed to parse document
- HTTP request failed
- Invalid response from server
- No endpoints configured
AI-assisted analysis of janhq/jan@7205d770c1 (2026-09-17).
Data as JSON: /api/errors/c90117b2fe17da63.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/src/core/updater/custom_updater.rs:34
use thiserror::Error;
/// Secret key for HMAC signature
/// - In CI: Set JAN_SIGNING_KEY environment variable at build time
/// - In local dev: Falls back to a test key
pub(crate) const SECRET_KEY: &str = match option_env!("JAN_SIGNING_KEY") {
Some(key) => key,
None => "local-dev-test-key-not-for-production",
};
/// Timeout for HTTP requests
const REQUEST_TIMEOUT_SECS: u64 = 30;
#[derive(Debug, Error)]
pub enum UpdateError {
#[error("HTTP request failed: {0}")]
RequestFailed(#[from] reqwest::Error),
#[error("Failed to parse update response: {0}")]
ParseError(String),
#[error("All endpoints failed")]
AllEndpointsFailed,
#[error("Invalid response from server: {0}")]
InvalidResponse(String),
#[error("No endpoints configured")]
NoEndpointsConfigured,
}
/// Update information returned by the update check endpoint
/// Compatible with Tauri's updater format
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UpdateInfo {
pub version: String,
#[serde(default)]View on GitHub (pinned to 7205d770c1)