rtk-ai/rtk · error

invalid regex

Error message

invalid regex

What it means

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.

Source

Thrown at src/discover/registry.rs:60

        "Tests" => 800,
        "Files" => 100,
        "Build" => 300,
        "Infra" => 120,
        "Network" => 150,
        "GitHub" => 200,
        "GitLab" => 200,
        "PackageManager" => 150,
        _ => 150,
    }
}

static REGEX_SET: LazyLock<RegexSet> = LazyLock::new(|| {
    RegexSet::new(RULES.iter().map(|r| r.pattern)).expect("invalid regex patterns")
});
static COMPILED: LazyLock<Vec<Regex>> = LazyLock::new(|| {
    RULES
        .iter()
        .map(|r| Regex::new(r.pattern).expect("invalid regex"))
        .collect()
});
static ENV_PREFIX: LazyLock<Regex> = LazyLock::new(|| {
    let double_quoted = r#""(?:[^"\\]|\\.)*""#;
    let single_quoted = r#"'(?:[^'\\]|\\.)*'"#;
    let unquoted = r#"[^\s]*"#;
    let env_value = format!("(?:{}|{}|{})", double_quoted, single_quoted, unquoted);
    let env_assign = format!(r#"[A-Z_][A-Z0-9_]*={}"#, env_value);
    Regex::new(&format!(r#"^(?:sudo\s+|env\s+|{}\s+)+"#, env_assign)).unwrap()
});
// Git global options that appear before the subcommand: -C <path>, -c <key=val>,
// --git-dir <dir>, --work-tree <dir>, and flag-only options (#163)
static GIT_GLOBAL_OPT: LazyLock<Regex> = LazyLock::new(|| {
    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()
});
// Issue #1362: each capture expects a SINGLE file argument (`\S+$`). Multi-file
// invocations like `head -3 a b c` fail to match so the segment is passed through
// to the native `head`/`tail` binary — which already handles multi-file with

View on GitHub (pinned to d977e1c316)

Solutions

  1. 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.
  2. Fix the pattern in src/discover/rules.rs — no lookarounds or backreferences, escape metacharacters, balance groups.
  3. 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.
  4. Add the regression test from error 43's fix so invalid rules fail CI instead of production.

Example fix

// before (src/discover/registry.rs:57)
static COMPILED: LazyLock<Vec<Regex>> = LazyLock::new(|| {
    RULES
        .iter()
        .map(|r| Regex::new(r.pattern).expect("invalid regex"))
        .collect()
});

// after: name the failing pattern
static COMPILED: LazyLock<Vec<Regex>> = LazyLock::new(|| {
    RULES
        .iter()
        .map(|r| {
            Regex::new(r.pattern)
                .unwrap_or_else(|e| panic!("invalid rule regex {:?}: {}", r.pattern, e))
        })
        .collect()
});
Defensive patterns

Strategy: validation

Validate before calling

// Same guard covers both REGEX_SET and COMPILED — they compile identical patterns:
#[test]
fn all_rule_patterns_compile() {
    for r in RULES {
        regex::Regex::new(r.pattern)
            .unwrap_or_else(|e| panic!("bad rule pattern {:?}: {}", r.pattern, e));
    }
    regex::RegexSet::new(RULES.iter().map(|r| r.pattern)).expect("RegexSet must build if all Regex::new succeed");
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of rtk-ai/rtk@d977e1c316 (2026-08-16). Data as JSON: /api/errors/82f08ba2e291854f. Report an issue: GitHub.