Skip to content

Commit 7e28617

Browse files
branchseerclaude
andauthored
fix: skip self-referential package dependency edges in workspace graph (#230)
## What When a package listed itself as a workspace dependency (e.g. `"rolldown": "workspace:*"` inside `rolldown`'s own `package.json`), running any task in that package would fail with: ``` Cycle dependency detected: rolldown#build-binding:release -> rolldown#build-binding:release ``` ## Why The workspace package graph is built by reading each `package.json` and adding a directed edge for every workspace dependency that resolves to another package in the monorepo. There was no guard against the case where a package names itself — the edge `A → A` was added unconditionally, producing a self-loop. That self-loop is a cycle by definition, so the task planner's acyclicity check correctly rejected it — but the error was misleading because the "cycle" was entirely artificial and had nothing to do with the task definitions. ## Fix Skip any edge where the source and destination package are the same. A package depending on itself carries no ordering information, so dropping it is safe and matches how tools like pnpm handle this in practice. Includes a new plan snapshot test with a fixture that reproduces the scenario. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 1acf3ee commit 7e28617

File tree

8 files changed

+101
-1
lines changed

8 files changed

+101
-1
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "test-workspace",
3+
"private": true
4+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "self-dep",
3+
"version": "1.0.0",
4+
"dependencies": {
5+
"self-dep": "workspace:*"
6+
},
7+
"scripts": {
8+
"build": "echo building"
9+
}
10+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
packages:
2+
- 'packages/*'
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Tests that a package with a self-referential workspace dependency (e.g. for
2+
# testing purposes) does not produce a false cycle-dependency error.
3+
4+
[[plan]]
5+
name = "build in self-referential package"
6+
args = ["run", "build"]
7+
cwd = "packages/self-dep"
8+
compact = true
9+
10+
[[plan]]
11+
name = "recursive build across workspace"
12+
args = ["run", "-r", "build"]
13+
compact = true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
source: crates/vite_task_plan/tests/plan_snapshots/main.rs
3+
expression: "&compact_plan"
4+
info:
5+
args:
6+
- run
7+
- build
8+
cwd: packages/self-dep
9+
input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency
10+
---
11+
{
12+
"packages/self-dep#build": []
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
source: crates/vite_task_plan/tests/plan_snapshots/main.rs
3+
expression: "&compact_plan"
4+
info:
5+
args:
6+
- run
7+
- "-r"
8+
- build
9+
input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency
10+
---
11+
{
12+
"packages/self-dep#build": []
13+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
source: crates/vite_task_plan/tests/plan_snapshots/main.rs
3+
expression: task_graph_json
4+
input_file: crates/vite_task_plan/tests/plan_snapshots/fixtures/package-self-dependency
5+
---
6+
[
7+
{
8+
"key": [
9+
"<workspace>/packages/self-dep",
10+
"build"
11+
],
12+
"node": {
13+
"task_display": {
14+
"package_name": "self-dep",
15+
"task_name": "build",
16+
"package_path": "<workspace>/packages/self-dep"
17+
},
18+
"resolved_config": {
19+
"command": "echo building",
20+
"resolved_options": {
21+
"cwd": "<workspace>/packages/self-dep",
22+
"cache_config": {
23+
"env_config": {
24+
"fingerprinted_envs": [],
25+
"untracked_env": [
26+
"<default untracked envs>"
27+
]
28+
},
29+
"input_config": {
30+
"includes_auto": true,
31+
"positive_globs": [],
32+
"negative_globs": []
33+
}
34+
}
35+
}
36+
},
37+
"source": "PackageJsonScript"
38+
},
39+
"neighbors": []
40+
}
41+
]

crates/vite_workspace/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,11 @@ impl PackageGraphBuilder {
164164
path2: dep_path2.clone(),
165165
});
166166
}
167-
self.graph.add_edge(*id, *dep_id, *dep_type);
167+
// Skip self-referential edges: a package listing itself as a dependency
168+
// (e.g. for testing purposes) must not create a cycle in the task graph.
169+
if *id != *dep_id {
170+
self.graph.add_edge(*id, *dep_id, *dep_type);
171+
}
168172
}
169173
// Silently skip if dependency not found - it might be an external package
170174
}

0 commit comments

Comments
 (0)