{"record":{"id":"6158cda6705b18e5","repo":"linera-io/linera-protocol","slug":"failed-to-generate-shard-configs","errorCode":null,"errorMessage":"Failed to generate shard configs","messagePattern":"Failed to generate shard configs","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-service/src/server.rs","lineNumber":938,"sourceCode":"                Persist::persist(&mut config)\n                    .await\n                    .expect(\"Unable to write committee description\");\n                info!(\"Wrote committee config {}\", committee.to_str().unwrap());\n            }\n        }\n\n        ServerCommand::EditShards {\n            server_config_path,\n            num_shards,\n            host,\n            port,\n            metrics_port,\n        } => {\n            let mut server_config =\n                persistent::File::<ValidatorServerConfig>::read(&server_config_path)\n                    .expect(\"Failed to read server config\");\n            let shards = generate_shard_configs(&num_shards, &host, &port, &metrics_port)\n                .expect(\"Failed to generate shard configs\");\n            server_config.internal_network.shards = shards;\n            Persist::persist(&mut server_config)\n                .await\n                .expect(\"Failed to write updated server config\");\n        }\n    }\n}\n\nfn generate_shard_configs(\n    num_shards: &str,\n    host: &str,\n    port: &str,\n    metrics_port: &Option<String>,\n) -> anyhow::Result<Vec<ShardConfig>> {\n    let mut shards = Vec::new();\n    let len = num_shards.len();\n    let num_shards = num_shards\n        .parse::<NonZeroU16>()","sourceCodeStart":920,"sourceCodeEnd":956,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-service/src/server.rs#L920-L956","documentation":"Panic when `generate_shard_configs(&num_shards, &host, &port, &metrics_port)` returns an error in `server edit-shards`. The helper parses `num_shards` as a NonZeroU16, builds a `%` pattern as wide as the num_shards string, and for each shard index substitutes the pattern once into host/port/metrics_port before parsing the ports as u16. The real causes are the inner contexts: 'Failed to parse the number of shards', 'Failed to decode port into an integers', or the metrics-port variant; the panic text is only the outer wrapper.","triggerScenarios":"num_shards given as 0, empty, non-numeric, or above 65535; a port string whose substituted value does not parse as u16 (letters left in the string, or a value above 65535); `--metrics-port` with an unparseable substituted value; a `%` pattern whose width does not match the num_shards digit count so substitution leaves stray `%` characters in the port.","commonSituations":"Shell mangling of unquoted `%` arguments; copy-pasted CLI templates where the pattern width no longer matches num_shards; configs migrated from scripts that used a different shard-numbering convention.","solutions":["Pass a positive integer for num_shards, e.g. `4` (not 0, not empty, at most 65535).","Make sure each substituted port value is an integer in 0..=65535, and that the `%` pattern width equals the number of digits in num_shards (num_shards `12` needs `%%` patterns yielding 01..12).","Quote the --host/--port/--metrics-port arguments in shell so `%` and digits reach the CLI intact.","Pre-validate the arguments in a wrapper script before invoking edit-shards."],"exampleFix":"// before\nlet shards = generate_shard_configs(&num_shards, &host, &port, &metrics_port)\n    .expect(\"Failed to generate shard configs\");\n// after\nlet shards = generate_shard_configs(&num_shards, &host, &port, &metrics_port)\n    .with_context(|| format!(\"invalid shard spec: num_shards={num_shards} host={host} port={port}\"))?;","handlingStrategy":"type-guard","validationCode":"let n = num_shards.parse::<NonZeroU16>().context(\"num_shards must be a non-zero integer <= 65535\")?;\nlet pattern = \"%\".repeat(num_shards.len());\nfor i in 1..=u16::from(n) {\n    let index = format!(\"{i:0width$}\", width = num_shards.len());\n    port.replacen(&pattern, &index, 1).parse::<u16>()\n        .with_context(|| format!(\"port for shard {index} is not a valid u16\"))?;\n    if let Some(mp) = metrics_port.as_ref() {\n        mp.replacen(&pattern, &index, 1).parse::<u16>()\n            .with_context(|| format!(\"metrics_port for shard {index} is not a valid u16\"))?;\n    }\n}","typeGuard":"fn valid_shard_specs(num_shards: &str, port: &str, metrics_port: Option<&str>) -> bool {\n    let Ok(n) = num_shards.parse::<NonZeroU16>() else { return false };\n    let pattern = \"%\".repeat(num_shards.len());\n    (1..=u16::from(n)).all(|i| {\n        let index = format!(\"{i:0width$}\", width = num_shards.len());\n        port.replacen(&pattern, &index, 1).parse::<u16>().is_ok()\n            && metrics_port\n                .map_or(true, |p| p.replacen(&pattern, &index, 1).parse::<u16>().is_ok())\n    })\n}","tryCatchPattern":"let shards = match generate_shard_configs(&num_shards, &host, &port, &metrics_port) {\n    Ok(shards) => shards,\n    Err(err) => {\n        eprintln!(\"error: failed to generate shard configs: {err:#}\");\n        std::process::exit(1);\n    }\n};","preventionTips":["Quote --host/--port/--metrics-port arguments so `%` reaches the CLI intact.","Keep the `%` pattern width equal to the digit count of num_shards (num_shards `12` needs `%%` patterns).","Sanity-check the substitution in shell first, e.g. `echo \"95%%\" | sed 's/%%/01/'`.","Reject 0, empty, and non-numeric num_shards in wrapper scripts before calling edit-shards."],"tags":["rust","validation","cli","port","config"],"backgroundTag":"invalid-config-value","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}