{"record":{"id":"04c25d6dcec46c1f","repo":"risingwavelabs/risingwave","slug":"plan-format-illegal","errorCode":null,"errorMessage":"plan \"{}\" format illegal","messagePattern":"plan \"(.+?)\" format illegal","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/ctl/src/cmd_impl/meta/reschedule.rs","lineNumber":154,"sourceCode":"    }\n\n    Ok(())\n}\n\n// It will match formats like `1:[1:+1,2:-1,3:1];2:[1:1,2:1]`, indicating which workers' actors need to change in quantity for each fragment.\nfn parse_plan(mut plan: String) -> Result<HashMap<u32, PbWorkerReschedule>> {\n    let mut reschedules = HashMap::new();\n    let regex = Regex::new(r\"^(\\d+):\\[((?:\\d+:[+-]?\\d+,?)+)]$\")?;\n    plan.retain(|c| !c.is_whitespace());\n\n    for fragment_reschedule_plan in plan.split(';') {\n        if fragment_reschedule_plan.is_empty() {\n            continue;\n        }\n\n        let captures = regex\n            .captures(fragment_reschedule_plan)\n            .ok_or_else(|| anyhow!(\"plan \\\"{}\\\" format illegal\", fragment_reschedule_plan))?;\n\n        let fragment_id = captures\n            .get(1)\n            .and_then(|mat| mat.as_str().parse::<u32>().ok())\n            .ok_or_else(|| anyhow!(\"plan \\\"{}\\\" does not have a valid fragment id\", plan))?;\n\n        let worker_changes: Vec<&str> = captures[2].split(',').collect();\n\n        let mut worker_actor_diff = HashMap::new();\n        for worker_change in &worker_changes {\n            let (worker_id, count) = worker_change.split(':').collect_tuple::<(_, _)>().unwrap();\n            let worker_id = worker_id.parse().unwrap();\n            let count = count.parse().unwrap();\n\n            if let Some(dup_change) = worker_actor_diff.insert(worker_id, count) {\n                anyhow::bail!(\n                    \"duplicate worker id {worker_id} in plan, prev {worker_id} -> {dup_change}\",\n                );","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/ctl/src/cmd_impl/meta/reschedule.rs#L136-L172","documentation":"parse_plan splits the reschedule plan into per-fragment strings and matches each against a regex to extract fragment id and worker changes. This error means a fragment plan chunk did not match the expected format, so it cannot be parsed.","triggerScenarios":"Passing a reschedule plan string whose fragment sections deviate from the expected pattern (missing brackets, wrong separators, whitespace or extra text where the regex expects 'fragment_id [worker:count,...]').","commonSituations":"Hand-editing plan text copied from meta logs; older/newer plan formats from mismatched risectl and meta versions; truncation when pasting multi-line plans.","solutions":["Reformat the plan to the expected per-fragment syntax the regex expects","Copy the plan directly from the meta tooling output instead of retyping it","Match risectl and meta versions so plan formats agree"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if !plan.lines().all(|l| l.trim().is_empty() || FRAGMENT_RE.is_match(l.trim())) {\n    return Err(anyhow!(\"plan lines must match fragment plan format\"));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Copy plans verbatim from meta output","Avoid hand-editing plan text","Keep risectl and meta node versions in sync"],"tags":["rust","risingwave","parsing","regex"],"backgroundTag":"invalid-argument-format","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}