{"record":{"id":"d861c9361acc0e38","repo":"rtk-ai/rtk","slug":"invalid-nextest-starting-regex","errorCode":null,"errorMessage":"invalid nextest starting regex","messagePattern":"invalid nextest starting regex","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/cmds/rust/cargo_cmd.rs","lineNumber":628,"sourceCode":"    }\n    let mut block = header.clone();\n    if !body.is_empty() {\n        block.push('\\n');\n        block.push_str(&body.join(\"\\n\"));\n    }\n    failures.push(block);\n    header.clear();\n    body.clear();\n}\n\n/// Filter cargo nextest output - show failures + compact summary\nfn filter_cargo_nextest(output: &str) -> String {\n    let summary_re = regex::Regex::new(\n        r\"Summary \\[\\s*([\\d.]+)s\\]\\s+(\\d+) tests? run:\\s+(\\d+) passed(?:,\\s+(\\d+) failed)?(?:,\\s+(\\d+) skipped)?\"\n    ).expect(\"invalid nextest summary regex\");\n\n    let starting_re = regex::Regex::new(r\"Starting \\d+ tests? across (\\d+) binar(?:y|ies)\")\n        .expect(\"invalid nextest starting regex\");\n\n    let mut failures: Vec<String> = Vec::new();\n    let mut in_failure_block = false;\n    let mut past_summary = false;\n    let mut current_failure_header = String::new();\n    let mut current_failure_body = Vec::new();\n    let mut summary_line = String::new();\n    let mut binaries: u32 = 0;\n    let mut has_cancel_line = false;\n\n    for line in output.lines() {\n        let trimmed = line.trim();\n\n        // Strip compilation noise\n        if trimmed.starts_with(\"Compiling\")\n            || trimmed.starts_with(\"Downloading\")\n            || trimmed.starts_with(\"Downloaded\")\n            || trimmed.starts_with(\"Finished\")","sourceCodeStart":610,"sourceCodeEnd":646,"githubUrl":"https://github.com/rtk-ai/rtk/blob/d977e1c31621fe8704e6500ceeb9c7a0de2b6836/src/cmds/rust/cargo_cmd.rs#L610-L646","documentation":"This panic fires when regex::Regex::new cannot compile the hardcoded nextest 'Starting' pattern (r\"Starting \\d+ tests? across (\\d+) binar(?:y|ies)\") at src/cmds/rust/cargo_cmd.rs:627-628, inside filter_cargo_nextest. The pattern is a string literal recompiled at runtime on every call, so the panic is only reachable after a developer edits the literal into an invalid regex — child output cannot trigger it. Like its sibling summary regex, it violates the repo's LazyLock rule for regex statics.","triggerScenarios":"Any invocation that calls filter_cargo_nextest (`rtk cargo nextest` or a hooked `cargo nextest`) after the literal was made invalid — e.g. renaming the `binar(?:y|ies)` alternation to `binar(y|ies` (unbalanced paren) or escaping a metachar wrongly. Regex::new executes unconditionally before the line loop, so the process panics even for output with no 'Starting' line.","commonSituations":"Contributor tweaks the pattern to also match nextest's 'Starting N tests across M binaries (M features)' variant, drops a closing paren or bracket, cargo build still succeeds (pattern is runtime data), and the first `rtk cargo nextest` run panics with this message — often noticed only in CI smoke tests or by an agent proxying test runs.","solutions":["Validate the edited literal in isolation (scratch program or `cargo test` with a compile-only test) to get the precise syntax error, then fix it at src/cmds/rust/cargo_cmd.rs:627.","Hoist into `static STARTING_RE: LazyLock<Regex>` per the repo regex convention, eliminating recompilation per call.","Add a unit test asserting the regex matches \"Starting 42 tests across 3 binaries\" so regressions fail in `cargo test`, not in production runs."],"exampleFix":"// before (src/cmds/rust/cargo_cmd.rs:627)\nlet starting_re = regex::Regex::new(r\"Starting \\d+ tests? across (\\d+) binar(?:y|ies)\")\n    .expect(\"invalid nextest starting regex\");\n\n// after\nstatic STARTING_RE: std::sync::LazyLock<regex::Regex> = std::sync::LazyLock::new(|| {\n    regex::Regex::new(r\"Starting \\d+ tests? across (\\d+) binar(?:y|ies)\")\n        .expect(\"invalid nextest starting regex\")\n});\n\n#[test]\nfn nextest_starting_regex_matches() {\n    assert!(STARTING_RE.is_match(\"Starting 42 tests across 3 binaries\"));\n}","handlingStrategy":"validation","validationCode":"#[test]\nfn nextest_starting_regex_compiles_and_matches() {\n    let starting = regex::Regex::new(r\"Starting \\d+ tests? across (\\d+) binar(?:y|ies)\")\n        .expect(\"invalid nextest starting regex\");\n    assert!(starting.is_match(\"Starting 42 tests across 3 binaries\"));\n    assert!(starting.is_match(\"Starting 1 test across 1 binary\"));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Move the pattern into a `static STARTING_RE: LazyLock<Regex>` next to the filter so it is a single reviewed constant instead of an inline literal compiled per call.","Cover both pluralizations (binary/binaries, test/tests) in the compile-and-match unit test, since that alternation is the part most often edited incorrectly.","Run `cargo test nextest` after touching any nextest pattern; the filter's tests are the only thing standing between a typo and a runtime panic.","Keep raw strings (r\"...\") for all patterns so backslashes are regex escapes only — mixing escaped normal strings is a classic way to produce an invalid pattern."],"tags":["regex","cargo","nextest","panic","expect","compile-time-constant"],"backgroundTag":null,"analyzedSha":"d977e1c31621fe8704e6500ceeb9c7a0de2b6836","analyzedAt":"2026-08-16T05:40:46.291Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}