{"record":{"id":"754a1ece39c3d6c4","repo":"tinyhumansai/openhuman","slug":"smithery-returned-http-status","errorCode":null,"errorMessage":"Smithery returned HTTP {status}: {}","messagePattern":"Smithery returned HTTP (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/openhuman/mcp/registry/registries/smithery.rs","lineNumber":77,"sourceCode":"        let mut req = client.get(format!(\"{SMITHERY_BASE}/servers\"));\n        if !q.is_empty() {\n            req = req.query(&[(\"q\", q)]);\n        }\n        req = req\n            .query(&[\n                (\"page\", &page.to_string()),\n                (\"pageSize\", &page_size.to_string()),\n            ])\n            .header(\"Accept\", \"application/json\");\n        req = apply_auth(config, req);\n\n        let resp = req.send().await.context(\"Smithery search request failed\")?;\n        let status = resp.status();\n        let body = resp.text().await.context(\"Smithery search read failed\")?;\n\n        if !status.is_success() {\n            tracing::warn!(\"[smithery] search HTTP {status} for key={cache_key}\");\n            anyhow::bail!(\n                \"Smithery returned HTTP {status}: {}\",\n                &body[..body.len().min(200)]\n            );\n        }\n\n        let parsed: SmitheryListResponse = serde_json::from_str(&body)\n            .with_context(|| format!(\"Failed to parse Smithery list response: {body}\"))?;\n\n        let total_pages = parsed.pagination.total_pages;\n        let servers = tag_source(parsed.servers);\n\n        let _ = store::set_cached(config, &cache_key, &body);\n        tracing::debug!(\n            \"[smithery] search ok servers={} total_pages={}\",\n            servers.len(),\n            total_pages\n        );\n","sourceCodeStart":59,"sourceCodeEnd":95,"githubUrl":"https://github.com/tinyhumansai/openhuman/blob/749120085864ce16e0f273c7b86fac7740b39c5b/src/openhuman/mcp/registry/registries/smithery.rs#L59-L95","documentation":"Raised by the Smithery MCP-catalog client when a paginated search against the Smithery registry returns any non-2xx HTTP status. The request itself completed (auth headers were already attached by apply_auth), so the status is Smithery's — or an intermediary proxy's — answer, not a transport failure. The message embeds the status code plus the first 200 bytes of the response body so the upstream API's own error text is visible; a warn! with the cache key is also emitted.","triggerScenarios":"Calling the registry search flow (SmitheryRegistry::search with query/page/pageSize, the query params visible in the source) when Smithery answers 401/403 (invalid or missing API key attached by apply_auth), 429 (catalog rate limit during bulk or scripted installs), 5xx (registry outage), or when a corporate TLS-inspection proxy answers 407/502 for registry.smithery.ai.","commonSituations":"Expired or wrong Smithery API key in config; install scripts looping over catalog pages until rate-limited; VPN/proxy environments; Smithery API host or schema change after a registry update; a fully offline machine requesting a cache key that store::set_cached never populated.","solutions":["Reproduce outside the app: curl -i 'https://registry.smithery.ai/servers?q=<query>' and read the status and body.","For 401/403, fix or remove the Smithery credentials that apply_auth attaches (the config-managed API key), then retry.","For 429/5xx, wait a few seconds and retry — these are transient, and previously cached pages (store::set_cached) still serve the UI.","For proxy interference (407/502 from a middlebox), allowlist registry.smithery.ai or bypass the proxy."],"exampleFix":"// before\nlet page = registry.search(config, &query, page, page_size).await?;\n\n// after — retry transient statuses once, fail fast on auth errors\nlet page = match registry.search(config, &query, page, page_size).await {\n    Ok(p) => p,\n    Err(e) if e.to_string().contains(\"HTTP 429\") || e.to_string().contains(\"HTTP 5\") => {\n        tokio::time::sleep(std::time::Duration::from_secs(3)).await;\n        registry.search(config, &query, page, page_size).await?\n    }\n    Err(e) => return Err(e),\n};","handlingStrategy":"retry","validationCode":"// Preflight: can we reach the Smithery registry at all?\nasync fn smithery_reachable(client: &reqwest::Client) -> bool {\n    const BASE: &str = \"https://registry.smithery.ai\"; // same host the registry client uses\n    match client.head(BASE).send().await {\n        Ok(resp) => !resp.status().is_server_error(),\n        Err(_) => false,\n    }\n}","typeGuard":null,"tryCatchPattern":"Inspect the embedded status in err.to_string(): treat `HTTP 401`/`HTTP 403` as a credentials bug (do not retry), `HTTP 429`/`HTTP 5xx` as transient (retry with seconds-scale backoff), anything else as network/proxy. Log the 200-byte body prefix — it carries Smithery's own message.","preventionTips":["Keep a valid Smithery API key configured where apply_auth expects it.","Rate-limit catalog refresh loops — sleep between pages instead of hammering search.","Rely on the built-in response cache for read paths so outages degrade gracefully.","Monitor registry status and fail soft in UI search results."],"tags":["mcp","smithery","http","network","registry"],"backgroundTag":"upstream-api-http-error","analyzedSha":"749120085864ce16e0f273c7b86fac7740b39c5b","analyzedAt":"2026-08-17T21:21:45.363Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}