I'm trying to understand how the UV tool identifies the project root when it creates a virtual environment (.venv) folder. I initially thought that it would check for a valid `pyproject.toml` file and work its way up the directory tree to find it. However, I learned about UV's workspace concept and realized I was mistaken. Here's what I discovered:
- A workspace consists of a parent `pyproject.toml` and multiple child `pyproject.toml` files.
- The virtual environment and lock file are only created in the parent folder, meaning all child directories share dependencies.
- Child `pyproject.toml` files don't indicate they're part of the workspace. Only the parent file keeps track of which children belong to it.
I'm looking for a similar functionality in my own tool where it can find a specific environment file at the project's root. I managed to get something working, but primarily by chance. I intended to stop looking at the root if there were no nested `pyproject.toml` files, but I want to traverse up to the system root if needed, and only stop searching if I find the file.
5 Answers
Check out the source code of the Python dotenv package. It does something similar by looking for the first `.env` file in parent directories of the current working directory if you don't specify one.
UV isn't doing anything magical; it's simply following its workspace specification. It only considers the top-level `pyproject.toml` as the source of truth and categorizes everything underneath it as part of the workspace. So if you want to replicate this behavior in your tool, just keep moving upward until you hit the first `pyproject.toml` and use that as your root.
It sounds like you're pretty much on the right track! You should keep moving up the directory tree. If you come across a `pyproject.toml`, take note of it and keep searching higher. There’s a field in there that lists its child members — you might need to run `uv init` in a subdirectory to populate that field in the parent file.
UV keeps moving up until it finds a valid workspace root, which is basically a `pyproject.toml` that has a workspace table and includes the package in either its members or exclude list. You can find more about this in a related GitHub issue.
Just keep digging up the whole tree! Directory trees aren’t generally very deep, so I doubt performance will be a huge concern.

I checked that out, but it seems to only look in the current working directory. I tried using `dotenv run...` from a subdirectory, but it didn't pick up the `.env` file in the parent directory.