denoland/deno · error · anyhow::Error
task name filter ' ' uses an exclusion group '(!...)' but…
Error message
task name filter '{}' uses an exclusion group '(!...)' but has no wildcard '*' to exclude from What it means
deno task accepts name filters with a trailing exclusion group, e.g. `test:*(!e2e|interactive)` — each capture made by a `*` is checked against the exclusion list (arg_to_task_name_filter, cli/tools/task.rs:1564-1594). An exclusion group is only meaningful when the pattern part contains a `*`; inputs like `build(!dev)` have nothing to exclude from, so the filter is rejected before any task runs.
Solutions
- Attach the exclusion to a wildcard family: `build:*(!dev)` excludes tasks whose capture equals dev.
- For "everything except", use the bare wildcard: `*(!dev)` matches every task except exactly `dev`.
- If you meant one exact task, drop the group entirely: `deno task build`.
Example fix
# before $ deno task 'build(!dev)' error: task name filter 'build(!dev)' uses an exclusion group '(!...)' but has no wildcard '*' to exclude from # after — exclude one capture from a wildcard family deno task 'build:*(!dev)' # or run every task except `dev` deno task '*(!dev)'
Defensive patterns
Strategy: validation
Validate before calling
// Validate a deno task filter before shelling out
function validateFilter(f: string): void {
const m = f.match(/^(.*)\(!([^)]*)\)$/);
if (!m) return; // no exclusion group
const pattern = m[1];
if (!pattern.includes("*")) {
throw new Error(`exclusion group '(!...)' requires a '*' in the pattern: ${f}`);
}
} Type guard
const isExclusionWithWildcard = (f: string) =>
!/\(![^)]*\)$/.test(f) || f.slice(0, f.lastIndexOf("(!")).includes("*"); Prevention
- Remember the grammar: pattern*(!excl|excl) — exclusions filter what the * captures.
- Quote filters so (, !, | and the * survive the shell unexpanded.
- Dry-run with bare `deno task` to see which names a filter would match.
When it happens
Trigger: deno task 'build(!dev)', deno task 'check(!lint)' — any filter whose text before `(!` contains no wildcard; usually written by someone expecting npm-style negation ("run everything except").
Common situations: Assuming `(!x)` alone means "all tasks except x"; shell quoting that drops a `*` (unquoted globs get expanded by the shell); filters copy-pasted from docs without their wildcard prefix.
Related errors
- Task not found
- An executable name was not provided. One could not be…
- app name resolves its to , which is already part of the…
- bundle identifier must be in reverse-DNS form (e.g…
- bundle identifier must match [A-Za-z0-9.-]+, but contains
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/abf61646b6fd132a.
Report an issue: GitHub.
Appendix: source
Thrown at cli/tools/task.rs:1581
fn arg_to_task_name_filter(
input: &str,
) -> Result<TaskNameFilter<'_>, AnyError> {
// Parse an optional trailing exclusion group of the form `(!a|b|c)`.
// Exclusion values are matched against what each `*` in the pattern
// captures, e.g. `test:*(!e2e|interactive)` excludes `test:e2e` and
// `test:interactive` but still matches `test:unit`.
let (pattern, exclusions): (&str, Vec<&str>) = match input.rfind("(!") {
Some(open) if input.ends_with(')') => {
let inner = &input[open + 2..input.len() - 1];
(&input[..open], inner.split('|').collect())
}
_ => (input, Vec::new()),
};
if !pattern.contains('*') {
if !exclusions.is_empty() {
return Err(anyhow!(
"task name filter '{}' uses an exclusion group '(!...)' but has no wildcard '*' to exclude from",
input
));
}
return Ok(TaskNameFilter::Exact(input));
}
let mut regex_str = regex::escape(pattern);
regex_str = regex_str.replace("\\*", "(.*)");
regex_str = format!("^{}", regex_str);
let re = Regex::new(®ex_str)?;
let exclusions = exclusions.into_iter().map(String::from).collect();
Ok(TaskNameFilter::Regex { re, exclusions })
}
#[derive(Debug)]
enum TaskNameFilter<'s> {
Exact(&'s str),View on GitHub (pinned to 9ad36f7a2c)