{"record":{"id":"291fae215fca95a3","repo":"neondatabase/neon","slug":"no-grpc-url-for-shard-shard-index","errorCode":null,"errorMessage":"no grpc_url for shard {shard_index}","messagePattern":"no grpc_url for shard (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"libs/compute_api/src/spec.rs","lineNumber":348,"sourceCode":"        };\n        let shard = self.shards.get(&shard_index).ok_or_else(|| {\n            anyhow::anyhow!(\"shard connection info missing for shard {}\", shard_index)\n        })?;\n\n        // Just use the first pageserver in the list. That's good enough for this\n        // convenience routine; if you need more control, like round robin policy or\n        // failover support, roll your own. (As of this writing, we never have more than\n        // one pageserver per shard anyway, but that will change in the future.)\n        let pageserver = shard\n            .pageservers\n            .first()\n            .ok_or(anyhow::anyhow!(\"must have at least one pageserver\"))?;\n\n        let result = match protocol {\n            PageserverProtocol::Grpc => pageserver\n                .grpc_url\n                .as_ref()\n                .ok_or(anyhow::anyhow!(\"no grpc_url for shard {shard_index}\"))?,\n            PageserverProtocol::Libpq => pageserver\n                .libpq_url\n                .as_ref()\n                .ok_or(anyhow::anyhow!(\"no libpq_url for shard {shard_index}\"))?,\n        };\n        Ok(result)\n    }\n}\n\n#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]\npub struct PageserverShardInfo {\n    pub pageservers: Vec<PageserverShardConnectionInfo>,\n}\n\n#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]\npub struct PageserverShardConnectionInfo {\n    pub id: Option<NodeId>,\n    pub libpq_url: Option<String>,","sourceCodeStart":330,"sourceCodeEnd":366,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/libs/compute_api/src/spec.rs#L330-L366","documentation":"Thrown by PageserverConnectionInfo::shard_url() in neon's compute_api crate when the caller asks for the Grpc protocol but the first PageserverShardConnectionInfo entry for that shard has grpc_url: None. Both libpq_url and grpc_url are Option<String> on the shard connection info, which is deserialized from control-plane JSON, so a pageserver that only registered a libpq URL will fail this lookup. It is an anyhow error (not a typed error), surfaced as a plain Err(String) to the compute startup code.","triggerScenarios":"Calling shard_url(shard_number, PageserverProtocol::Grpc) on a PageserverConnectionInfo whose shard map entry contains pageservers whose grpc_url field is null/absent. Happens when the control plane (e.g. neon_local / control_plane_builtin) only fills libpq_url for pageservers, but the compute was configured to prefer or explicitly use the gRPC protocol.","commonSituations":"Running a new compute against an older pageserver that does not expose a gRPC endpoint; mixed-version dev environments where the storage nodes never advertise grpc:// URLs; tests that hand-build PageserverShardConnectionInfo with only one of the two URL fields; toggling prefer_protocol to Grpc before the pageserver deployment actually publishes grpc_url.","solutions":["Check the shard's PageserverShardConnectionInfo and confirm grpc_url is populated; if the pageserver has no gRPC listener, call shard_url with PageserverProtocol::Libpq instead","If you control the control-plane JSON, add the grpc:// URL for each pageserver so the field deserializes as Some(...)","Fall back: try Grpc, and on this error retry the lookup with Libpq before giving up","If you need per-pageserver selection or failover, iterate shard.pageservers yourself instead of using this first()-only convenience routine"],"exampleFix":"// before\nlet url = conn_info.shard_url(shard_number, PageserverProtocol::Grpc)?;\n\n// after\nlet url = conn_info\n    .shard_url(shard_number, PageserverProtocol::Grpc)\n    .or_else(|_| conn_info.shard_url(shard_number, PageserverProtocol::Libpq))?;","handlingStrategy":"validation","validationCode":"// Before calling shard_url with Grpc, confirm the field exists:\nfn has_grpc_url(ci: &PageserverConnectionInfo, sn: ShardNumber) -> bool {\n    let idx = ShardIndex { shard_number: sn, shard_count: ci.shard_count };\n    ci.shards\n        .get(&idx)\n        .and_then(|s| s.pageservers.first())\n        .and_then(|p| p.grpc_url.as_ref())\n        .is_some()\n}","typeGuard":null,"tryCatchPattern":"// In Rust, treat it as a recoverable protocol-availability error:\nmatch conn_info.shard_url(sn, PageserverProtocol::Grpc) {\n    Ok(url) => { /* use grpc */ }\n    Err(e) if e.to_string().contains(\"no grpc_url\") => {\n        let url = conn_info.shard_url(sn, PageserverProtocol::Libpq)?;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Validate that grpc_url is populated for every shard before setting prefer_protocol to Grpc","In control-plane/test code, assert both libpq_url and grpc_url are Some when building PageserverShardConnectionInfo","Prefer checking prefer_protocol and available URLs together instead of hardcoding the protocol at call sites"],"tags":["neon","pageserver","grpc","connection-string","compute-api"],"backgroundTag":"missing-endpoint-url","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}