Nested workspace omits sub configs, probably because of extend-exclude
Hey,
I'm working on a complex project, with submodules, with different settings and the meta project excludes the sub ones with extend-exclude. We use it so that CI only care about its repository but while developing we do use the whole collection of repo, even when developing in submodules.
As excluding sub project is an intended behavior, I opened sub projects as new folder in the workspace, awaiting it to force include the sub directories, but it doesn't work as I intended.
The closest issue I found is #414, except this time it is for a whole child folder opened in a workspace.
I've reproduced a minimal exemple to clarify:
ex/
├── .ruff.toml
├── sub
│ ├── .ruff.toml
│ ├── test.py
│ └── foo
│ └── test.py
└── test.py
Where the test.py files are filled with
print("That's a pretty long long long long long long long long long long long long long long long long long long long long long long line")
-> cat ex/.ruff.toml
target-version = "py312"
line-length = 120
extend-exclude = [
"sub",
]
-> cat ex/sub/.ruff.toml
target-version = "py312"
line-length = 120
extend-exclude = [
"foo",
]
What I get:
- formatting
temp/sub/foo/test.pydoes nothing as expected - formatting
temp/test.pybreak the long line as expected - formatting
temp/sub/test.pydoes nothing instead of breaking the line
In the extension output I get (I changed my home to <$HOME> for privacy):
DEBUG main ruff_server::session::index::ruff_settings: Indexing settings for workspace: <$HOME>/ex
2025-05-14 10:05:51.825783382 DEBUG ThreadId(04) ruff_server::session::index::ruff_settings: Loaded settings from: `<$HOME>/ex/.ruff.toml` for `<$HOME>/ex`
2025-05-14 10:05:51.825915250 DEBUG ThreadId(04) ruff_server::session::index::ruff_settings: Ignored path via `extend-exclude`: <$HOME>/ex/sub
2025-05-14 10:05:51.826834994 INFO main ruff_server::session::index: Registering workspace: <$HOME>/ex
2025-05-14 10:05:51.826868981 DEBUG main ruff_server::session::index::ruff_settings: Indexing settings for workspace: <$HOME>/ex/sub
2025-05-14 10:05:51.827300462 DEBUG main ruff_server::session::index::ruff_settings: Loaded settings from: `<$HOME>/ex/.ruff.toml`
2025-05-14 10:05:51.827922641 DEBUG ThreadId(16) ruff_server::session::index::ruff_settings: Ignored path via `extend-exclude`: <$HOME>/ex/sub
2025-05-14 10:05:51.830188073 INFO main ruff_server::session::index: Registering workspace: <$HOME>/ex/sub
2025-05-14 10:05:51.836248830 INFO ruff:main ruff_server::server: Configuration file watcher successfully registered
It seems to me a bug occurs right after the Indexing settings for workspace: <$HOME>/ex/sub, I feel like the intended behavior would be loading ex/sub/.ruff.toml.
Also feel free to edit the title I struggle to find a representative one
Thanks for the detailed write up. I think this is working as intended for performance reasons.
Ruff tries to skip over directories that are excluded. For example, ruff skips over .venv and it won't show any lint warnings even if there's a ruff.toml in one of the packages that you installed (there shouldn't be any but there could).
What you're asking for would require ruff to always walk the entire directory tree because, maybe, there's some .ruff.toml somewhere. This would be very expensive because folders like .venv tend to contain many files.
Ruff's behavior here is consistent with git's where you can't unignore files in a previously ignored directory because git doesn't traverse the directory (for performance reasons).
You could make use of VS code's multi folder feature and adding individual folders (e.g. opening sub explicitly)
Hum there might be a misunderstanding, I get the performance reasons and tried to force include the folder :
As excluding sub project is an intended behavior, I opened sub projects as new folder in the workspace, awaiting it to force include the sub directories, but it doesn't work as I intended.
Except if what you call multi folder feature is different of what I call Workspace ? I must admit that I don't use this kind of feature really often...
But the sequence
2025-05-14 10:05:51.826868981 DEBUG main ruff_server::session::index::ruff_settings: Indexing settings for workspace: <$HOME>/ex/sub
2025-05-14 10:05:51.827300462 DEBUG main ruff_server::session::index::ruff_settings: Loaded settings from: `<$HOME>/ex/.ruff.toml`
really make me feel something went wrong.
Well well well... The issue actually probably is directly in ruff. Applying the following dirty patch I got it to work:
diff --git a/crates/ruff_server/src/session/index/ruff_settings.rs b/crates/ruff_server/src/session/index/ruff_settings.rs
index 3156d6e0f..484909330 100644
--- a/crates/ruff_server/src/session/index/ruff_settings.rs
+++ b/crates/ruff_server/src/session/index/ruff_settings.rs
@@ -177,14 +177,8 @@ impl RuffSettingsIndex {
let mut respect_gitignore = None;
let mut index = BTreeMap::default();
- // If this is *not* the default workspace, then we should skip the workspace root itself
- // because it will be resolved when walking the workspace directory tree. This is done by
- // the `WalkBuilder` below.
- let should_skip_workspace = usize::from(!is_default_workspace);
-
- // Add any settings from above the workspace root, skipping the workspace root itself if
- // this is *not* the default workspace.
- for directory in root.ancestors().skip(should_skip_workspace) {
+ // Add any settings from above the workspace root.
+ for directory in root.ancestors() {
match settings_toml(directory) {
Ok(Some(pyproject)) => {
match ruff_workspace::resolver::resolve_root_settings(
@@ -308,6 +302,10 @@ impl RuffSettingsIndex {
);
return WalkState::Skip;
}
+ if directory == root {
+ // Skip root as already included
+ return WalkState::Continue;
+ }
}
match settings_toml(&directory) {
It looks like the parent's yaml takes precedence over root folder's yaml because scanned first... the should_skip_workspace seem to actually kills this feature.
Here are the logs with my freshly built custom server:
2025-05-20 23:07:30.849417257 DEBUG Negotiated position encoding: UTF16
2025-05-20 23:07:30.849641705 DEBUG Indexing settings for workspace: <$HOME>/ex
2025-05-20 23:07:30.857199904 DEBUG Loaded settings from: `<$HOME>/ex/.ruff.toml`
2025-05-20 23:07:30.865953994 DEBUG Ignored path via `extend-exclude`: <$HOME>/ex/sub
2025-05-20 23:07:30.876157818 INFO Registering workspace: <$HOME>/ex
2025-05-20 23:07:30.876319587 DEBUG Indexing settings for workspace: <$HOME>/ex/sub
2025-05-20 23:07:30.880635114 DEBUG Loaded settings from: `<$HOME>/ex/sub/.ruff.toml`
2025-05-20 23:07:30.888796223 DEBUG Ignored path via `extend-exclude`: <$HOME>/ex/sub/foo
2025-05-20 23:07:30.892008207 INFO Registering workspace: <$HOME>/ex/sub
2025-05-20 23:07:30.894010274 DEBUG Included path via `include`: <$HOME>/ex/test.py
2025-05-20 23:07:30.894039900 DEBUG Included path via `include`: <$HOME>/ex/sub/test.py
2025-05-20 23:07:30.895065355 INFO Configuration file watcher successfully registered
2025-05-20 23:09:35.696582001 WARN Received notification $/setTrace which does not have a handler.
2025-05-20 23:16:33.251551310 DEBUG Included path via `include`: <$HOME>/ex/test.py
Should I close this issue and open an issue or a PR there ?
I'm facing the same issue and maybe can elaborate more on it. If I simply open the subproject folder sub in VS code, I wouldn't expect settings from any parent folders (outside the current workspace) to take place, as I'm purely developing the sub project. What happens now with the native server, is that it still reads the settings from the parent directory (which is outside the current workspace), exludes the sub folder, and thus it never sees the correct config sub/.ruff.toml. Everything works as expected on command line. Also, everything works as expected in VS code if I set "ruff.nativeServer": false in VS Code settings.