zeroclaw-labs/zeroclaw · error · anyhow::Error

WS endpoint failed: code={} msg={}

Error message

WS endpoint failed: code={} msg={}

What it means

Before the Lark long-connection event loop can open a WebSocket, get_ws_endpoint POSTs the app credentials to {ws_base}/callback/ws/endpoint to provision a wss URL; Lark answers with its code/msg envelope and any non-zero code bails here with the server's msg (or '(none)'). The error text is the passthrough of Lark's provisioning refusal — most commonly bad AppID/AppSecret, or the app not being configured for long-connection (WebSocket) event mode on the open platform.

Source

Thrown at crates/zeroclaw-channels/src/lark.rs:1079

        Ok(response)
    }

    /// POST /callback/ws/endpoint → (wss_url, client_config)
    async fn get_ws_endpoint(&self) -> anyhow::Result<(String, WsClientConfig)> {
        let resp = self
            .http_client()
            .post(format!("{}/callback/ws/endpoint", self.ws_base()))
            .header("locale", self.platform.locale_header())
            .json(&serde_json::json!({
                "AppID": self.app_id,
                "AppSecret": self.app_secret,
            }))
            .send()
            .await?
            .json::<WsEndpointResp>()
            .await?;
        if resp.code != 0 {
            anyhow::bail!(
                "WS endpoint failed: code={} msg={}",
                resp.code,
                resp.msg.as_deref().unwrap_or("(none)")
            );
        }
        let ep = resp.data.ok_or_else(|| {
            ::zeroclaw_log::record!(
                ERROR,
                ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
                    .with_outcome(::zeroclaw_log::EventOutcome::Failure),
                "WS endpoint: empty data"
            );
            anyhow::Error::msg("WS endpoint: empty data")
        })?;
        Ok((ep.url, ep.client_config.unwrap_or_default()))
    }

    /// WS long-connection event loop.  Returns Ok(()) when the connection closes

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Re-copy AppID/AppSecret from the Feishu/Lark developer console into the channel config and confirm no stray whitespace
  2. In the developer console under Events & Callbacks, switch the app to 'Long connection' (WebSocket) mode and republish the app version
  3. Make sure the configured domain matches the app's platform (feishu.cn vs larksuite.com)
  4. The msg field in the error is Lark's own reason — paste it into the Lark error-code docs for the exact fix
Defensive patterns

Strategy: try-catch

Try / catch

match lark_channel.listen(tx).await {
    Err(e) if e.to_string().contains("WS endpoint failed") => {
        // read code/msg: credential or long-connection-mode problem;
        // surface to operator — retrying without a config/console fix will keep failing
    }
    other => { let _ = other?; }
}

Prevention

When it happens

Trigger: listen_ws calls get_ws_endpoint; the POST succeeds but resp.code != 0 — wrong app_id/app_secret in config, the Feishu/Lark app has 'Long connection' event subscription disabled in the developer console, or domain/region mismatch (feishu endpoint used with a Larksuite app).

Common situations: Credentials rotated or typoed in config.toml; the app was created with webhook event mode and never switched to long-connection mode; using open.larksuite.cn vs open.feishu.cn base for the wrong tenant.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/cc435924b8201d58. Report an issue: GitHub.