unionlabs/union · error

client info not found

Error message

client info not found

What it means

In `voyager rpc client-state --decode`, after the raw client state was successfully fetched (`ibc_state.state` is `Some`), the CLI asks the state module for `ClientInfo` (client type + IBC interface) needed to pick a decoder. The RPC returned `null`, so `.ok_or(anyhow!("client info not found"))` fires. In practice the state module resolves the client type on chain (e.g. `clientTypes(clientId)` on the EVM IBC handler) and yields `None` when the chain has no such client, so the raw path existed but the client is unknown to the IBC contract — or the request targeted the wrong chain.

Source

Thrown at voyager/src/main.rs:502

                } => {
                    let ibc_state = voyager_client
                        .query_ibc_state(
                            on.clone(),
                            ibc_spec_id.clone(),
                            height,
                            (ibc_handlers
                                .get(&ibc_spec_id)
                                .context(anyhow!("unknown IBC spec `{ibc_spec_id}`"))?
                                .client_state_path)(client_id.clone())?,
                        )
                        .await?;

                    match (ibc_state.state, decode) {
                        (Some(state), true) => {
                            let client_info = voyager_client
                                .client_info(on, ibc_spec_id.clone(), client_id)
                                .await?
                                .ok_or(anyhow!("client info not found"))?;

                            let decoded = voyager_client
                                .decode_client_state(
                                    client_info.client_type,
                                    client_info.ibc_interface,
                                    ibc_spec_id,
                                    serde_json::from_value(state)
                                        .expect("serialization is infallible; qed;"),
                                )
                                .await?;

                            print_json(&IbcStateResponse {
                                height: ibc_state.height,
                                state: Some(decoded),
                            });
                        }
                        (state, _) => {
                            print_json(&IbcStateResponse {

View on GitHub (pinned to 031785bb6d)

Solutions

  1. Verify the client exists on that chain first: `voyager rpc client-info --on <chain> --client-id <id> --ibc-spec-id <spec>` and inspect the result.
  2. Double-check `--on` — use the chain id where the client was actually created, not the counterparty.
  3. Check the client id for typos and correct sequence number (query `client_state_meta` or the chain's clients).
  4. If you only need the raw state, drop `--decode` to print it without resolving client info.
  5. If the client genuinely exists, re-run against the correct height (`--height`) and confirm the state module for that chain is healthy.

Example fix

# before
voyager rpc client-state --on union --client-id 07-tendermint-0 --ibc-spec-id ibc-classic --decode
# error: client info not found

# check ownership of the client, then query the right chain
voyager rpc client-info --on cosmos-hub --client-id 07-tendermint-0 --ibc-spec-id ibc-classic

# after
voyager rpc client-state --on cosmos-hub --client-id 07-tendermint-0 --ibc-spec-id ibc-classic --decode
Defensive patterns

Strategy: validation

Validate before calling

# confirm the client resolves on that chain before decoding
voyager rpc client-info --on "$CHAIN" --client-id "$CLIENT_ID" --ibc-spec-id "$SPEC" >/dev/null \
  || { echo "client $CLIENT_ID not found on $CHAIN" >&2; exit 2; }

Type guard

async fn client_exists(client: &HttpClient, on: &str, spec: &str, id: &str) -> bool {
    client
        .client_info(on.to_string(), spec.to_string(), RawClientId::new(id.into()))
        .await
        .ok()
        .flatten()
        .is_some()
}

Try / catch

// treat as data error, not transient: report and skip, do not retry
match maybe_client_info(...).await {
    Ok(None) => return Err(anyhow!("client info not found for {client_id} on {on}")),
    Ok(Some(info)) => decode_with(info),
    Err(e) if is_transport_error(&e) => retry(),
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: Running `voyager rpc client-state --on <chain> --client-id <id> --ibc-spec-id <spec> --decode` where the client id does not exist on `--on` (typo, wrong chain, counterparty's client id), the client has not been created yet, or the chain's IBC contract returns an empty client type for that id. Only the `--decode` path hits this: without `decode` the raw state is printed without `client_info`.

Common situations: Mixing up which chain owns a client id (client ids are per-chain, e.g. 07-tendermint-3 on the counterparty); querying a height from before the client was created; renaming/regenesis of chains so old client ids are gone; scripts iterating client ids past the created range.

Related errors


AI-assisted analysis of unionlabs/union@031785bb6d (2026-08-16). Data as JSON: /api/errors/89953b89ce317370. Report an issue: GitHub.