{"record":{"id":"abf61646b6fd132a","repo":"denoland/deno","slug":"task-name-filter-uses-an-exclusion-group","errorCode":null,"errorMessage":"task name filter '{}' uses an exclusion group '(!...)' but has no wildcard '*' to exclude from","messagePattern":"task name filter '(.+?)' uses an exclusion group '\\(!\\.\\.\\.\\)' but has no wildcard '\\*' to exclude from","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"cli/tools/task.rs","lineNumber":1581,"sourceCode":"\nfn arg_to_task_name_filter(\n  input: &str,\n) -> Result<TaskNameFilter<'_>, AnyError> {\n  // Parse an optional trailing exclusion group of the form `(!a|b|c)`.\n  // Exclusion values are matched against what each `*` in the pattern\n  // captures, e.g. `test:*(!e2e|interactive)` excludes `test:e2e` and\n  // `test:interactive` but still matches `test:unit`.\n  let (pattern, exclusions): (&str, Vec<&str>) = match input.rfind(\"(!\") {\n    Some(open) if input.ends_with(')') => {\n      let inner = &input[open + 2..input.len() - 1];\n      (&input[..open], inner.split('|').collect())\n    }\n    _ => (input, Vec::new()),\n  };\n\n  if !pattern.contains('*') {\n    if !exclusions.is_empty() {\n      return Err(anyhow!(\n        \"task name filter '{}' uses an exclusion group '(!...)' but has no wildcard '*' to exclude from\",\n        input\n      ));\n    }\n    return Ok(TaskNameFilter::Exact(input));\n  }\n\n  let mut regex_str = regex::escape(pattern);\n  regex_str = regex_str.replace(\"\\\\*\", \"(.*)\");\n  regex_str = format!(\"^{}\", regex_str);\n  let re = Regex::new(&regex_str)?;\n  let exclusions = exclusions.into_iter().map(String::from).collect();\n  Ok(TaskNameFilter::Regex { re, exclusions })\n}\n\n#[derive(Debug)]\nenum TaskNameFilter<'s> {\n  Exact(&'s str),","sourceCodeStart":1563,"sourceCodeEnd":1599,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/cli/tools/task.rs#L1563-L1599","documentation":"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.","triggerScenarios":"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\").","commonSituations":"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.","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`."],"exampleFix":"# before\n$ deno task 'build(!dev)'\nerror: task name filter 'build(!dev)' uses an exclusion group '(!...)' but has no wildcard '*' to exclude from\n\n# after — exclude one capture from a wildcard family\ndeno task 'build:*(!dev)'\n# or run every task except `dev`\ndeno task '*(!dev)'","handlingStrategy":"validation","validationCode":"// Validate a deno task filter before shelling out\nfunction validateFilter(f: string): void {\n  const m = f.match(/^(.*)\\(!([^)]*)\\)$/);\n  if (!m) return; // no exclusion group\n  const pattern = m[1];\n  if (!pattern.includes(\"*\")) {\n    throw new Error(`exclusion group '(!...)' requires a '*' in the pattern: ${f}`);\n  }\n}","typeGuard":"const isExclusionWithWildcard = (f: string) =>\n  !/\\(![^)]*\\)$/.test(f) || f.slice(0, f.lastIndexOf(\"(!\")).includes(\"*\");","tryCatchPattern":null,"preventionTips":["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."],"tags":["task","filter","glob","cli"],"backgroundTag":"invalid-glob-pattern","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}