What version of Oxlint are you using?
1.74.0 (also reproduced on 1.11.1; behaviour differs from 1.11.0)
What command did you run?
oxlint -c .oxlintrc.json src/app.js
What does your .oxlintrc.json (or oxlint.config.ts) config file look like?
With src/app.js containing just debugger;.
What happened?
A malformed glob in overrides.files is accepted without any diagnostic, and
whether it matches is undefined — it has changed between releases, silently
changing which rules apply.
1.11.0 rejects the config, naming the problem and suggesting the fix:
Failed to parse configuration file.
x Failed to parse config with error Error("error parsing glob 'src/**/*.{js,ts':
unclosed alternate group; missing '}' (maybe escape '{' with '[{]'?)", line: 0, column: 0)
1.11.1 and later accept it silently. But the effect is not stable:
overrides.files |
1.11.0 |
1.11.1 |
1.74.0 |
| (no override) |
rule fires |
rule fires |
rule fires |
src/**/*.{js,ts} — valid, matches |
rule off |
rule off |
rule off |
other/**/*.{js,ts} — valid, no match |
rule fires |
rule fires |
rule fires |
src/**/*.{js,ts — malformed |
config error |
rule off (matched) |
rule fires (did not match) |
The three control rows are identical across all versions; only the malformed
pattern diverges. So on 1.11.1 the typo silently enabled the override, and by
1.74.0 the same config silently disabled it — no error, no warning, at any
point. A user carrying this typo through an upgrade had the set of rules applied
to their files change underneath them.
The same holds for an unclosed character class (src/[abpp.js vs src/[ab]pp.js).
For contrast, oxlint is strict and helpful about everything else in the same
file — which is what makes the glob case read as "my override is broken" rather
than "my pattern is invalid":
$ oxlint -c bad.json src/ # {"rules": {"no-debugger": "erorr"}}
x Failed to parse config with error Error("Failed to parse rule severity, expected
one of "allow", "off", "deny", "error" or "warn", but got "erorr"")
$ oxlint -c bad2.json src/ # {"overrides": [{"filez": [...]}]}
x Failed to parse config with error Error("unknown field `filez`, expected one of
`files`, `excludeFiles`, `env`, `globals`, `plugins`, `jsPlugins`, `rules`")
Origin
oxlint_v1.11.0 is the last tag without
#12870 (238b183dfa); oxlint_v1.11.1
is the first with it. That matches the behaviour boundary above exactly.
That PR moved GlobSet from globset to fast-glob. Its review thread asked
the right question — whether matching could change — and got a careful answer
about *.txt being auto-expanded to **/*.txt and about */*.txt depth, both
of which the PR then compensated for in GlobSet::new (the **/ prefixing that
is still there today). Validation is a third category that the thread didn't
cover, and it changed too:
// before — invalid patterns surfaced through serde
Self::new(globs).map_err(de::Error::custom)
// after — cannot fail
Ok(Self::new(Vec::<String>::deserialize(deserializer)?))
globset::Glob::new returns Result, so a bad pattern errored at config load.
fast_glob::glob_match(glob: impl AsRef<[u8]>, path: impl AsRef<[u8]>) -> bool
is that crate's entire public surface — there's no parse or validate entry
point, so an invalid pattern has nowhere to be rejected and its result is
whatever the matcher happens to do with it. That also fits the PR carrying
C-cleanup ("not expected to change behavior") and CodSpeed reporting no
performance change: the loss looks incidental rather than chosen.
I'd guess the 1.11.1 → 1.74.0 difference is the workspace moving fast-glob
from 1.0.0 to 1.0.1 over that range, but I haven't bisected it — treat that
as unverified. The observable point stands either way: a patch-level dependency
bump can flip the meaning of a malformed pattern, because nothing defines what
it should mean.
I'm not suggesting reverting #12870 — it
deleted a real pile of boilerplate (the raw: Vec<String> kept beside
globs: globset::GlobSet for --print-config, plus four hand-written impls).
Restoring an error presumably means either a validation pass in GlobSet::new
or an upstream fast-glob API, and whether that's worth it is your call. If the
silent behaviour is known and accepted, please close.
Disclosure per the AI usage policy in CONTRIBUTING.md: this report was drafted
with AI assistance. I have reproduced and verified the behaviour myself before
filing.
What version of Oxlint are you using?
1.74.0 (also reproduced on 1.11.1; behaviour differs from 1.11.0)
What command did you run?
oxlint -c .oxlintrc.json src/app.js
What does your
.oxlintrc.json(oroxlint.config.ts) config file look like?With
src/app.jscontaining justdebugger;.What happened?
A malformed glob in
overrides.filesis accepted without any diagnostic, andwhether it matches is undefined — it has changed between releases, silently
changing which rules apply.
1.11.0 rejects the config, naming the problem and suggesting the fix:
1.11.1 and later accept it silently. But the effect is not stable:
overrides.filessrc/**/*.{js,ts}— valid, matchesother/**/*.{js,ts}— valid, no matchsrc/**/*.{js,ts— malformedThe three control rows are identical across all versions; only the malformed
pattern diverges. So on 1.11.1 the typo silently enabled the override, and by
1.74.0 the same config silently disabled it — no error, no warning, at any
point. A user carrying this typo through an upgrade had the set of rules applied
to their files change underneath them.
The same holds for an unclosed character class (
src/[abpp.jsvssrc/[ab]pp.js).For contrast, oxlint is strict and helpful about everything else in the same
file — which is what makes the glob case read as "my override is broken" rather
than "my pattern is invalid":
Origin
oxlint_v1.11.0is the last tag without#12870 (
238b183dfa);oxlint_v1.11.1is the first with it. That matches the behaviour boundary above exactly.
That PR moved
GlobSetfromglobsettofast-glob. Its review thread askedthe right question — whether matching could change — and got a careful answer
about
*.txtbeing auto-expanded to**/*.txtand about*/*.txtdepth, bothof which the PR then compensated for in
GlobSet::new(the**/prefixing thatis still there today). Validation is a third category that the thread didn't
cover, and it changed too:
globset::Glob::newreturnsResult, so a bad pattern errored at config load.fast_glob::glob_match(glob: impl AsRef<[u8]>, path: impl AsRef<[u8]>) -> boolis that crate's entire public surface — there's no parse or validate entry
point, so an invalid pattern has nowhere to be rejected and its result is
whatever the matcher happens to do with it. That also fits the PR carrying
C-cleanup("not expected to change behavior") and CodSpeed reporting noperformance change: the loss looks incidental rather than chosen.
I'd guess the 1.11.1 → 1.74.0 difference is the workspace moving
fast-globfrom
1.0.0to1.0.1over that range, but I haven't bisected it — treat thatas unverified. The observable point stands either way: a patch-level dependency
bump can flip the meaning of a malformed pattern, because nothing defines what
it should mean.
I'm not suggesting reverting #12870 — it
deleted a real pile of boilerplate (the
raw: Vec<String>kept besideglobs: globset::GlobSetfor--print-config, plus four hand-written impls).Restoring an error presumably means either a validation pass in
GlobSet::newor an upstream
fast-globAPI, and whether that's worth it is your call. If thesilent behaviour is known and accepted, please close.
Disclosure per the AI usage policy in CONTRIBUTING.md: this report was drafted
with AI assistance. I have reproduced and verified the behaviour myself before
filing.