linera-io/linera-protocol · error

Failed to deserialize query response from application

Error message

Failed to deserialize query response from application

What it means

ServiceRuntime::query_application forwards a JSON query to another application through the host (service_wit::try_query_application) and parses the returned bytes with serde_json::from_slice::<A::QueryResponse>. The expect panics when the target application's query response does not decode into the type the caller compiled against.

Source

Thrown at linera-sdk/src/service/runtime.rs:218

            .expect("Failed to serialize application operation");

        service_wit::schedule_operation(&bytes);
    }

    /// Queries another application.
    pub fn query_application<A: ServiceAbi>(
        &self,
        application: ApplicationId<A>,
        query: &A::Query,
    ) -> A::QueryResponse {
        let query_bytes =
            serde_json::to_vec(&query).expect("Failed to serialize query to another application");

        let response_bytes =
            service_wit::try_query_application(application.forget_abi().into(), &query_bytes);

        serde_json::from_slice(&response_bytes)
            .expect("Failed to deserialize query response from application")
    }
}

impl<Application> ServiceRuntime<Application>
where
    Application: Service,
{
    /// Loads a value from the `slot` cache or fetches it and stores it in the cache.
    fn fetch_value_through_cache<T>(slot: &Mutex<Option<T>>, fetch: impl FnOnce() -> T) -> T
    where
        T: Clone,
    {
        let mut value = slot
            .lock()
            .expect("Mutex should never be poisoned because service runs in a single thread");

        if value.is_none() {
            *value = Some(fetch());

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Align versions: rebuild the querying service against the exact crate version of the target application that is deployed, then restart the service
  2. Validate the raw response first during debugging: log serde_json::from_slice::<serde_json::Value>(&response_bytes) to see the actual shape the peer returned
  3. Make the caller's QueryResponse tolerant (Option fields, #[serde(default)], ignore unknown keys) for forward compatibility
  4. Check that application_id comes from the same publish/genesis as the deployed target, not from a config file of a previous network

Example fix

// before: panic on any drift
let res: TokenResponse = runtime.query_application(app_id, q).await;

// after (debug path): inspect then decode with a clear message
let bytes = ...; // response bytes
let value: serde_json::Value = serde_json::from_slice(&bytes)
    .expect("peer returned non-JSON");
let res: TokenResponse = serde_json::from_value(value)
    .unwrap_or_else(|e| panic!("token app response schema mismatch: {e}"));
Defensive patterns

Strategy: validation

Validate before calling

// Decode defensively via serde_json::Value first when peers are versioned.
let v: serde_json::Value = serde_json::from_slice(&response_bytes).map_err(QueryError::BadJson)?;
let r: A::QueryResponse = serde_json::from_value(v).map_err(QueryError::SchemaMismatch)?;

Prevention

When it happens

Trigger: Calling query_application::<OtherApp>(application_id, query) where the deployed OtherApp returns JSON not matching the caller's QueryResponse — version drift between modules, wrong application_id, or a response schema change (field rename, nesting, Option/required flip).

Common situations: Running a proxy/composed service (e.g. a wrapper app querying a token app) after the inner app was upgraded to a new response format; querying an application whose query handler errors and returns an error body instead of the expected payload; mixing module versions across testnet resets.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/183451f5a0f39132. Report an issue: GitHub.