{"record":{"id":"82f08ba2e291854f","repo":"rtk-ai/rtk","slug":"invalid-regex","errorCode":null,"errorMessage":"invalid regex","messagePattern":"invalid regex","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/discover/registry.rs","lineNumber":60,"sourceCode":"        \"Tests\" => 800,\n        \"Files\" => 100,\n        \"Build\" => 300,\n        \"Infra\" => 120,\n        \"Network\" => 150,\n        \"GitHub\" => 200,\n        \"GitLab\" => 200,\n        \"PackageManager\" => 150,\n        _ => 150,\n    }\n}\n\nstatic REGEX_SET: LazyLock<RegexSet> = LazyLock::new(|| {\n    RegexSet::new(RULES.iter().map(|r| r.pattern)).expect(\"invalid regex patterns\")\n});\nstatic COMPILED: LazyLock<Vec<Regex>> = LazyLock::new(|| {\n    RULES\n        .iter()\n        .map(|r| Regex::new(r.pattern).expect(\"invalid regex\"))\n        .collect()\n});\nstatic ENV_PREFIX: LazyLock<Regex> = LazyLock::new(|| {\n    let double_quoted = r#\"\"(?:[^\"\\\\]|\\\\.)*\"\"#;\n    let single_quoted = r#\"'(?:[^'\\\\]|\\\\.)*'\"#;\n    let unquoted = r#\"[^\\s]*\"#;\n    let env_value = format!(\"(?:{}|{}|{})\", double_quoted, single_quoted, unquoted);\n    let env_assign = format!(r#\"[A-Z_][A-Z0-9_]*={}\"#, env_value);\n    Regex::new(&format!(r#\"^(?:sudo\\s+|env\\s+|{}\\s+)+\"#, env_assign)).unwrap()\n});\n// Git global options that appear before the subcommand: -C <path>, -c <key=val>,\n// --git-dir <dir>, --work-tree <dir>, and flag-only options (#163)\nstatic GIT_GLOBAL_OPT: LazyLock<Regex> = LazyLock::new(|| {\n    Regex::new(r\"^(?:(?:-C\\s+\\S+|-c\\s+\\S+|--git-dir(?:=\\S+|\\s+\\S+)|--work-tree(?:=\\S+|\\s+\\S+)|--no-pager|--no-optional-locks|--bare|--literal-pathspecs)\\s+)+\").unwrap()\n});\n// Issue #1362: each capture expects a SINGLE file argument (`\\S+$`). Multi-file\n// invocations like `head -3 a b c` fail to match so the segment is passed through\n// to the native `head`/`tail` binary — which already handles multi-file with","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/rtk-ai/rtk/blob/d977e1c31621fe8704e6500ceeb9c7a0de2b6836/src/discover/registry.rs#L42-L78","documentation":"This panic fires in the COMPILED LazyLock (src/discover/registry.rs:57-62) when Regex::new fails for any single RULES pattern, converted by `.expect(\"invalid regex\")`. It is the per-rule twin of the REGEX_SET guard above it: both compile the exact same patterns with the same engine, so if REGEX_SET initialized successfully, COMPILED cannot fail — this branch is effectively a duplicate guard that only fires (like its sibling) after someone edits a rule in src/discover/rules.rs into an invalid regex. The LazyLock defers the abort to the first code path that needs the matching rule's captures.","triggerScenarios":"Same root cause as error 43: an invalid pattern added to RULES in src/discover/rules.rs (unbalanced groups, bad escapes, or PCRE-only constructs like (?=...) lookaheads and \\1 backreferences that the rust regex crate rejects). In practice you hit REGEX_SET's expect first because classification checks the set before indexing individual rules; this one would surface only if that ordering changed or the set were bypassed.","commonSituations":"Contributors adding new command rewrite rules with patterns copied from other regex dialects; CI stays green because patterns are runtime data; the panic then appears on the first hooked command or `rtk discover` run, deferred from the edit by LazyLock initialization.","solutions":["Identify the bad pattern by iterating RULES with Regex::new in a scratch test (or run `cargo test`, which exercises registry classification) and read the exact regex error.","Fix the pattern in src/discover/rules.rs — no lookarounds or backreferences, escape metacharacters, balance groups.","Consolidate the two guards: build the Vec<Regex> once, validate it, and construct the RegexSet from the same compiled inputs (or keep both but add the all_rule_patterns_compile test) so there is a single point of failure with a pattern-naming message.","Add the regression test from error 43's fix so invalid rules fail CI instead of production."],"exampleFix":"// before (src/discover/registry.rs:57)\nstatic COMPILED: LazyLock<Vec<Regex>> = LazyLock::new(|| {\n    RULES\n        .iter()\n        .map(|r| Regex::new(r.pattern).expect(\"invalid regex\"))\n        .collect()\n});\n\n// after: name the failing pattern\nstatic COMPILED: LazyLock<Vec<Regex>> = LazyLock::new(|| {\n    RULES\n        .iter()\n        .map(|r| {\n            Regex::new(r.pattern)\n                .unwrap_or_else(|e| panic!(\"invalid rule regex {:?}: {}\", r.pattern, e))\n        })\n        .collect()\n});","handlingStrategy":"validation","validationCode":"// Same guard covers both REGEX_SET and COMPILED — they compile identical patterns:\n#[test]\nfn all_rule_patterns_compile() {\n    for r in RULES {\n        regex::Regex::new(r.pattern)\n            .unwrap_or_else(|e| panic!(\"bad rule pattern {:?}: {}\", r.pattern, e));\n    }\n    regex::RegexSet::new(RULES.iter().map(|r| r.pattern)).expect(\"RegexSet must build if all Regex::new succeed\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Treat COMPILED and REGEX_SET as one invariant: both are built from RULES with the same engine, so a single all-patterns-compile test defends both guards.","Validate new rule patterns locally with a one-line `Regex::new(pat)` check (scratch test or `cargo test`) before committing the rules file.","Avoid PCRE-only syntax (lookaround, backreferences) when authoring rules — the most frequent source of regex-crate compile failures.","If refactoring registry.rs, consider deriving RegexSet from the already-compiled Vec<Regex> patterns so the two statics cannot drift or fail independently."],"tags":["regex","lazylock","panic","rules","discover","duplicate-guard"],"backgroundTag":null,"analyzedSha":"d977e1c31621fe8704e6500ceeb9c7a0de2b6836","analyzedAt":"2026-08-16T05:40:46.291Z","schemaVersion":2},"datasetVersion":"2026-08-16T08:17:34.114Z"}