linera-io/linera-protocol · error · async_graphql::Error

Unexpected response type

Error message

Unexpected response type

What it means

`do_claim` only ever enqueues initial claims (`target_chain_id: None`), so the batch processor answers with `PendingResponse::Initial`. The match at the end of `do_claim` therefore expects `Initial`; receiving `PendingResponse::Daily` violates that invariant. The error is a defensive guard, not a condition any public input can produce — seeing it means the faucet's internal request/response routing is inconsistent (a regression or a patched build).

Source

Thrown at linera-faucet/server/src/lib.rs:544

        #[cfg(with_metrics)]
        {
            // Refusals detected during batch validation (e.g. a duplicate that
            // raced past the front-door check) arrive here as errors; classify
            // them by message so they are counted once, under their own label.
            let label = match &response {
                PendingResponse::Initial(Ok(_)) => "success",
                PendingResponse::Initial(Err(error)) => refusal_label(error).unwrap_or("error"),
                PendingResponse::Daily(_) => "error",
            };
            metrics::CLAIM_REQUESTS_TOTAL
                .with_label_values(&[label])
                .inc();
        }

        match response {
            PendingResponse::Initial(result) => result.map(|b| *b),
            PendingResponse::Daily(_) => Err(Error::new("Unexpected response type")),
        }
    }

    async fn do_daily_claim(
        &self,
        owner: AccountOwner,
        destination: AccountOwner,
    ) -> Result<ClaimOutcome, Error> {
        // Each early return below is a *refusal*, not a failure, and must be counted
        // under its own `result` label: these paths return before the queue round-trip
        // that increments `CLAIM_REQUESTS_TOTAL`, so without the explicit counters
        // refusals are invisible in metrics (they only appear as unlabelled
        // `claim_latency_ms{result="error"}` observations).
        if self.daily_claim_amount == Amount::ZERO {
            #[cfg(with_metrics)]
            metrics::CLAIM_REQUESTS_TOTAL
                .with_label_values(&["daily_disabled"])
                .inc();

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Verify the faucet is an unmodified release via the `version` GraphQL query and redeploy from a clean checkout of the same version
  2. If you maintain a patched faucet, audit `do_claim`'s enqueue (target_chain_id must be None) and `execute_batch`'s response construction (responses keyed off `request.target_chain_id`) so initial claims always answer with `PendingResponse::Initial`
  3. Report the invariant break upstream with the faucet version and logs, since it indicates an internal routing bug
Defensive patterns

Strategy: try-catch

Type guard

fn is_invariant_violation(msg: &str) -> bool { msg == "Unexpected response type" }

Try / catch

Catch the GraphQL error and match the exact message `Unexpected response type`; do NOT retry — record the faucet version (via the `version` query) and report it as an internal routing bug. Retrying cannot fix an invariant break.

Prevention

When it happens

Trigger: Not reachable through the public `claim`/`dailyClaim` API: the response variant is chosen from the same `request.target_chain_id` field that was set at enqueue time (lib.rs:1099-1112). It would take a code change that pairs an initial-claim request with a daily-claim response, or manual corruption of the pending queue, to hit this arm.

Common situations: Running a fork or locally modified faucet where the enqueue path or response construction was changed; mixing binaries from different Linera versions after an upgrade; otherwise effectively never seen on unmodified releases.

Related errors


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