janhq/jan · error · UpdateError

Invalid response from server

Error message

Invalid response from server: {0}

What it means

UpdateError::InvalidResponse is returned when the endpoint answered successfully but the response content is semantically invalid — for example an unexpected HTTP body, missing fields, or a status/shape the updater doesn't accept. It renders 'Invalid response from server: <detail>'.

Solutions

  1. Read the embedded detail to identify which part of the response was invalid
  2. Fetch the endpoint manually and compare against the expected updater format
  3. Coordinate with the update-server team on schema changes
  4. Pin/upgrade the app's updater to a version compatible with the new server format

Example fix

// before
if info.version.is_empty() { return Err(UpdateError::InvalidResponse("missing version".into())); }
// after
match semver::Version::parse(&info.version) {
    Ok(_) => {}
    Err(e) => return Err(UpdateError::InvalidResponse(format!("bad version '{}': {}", info.version, e))),
}
Defensive patterns

Strategy: validation

Validate before calling

const body = await res.json();
if (!body.version || !/^\d+\.\d+\.\d+/.test(body.version)) throw new Error('invalid manifest: bad version');

Type guard

function isValidUpdateManifest(x: unknown): x is { version: string; pub_date: string; platforms: Record<string, unknown> } {
  const m = x as any;
  return !!m && typeof m.version === 'string' && typeof m.pub_date === 'string' && !!m.platforms;
}

Prevention

When it happens

Trigger: The updater receives a 200 response whose content fails its post-parse validation checks (e.g. missing version field or wrong content type), and the code constructs InvalidResponse(detail).

Common situations: Server updated its manifest schema; endpoint returns 200 with an HTML login page; load balancer intercepting requests; version string format changed.

Related errors


AI-assisted analysis of janhq/jan@7205d770c1 (2026-09-17). Data as JSON: /api/errors/c9860d58307d83e1. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/src/core/updater/custom_updater.rs:40

    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)]
    pub notes: Option<String>,
    #[serde(default)]
    pub pub_date: Option<String>,
    #[serde(default)]
    pub platforms: Option<serde_json::Value>,
    /// URL to download the update

View on GitHub (pinned to 7205d770c1)