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 withView on GitHub (pinned to d977e1c316)
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.
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
- 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.
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
- invalid regex patterns
- invalid nextest summary regex
- invalid nextest starting regex
- stdout streaming thread panicked
- stderr streaming thread panicked
AI-assisted analysis of rtk-ai/rtk@d977e1c316 (2026-08-16).
Data as JSON: /api/errors/82f08ba2e291854f.
Report an issue: GitHub.