Repository: dbt-labs/dbt-core

Issue:
[CT-2665] [Regression] For the list command the path selector does not respect the --project-dir param
### Is this a regression in a recent version of dbt-core?

- [X] I believe this is a regression in dbt-core functionality
- [X] I have searched the existing issues, and I could not find an existing issue for this regression

### Current Behavior

in dbt 1.5.1, the `path` selector does not seem to work with `--project-dir`. 

that is, given a directory structure:
- dbt_projects
  - project1
  - project2
  
from the dbt_projects root, the following works in 1.4.6 but not 1.5.1.



### Expected/Previous Behavior

I expect the path selector to work as in the previous versions of dbt.



### Steps To Reproduce

1. in a dbt-core==1.5.1 environement..
2. with a root/<dbt_projects> structure
3. from root, try to `dbt ls -s path:path/to/models/dir/ --project-dir <one of the dbt_sub_projects>`
4. it will find no nodes.

repeat this for dbt-core==1.4.6 which will find the expected nodes

### Relevant log output

_No response_

### Environment

```markdown
- OS: macOS
- Python: 3.11.1
- dbt (working version): 1.4.6
- dbt (regression version): 1.5.1
```


### Which database adapter are you using with dbt?

snowflake

### Additional Context

_No response_


Discussion:
re-read the upgrade docs and closed as I thought I'd missed something, however this did not work either:

```dbt ls --project-dir=snowflake/snowflake_dbt -s path:models/marts/finance```

So I think this might be legit
Thanks for reporting this @rudeb0y.

This is legit, and I was able to reproduce it as you described 👍

It's possible this is similar to https://github.com/dbt-labs/dbt-core/issues/7465 (but unique).

<details>
<summary>

### Reprex</summary>

Tree structure:
```
dbt_projects
└── project1
    ├── dbt_project.yml
    └── models
        └── my_model.sql
```

`dbt_projects/project1/models/my_model.sql`
```sql
select 1 as id
```

This works with dbt 1.4:
```shell
$ dbt ls --project-dir dbt_projects/project1 -s path:models

my_project.my_model
```

But not 1.5:
```shell
$ dbt ls --project-dir dbt_projects/project1 -s path:models

02:19:46  Running with dbt=1.5.0
02:19:47  Found 1 model, 0 tests, 0 snapshots, 0 analyses, 307 macros, 0 operations, 0 seed files, 0 sources, 0 exposures, 0 metrics, 0 groups
02:19:47  The selection criterion 'path:models' does not match any nodes
02:19:47  No nodes selected!
```

</details>

Resolving patch:
diff --git a/core/dbt/graph/selector_methods.py b/core/dbt/graph/selector_methods.py
--- a/core/dbt/graph/selector_methods.py
+++ b/core/dbt/graph/selector_methods.py
@@ -26,6 +26,7 @@
     DbtRuntimeError,
 )
 from dbt.node_types import NodeType
+from dbt.task.contextvars import cv_project_root
 
 
 SELECTOR_GLOB = "*"
@@ -324,8 +325,8 @@ def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[Uniqu
 class PathSelectorMethod(SelectorMethod):
     def search(self, included_nodes: Set[UniqueId], selector: str) -> Iterator[UniqueId]:
         """Yields nodes from included that match the given path."""
-        # use '.' and not 'root' for easy comparison
-        root = Path.cwd()
+        # get project root from contextvar
+        root = Path(cv_project_root.get())
         paths = set(p.relative_to(root) for p in root.glob(selector))
         for node, real_node in self.all_nodes(included_nodes):
             ofp = Path(real_node.original_file_path)
diff --git a/core/dbt/task/base.py b/core/dbt/task/base.py
--- a/core/dbt/task/base.py
+++ b/core/dbt/task/base.py
@@ -45,6 +45,7 @@
 from dbt.graph import Graph
 from dbt.logger import log_manager
 from .printer import print_run_result_error
+from dbt.task.contextvars import cv_project_root
 
 
 class NoneConfig:
@@ -75,6 +76,8 @@ def __init__(self, args, config, project=None):
         self.args = args
         self.config = config
         self.project = config if isinstance(config, Project) else project
+        if self.config:
+            cv_project_root.set(self.config.project_root)
 
     @classmethod
     def pre_init_hook(cls, args):
diff --git a/core/dbt/task/contextvars.py b/core/dbt/task/contextvars.py
new file mode 100644
--- /dev/null
+++ b/core/dbt/task/contextvars.py
@@ -0,0 +1,6 @@
+from contextvars import ContextVar
+
+# This is a place to hold common contextvars used in tasks so that we can
+# avoid circular imports.
+
+cv_project_root: ContextVar = ContextVar("project_root")


Test patch:
diff --git a/tests/functional/graph_selection/test_graph_selection.py b/tests/functional/graph_selection/test_graph_selection.py
--- a/tests/functional/graph_selection/test_graph_selection.py
+++ b/tests/functional/graph_selection/test_graph_selection.py
@@ -121,11 +121,24 @@ def test_locally_qualified_name(self, project):
         check_result_nodes_by_name(results, ["nested_users", "subdir", "versioned"])
         assert_correct_schemas(project)
 
-        results = run_dbt(["run", "--select", "models/test/subdir*"])
+        os.chdir(
+            project.profiles_dir
+        )  # Change to random directory to test that Path selector works with project-dir
+        results = run_dbt(
+            ["run", "--project-dir", str(project.project_root), "--select", "models/test/subdir*"]
+        )
         check_result_nodes_by_name(results, ["nested_users", "subdir", "versioned"])
         assert_correct_schemas(project)
 
-        results = run_dbt(["build", "--select", "models/patch_path_selection_schema.yml"])
+        results = run_dbt(
+            [
+                "build",
+                "--project-dir",
+                str(project.project_root),
+                "--select",
+                "models/patch_path_selection_schema.yml",
+            ]
+        )
         check_result_nodes_by_name(results, ["subdir"])
         assert_correct_schemas(project)