Skip to content

fix: respect transitive dependencies when sorting filtered projects#12688

Merged
zkochan merged 12 commits into
pnpm:mainfrom
adrian-niculescu:fix/topo-sort-filtered-transitive-deps
Jul 2, 2026
Merged

fix: respect transitive dependencies when sorting filtered projects#12688
zkochan merged 12 commits into
pnpm:mainfrom
adrian-niculescu:fix/topo-sort-filtered-transitive-deps

Conversation

@adrian-niculescu

@adrian-niculescu adrian-niculescu commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

When a recursive command (run, exec, publish, pack, rebuild) is filtered to a subset of projects, the topological sort ran on the selected subgraph only and dropped edges that pass through unselected projects. Two selected projects related only transitively could then run concurrently or in the wrong order, so a dependency's script could start after its dependent's.

The sort now resolves edges through the full workspace graph: a project runs after the selected projects it transitively depends on, while projects with no real dependency relationship still run concurrently.

Prod-only filters (--filter-prod) prune dev dependencies from the graph they select against. The prod-only selected projects resolve order through that prod-pruned graph, so transitive prod dependencies are respected without pulling the dropped dev edges back. A selection that mixes --filter with --filter-prod resolves each project through the graph that matches how it was selected: regularly filtered projects through the full graph, prod-only ones through the prod-pruned graph. That keeps a regular filter's transitive ordering intact when a prod-only filter is also present, and stops a dev-only reverse edge from forming a cycle that would collapse a real prod dependency and its dependent into one chunk.

Note: the cyclic-workspace-dependency warning still inspects the selected subgraph only, so a cycle that runs through unselected projects affects ordering but is not warned about. Left as is to keep this change focused.

Fixes #8335

Squash Commit Body

The recursive command runners sorted opts.selectedProjectsGraph, which keeps
only the selected projects as keys. Edges to unselected projects were dropped,
so a transitive dependency between two selected projects (a -> b -> c with only
a and c selected) was lost and the two ran out of order.

sortFilteredProjects resolves the order through the full workspace graph: for
each sorted project it walks dependencies, tunneling past unselected projects
and stopping at selected ones, so a transitive relationship becomes a direct
edge. Projects without a real dependency stay in the same chunk, preserving
concurrency.

Prod-only filters (--filter-prod) prune dev edges. Their selected projects are
sorted through the prod-pruned full graph, so transitive prod deps are honored
without reintroducing the dropped dev edges. In a mixed selection each project
is sorted through the graph that matches how it was selected: regularly filtered
projects through the full graph, prod-only ones through the prod-pruned graph.
Carrying prod-only projects on the full graph would let a dev-only reverse edge
form a cycle and collapse a real prod dependency and its dependent into one
chunk.

Fixes pnpm/pnpm#8335

Checklist

  • Ported to pacquet, as the maintainer requested
    (comment):
    commit dce50a7 applies the same sort_filtered_projects fix — resolving
    transitive order through the full / prod-pruned workspace graph — to pacquet's
    recursive run, exec, and pack runners, with matching tests. pacquet has
    no recursive publish/rebuild ordering, so nothing else needs porting.
  • Added a changeset.
  • Added or updated tests.

Summary by CodeRabbit

  • New Features

    • Improved recursive command ordering when selecting projects with filters, including mixed regular and production-only selections.
  • Bug Fixes

    • Fixed command execution, build, pack, publish, and rebuild order so dependencies selected through unselected intermediaries run first.
    • Corrected behavior for --filter-prod and combined --filter + --filter-prod use cases.
    • Recursive operations now handle production-only project selections consistently.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a4271bd1-2908-4ef1-9330-59811be21489

📥 Commits

Reviewing files that changed from the base of the PR and between bace374 and 6333553.

📒 Files selected for processing (7)
  • .changeset/sort-filtered-transitive-deps.md
  • pacquet/crates/cli/src/cli_args/exec/recursive.rs
  • pacquet/crates/cli/src/cli_args/pack.rs
  • pacquet/crates/cli/src/cli_args/recursive.rs
  • pacquet/crates/cli/src/cli_args/recursive/tests.rs
  • pacquet/crates/cli/src/cli_args/run/recursive.rs
  • pacquet/crates/cli/tests/run_recursive.rs
✅ Files skipped from review due to trivial changes (1)
  • .changeset/sort-filtered-transitive-deps.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • pacquet/crates/cli/src/cli_args/pack.rs
  • pacquet/crates/cli/src/cli_args/exec/recursive.rs
  • pacquet/crates/cli/src/cli_args/recursive/tests.rs
  • pacquet/crates/cli/src/cli_args/run/recursive.rs
  • pacquet/crates/cli/tests/run_recursive.rs
  • pacquet/crates/cli/src/cli_args/recursive.rs

📝 Walkthrough

Walkthrough

This PR fixes topological ordering for --filtered recursive commands when selected projects connect only through unselected intermediaries. It adds sortFilteredProjects (TypeScript) and equivalent Rust helpers, propagates prod-pruned graph data through config/filter APIs, wires new inputs into run, exec, rebuild, pack, and publish, and mirrors the fix in the Pacquet CLI's recursive selection and sorting logic.

Changes

Workspace ordering fix

Layer / File(s) Summary
Filter graph data and context propagation
pnpm11/workspace/projects-filter/src/index.ts, pnpm11/workspace/projects-filter/src/filterProjectsFromDir.ts, pnpm11/workspace/projects-filter/test/index.ts, pnpm11/config/reader/src/Config.ts, pnpm11/config/reader/src/index.ts, pnpm11/pnpm/src/main.ts
filterProjectsBySelectorObjects builds a prod-pruned graph and derives prodOnlySelectedProjectDirs; these are forwarded through filterProjectsFromDir, added to ConfigContext, destructured in getConfig, and stored on the pnpm main context.
Sorter transitive-edge resolution
pnpm11/workspace/projects-sorter/src/index.ts, pnpm11/workspace/projects-sorter/test/*, pnpm11/workspace/projects-sorter/package.json
New sortFilteredProjects with helpers sequenceGraphByProject/sortedDependencies tunnels through unselected projects using full or prod-pruned graphs; tests and Jest config are added.
pnpm command wiring
pnpm11/building/commands/src/build/{rebuild,recursive}.ts, pnpm11/exec/commands/src/{exec,runRecursive}.ts, pnpm11/exec/commands/test/runRecursive.ts, pnpm11/releasing/commands/src/publish/{pack,recursivePublish}.ts, .changeset/sort-filtered-transitive-deps.md
Opts types for rebuild, exec, run, pack, and publish are expanded with allProjectsGraph/prodAllProjectsGraph/prodOnlySelectedProjectDirs, and chunk ordering switches to sortFilteredProjects; new tests validate ordering across unselected intermediaries and prod-only selections; a changeset documents the patch bumps.
Pacquet recursive selection and sorting
pacquet/crates/cli/src/cli_args/recursive.rs, pacquet/crates/cli/src/cli_args/recursive/tests.rs
RecursiveSelection struct is introduced; select_recursive_projects returns selected/full/prod graphs and prod_only_selected; sort_filtered_projects/sort_projects tunnel through unselected projects; AutoExcludeRoot returns a selector string; unit tests cover ordering and cycle scenarios.
Pacquet CLI consumers and integration tests
pacquet/crates/cli/src/cli_args/{exec/recursive,pack,run/recursive}.rs, pacquet/crates/cli/tests/run_recursive.rs
exec_recursive, pack, and run_recursive consume the new RecursiveSelection and sort_filtered_projects; integration tests assert ordering through unselected intermediaries, prod-only traversal, mixed filters, and resume behavior.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as pnpm run --filter
  participant Filter as filterProjectsBySelectorObjects
  participant Context as ConfigContext
  participant Recursive as runRecursive
  participant Sorter as sortFilteredProjects

  CLI->>Filter: apply --filter / --filter-prod selectors
  Filter-->>Context: selectedProjectsGraph, allProjectsGraph, prodAllProjectsGraph, prodOnlySelectedProjectDirs
  Context-->>Recursive: opts with graph fields
  Recursive->>Sorter: sortFilteredProjects(opts)
  Sorter->>Sorter: tunnel through unselected/prod-pruned graph
  Sorter-->>Recursive: ordered chunks respecting transitive deps
  Recursive-->>CLI: execute chunks in dependency order
Loading

Assessment against linked issues

Objective Addressed Explanation
Fix topological sort so selected projects transitively connected through unselected intermediaries build in correct order (#8335)
Preserve correct behavior for --filter-prod and mixed --filter/--filter-prod selections

Possibly related PRs

  • pnpm/pnpm#12093: Both PRs modify pacquet's recursive run implementation's chunking/resume logic in the same file.
  • pnpm/pnpm#12299: Both PRs edit recursivePublish.ts's chunk computation and PublishRecursiveOpts, with overlapping implementation.
  • pnpm/pnpm#12712: Both PRs modify pacquet's recursive.rs and related run/exec/pack paths for filter-driven project selection.

Suggested labels: product: pnpm@11, product: pacquet

Suggested reviewers: zkochan, KSXGitHub

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 26, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Fix recursive filtered project ordering across transitive workspace deps
🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

Description

• Fix recursive command sorting to respect transitive deps through unselected workspace projects.
• Preserve --filter-prod dev-dep pruning when resolving order, including mixed filter selections.
• Add unit/integration tests for filtered topo ordering and prod-graph metadata plumbing.
Diagram

graph TD
A["pnpm CLI main"] --> B["projects-filter"] --> C["ConfigContext graphs"] --> D["recursive runners"] --> F["sortFilteredProjects"] --> G["graphSequencer"]
B --> H["prodAllProjectsGraph"] --> C
D --> E{"Pick graph per project"} --> F
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Expand selection subgraph to include intermediary nodes for sorting only
  • ➕ Reuses existing sortProjects/sequenceGraph without special tunneling logic
  • ➕ Cycle detection and ordering naturally reflect the full path structure
  • ➖ Intermediates can increase node count and reduce concurrency if treated as schedulable work
  • ➖ Harder to preserve prod-only pruning semantics per root without careful graph partitioning
2. Precompute condensed selected-to-selected edges (transitive closure) once
  • ➕ Makes sorting a simple run over a smaller condensed graph
  • ➕ Could be reused across multiple commands in the same invocation
  • ➖ Computing closure can be more expensive on large workspaces than on-demand DFS
  • ➖ Still must respect per-root graph choice (full vs prod-pruned) in mixed selections

Recommendation: The PR’s approach (per-selected-project DFS that tunnels through unselected nodes, while sourcing the root’s direct edges from the selected graph) is the best fit: it fixes transitive ordering, preserves concurrency for independent projects, and keeps prod-only pruning intact (including mixed --filter/--filter-prod selections) without needing to expand the runnable set or compute a full transitive closure.

Files changed (19) +604 / -36

Enhancement (3) +13 / -4
Config.tsAdd prod graph metadata to ConfigContext +9/-0

Add prod graph metadata to ConfigContext

• Adds prodAllProjectsGraph and prodOnlySelectedProjectDirs to ConfigContext with documentation. This allows command handlers to distinguish regular vs prod-only filtered selections when sorting.

pnpm11/config/reader/src/Config.ts

index.tsInclude prod graph metadata when constructing ConfigContext +2/-2

Include prod graph metadata when constructing ConfigContext

• Threads prodAllProjectsGraph and prodOnlySelectedProjectDirs from the computed pnpmConfig into the returned context object. Ensures downstream commands can access the additional graph data.

pnpm11/config/reader/src/index.ts

filterProjectsFromDir.tsReturn prod graph metadata from filterProjectsBySelectorObjectsFromDir +2/-2

Return prod graph metadata from filterProjectsBySelectorObjectsFromDir

• Extends the directory-based filtering helper to return prodAllProjectsGraph and prodOnlySelectedProjectDirs. This surfaces prod-only selection details to the CLI entrypoint.

pnpm11/workspace/projects-filter/src/filterProjectsFromDir.ts

Bug fix (9) +147 / -28
rebuild.tsPlumb full/prod graph context into rebuild command opts +3/-0

Plumb full/prod graph context into rebuild command opts

• Extends RebuildCommandOpts to include allProjectsGraph, prodAllProjectsGraph, and prodOnlySelectedProjectDirs. Enables recursive rebuild sorting to resolve transitive dependencies correctly under filtering.

pnpm11/building/commands/src/build/rebuild.ts

recursive.tsUse sortFilteredProjects for recursive rebuild chunking +3/-3

Use sortFilteredProjects for recursive rebuild chunking

• Replaces sortProjects(selectedProjectsGraph) with sortFilteredProjects(opts). Passes through optional full/prod graph context so ordering can tunnel through unselected intermediates while preserving prod-only pruning.

pnpm11/building/commands/src/build/recursive.ts

exec.tsUse sortFilteredProjects for recursive exec ordering +3/-3

Use sortFilteredProjects for recursive exec ordering

• Switches recursive exec ordering from sortProjects(selectedProjectsGraph) to sortFilteredProjects(opts). Expands ExecOpts to accept full/prod graph metadata required for correct filtered ordering.

pnpm11/exec/commands/src/exec.ts

runRecursive.tsUse sortFilteredProjects for recursive run ordering +3/-3

Use sortFilteredProjects for recursive run ordering

• Updates recursive run chunking to use sortFilteredProjects(opts) so filtered selections preserve transitive dependency order. Accepts full/prod graph metadata via options for mixed filter behavior.

pnpm11/exec/commands/src/runRecursive.ts

main.tsExpose prod graph metadata in CLI context +2/-0

Expose prod graph metadata in CLI context

• Stores prodAllProjectsGraph and prodOnlySelectedProjectDirs from filter results into the runtime context. This makes the metadata available to recursive command handlers.

pnpm11/pnpm/src/main.ts

pack.tsUse sortFilteredProjects for recursive pack ordering +10/-2

Use sortFilteredProjects for recursive pack ordering

• Replaces sortProjects with sortFilteredProjects when packing recursively. Passes selected graph plus optional full/prod graphs and prod-only selection dirs for correct ordering.

pnpm11/releasing/commands/src/publish/pack.ts

recursivePublish.tsUse sortFilteredProjects for recursive publish ordering +5/-2

Use sortFilteredProjects for recursive publish ordering

• Switches recursive publish chunk computation to sortFilteredProjects(opts). Extends options type to accept full/prod graph metadata to preserve correct ordering under filtered selections.

pnpm11/releasing/commands/src/publish/recursivePublish.ts

index.tsCompute and return prod graph + prod-only selection dirs +22/-2

Compute and return prod graph + prod-only selection dirs

• Builds a prod-pruned full graph (ignoreDevDeps) for prod-only selectors and returns it as prodAllProjectsGraph. Computes prodOnlySelectedProjectDirs as projects selected only via prod-only filters (not also regular filters), enabling per-project graph choice during sorting.

pnpm11/workspace/projects-filter/src/index.ts

index.tsResolve transitive deps for filtered sorts (full/prod graphs) +96/-13

Resolve transitive deps for filtered sorts (full/prod graphs)

• Enhances sequenceGraph/sortProjects to optionally resolve dependencies through a separate fullProjectsGraph, tunneling through unselected nodes to preserve transitive ordering among selected projects. Adds sortFilteredProjects which chooses per-project traversal graph (full vs prod-pruned) based on prodOnlySelectedProjectDirs, while respecting any edges pruned from the selected graph.

pnpm11/workspace/projects-sorter/src/index.ts

Tests (3) +407 / -1
runRecursive.tsAdd integration tests for filtered transitive ordering +218/-0

Add integration tests for filtered transitive ordering

• Adds recursive-run tests covering: (1) transitive deps via unselected intermediates, (2) prod-only filter ordering via prod graph, and (3) mixed regular/prod-only selection behavior. Uses workspace:* deps to ensure local linking under default settings.

pnpm11/exec/commands/test/runRecursive.ts

index.tsTest prod graph metadata for mixed filters +42/-1

Test prod graph metadata for mixed filters

• Adds a unit test verifying that mixed regular and prod-only selectors return prodAllProjectsGraph and prodOnlySelectedProjectDirs. Confirms selectedProjectsGraph merges correctly and that prod edges are preserved in the prod graph.

pnpm11/workspace/projects-filter/test/index.ts

index.tsAdd unit tests for transitive edge resolution and mixed prod behavior +147/-0

Add unit tests for transitive edge resolution and mixed prod behavior

• Introduces a Jest test suite covering transitive ordering through unselected nodes, preserved concurrency for independent projects, non-reintroduction of pruned edges, mixed regular/prod-only graph selection, and cycle detection through unselected projects.

pnpm11/workspace/projects-sorter/test/index.ts

Documentation (1) +11 / -0
sort-filtered-transitive-deps.mdAdd changeset for filtered topo-order fix +11/-0

Add changeset for filtered topo-order fix

• Introduces a patch changeset for multiple packages, documenting corrected ordering for filtered recursive commands. Notes correct behavior for both regular and prod-only filters, including mixed selections.

.changeset/sort-filtered-transitive-deps.md

Other (3) +26 / -3
pnpm-lock.yamlAdd Jest globals to workspace.projects-sorter dev deps +3/-0

Add Jest globals to workspace.projects-sorter dev deps

• Updates the lockfile to include @jest/globals for the projects-sorter package. Supports newly added Jest-based unit tests.

pnpm-lock.yaml

package.jsonEnable Jest tests in projects-sorter package +5/-3

Enable Jest tests in projects-sorter package

• Expands lint to include test sources, updates test script to run Jest, and adds a dedicated .test command with Node VM module flags. Adds @jest/globals as a devDependency.

pnpm11/workspace/projects-sorter/package.json

tsconfig.jsonAdd test tsconfig for projects-sorter Jest suite +18/-0

Add test tsconfig for projects-sorter Jest suite

• Adds a dedicated tsconfig for compiling/running sorter tests with an explicit outDir and isolatedModules. Includes repo typings and references the package tsconfig.

pnpm11/workspace/projects-sorter/test/tsconfig.json

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 26, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (14) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. NODE_OPTIONS breaks Windows 🐞 Bug ☼ Reliability
Description
The new workspace.projects-sorter .test script sets NODE_OPTIONS using POSIX $NODE_OPTIONS
expansion, which is not expanded by Windows cmd.exe, so Node can receive a literal $NODE_OPTIONS
token and fail before running Jest. This can break the repo’s Windows test jobs when this package’s
tests run.
Code

pnpm11/workspace/projects-sorter/package.json[31]

+    ".test": "cross-env NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169\" jest"
Evidence
The .test script embeds $NODE_OPTIONS in the value passed to cross-env, which relies on POSIX
shell expansion and is not expanded by Windows cmd.exe. The repo runs a dedicated Windows test
matrix (windows-latest) and executes the test script in CI, so this script can fail on Windows and
break CI.

pnpm11/workspace/projects-sorter/package.json[26-33]
.github/workflows/ci.yml[115-190]
.github/workflows/test.yml[171-196]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`pnpm11/workspace/projects-sorter/package.json` defines a `.test` script that attempts to append to `NODE_OPTIONS` via POSIX `$NODE_OPTIONS` expansion. On Windows (where pnpm runs scripts through `cmd.exe` by default), `$NODE_OPTIONS` is not expanded, so the environment variable value can start with the literal string `$NODE_OPTIONS`, causing Node to error (invalid NODE_OPTIONS) and Jest to never start.
### Issue Context
The repository executes test shards on `windows-latest`, so this can break CI for PRs that touch this package.
### Fix
Avoid shell-dependent variable expansion in package scripts. For example:
- Set `NODE_OPTIONS` directly without referencing the prior value:
- `cross-env NODE_OPTIONS="--experimental-vm-modules --disable-warning=ExperimentalWarning --disable-warning=DEP0169" jest`
(If preserving existing `NODE_OPTIONS` is required, use a platform-neutral wrapper that concatenates `process.env.NODE_OPTIONS` in Node before invoking Jest.)
### Fix Focus Areas
- pnpm11/workspace/projects-sorter/package.json[26-33]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Not sorting full workspace graph ✓ Resolved 📎 Requirement gap ≡ Correctness
Description
sequenceGraph() topologically sequences only Object.keys(projectsGraph) (the selected/filtered
graph) and not the complete workspace graph before filtering. This violates the requirement to
compute order from the full graph first, which can lead to ordering semantics diverging from a true
full-graph topological order under --filter.
Code

pnpm11/workspace/projects-sorter/src/index.ts[R19-27]

+  const sortedProjectDirs = Object.keys(projectsGraph) as ProjectRootDir[]
+  const sorted = new Set(sortedProjectDirs)
+  const graph = new Map<ProjectRootDir, ProjectRootDir[]>(
+    sortedProjectDirs.map((projectDir) => [
+      projectDir,
+      sortedDependencies(fullProjectsGraph, projectDir, sorted),
+    ])
)
-  return graphSequencer(graph, keys)
+  return graphSequencer(graph, sortedProjectDirs)
Evidence
PR Compliance ID 2 requires computing the execution order from the complete workspace graph before
applying --filter. The updated sequenceGraph() derives the node set from projectsGraph
(selected graph) and calls graphSequencer() only on those nodes, even when a fullProjectsGraph
is available, so it is not ordering the full workspace graph first and then filtering.

Compute execution order using the full workspace graph before applying --filter selection
pnpm11/workspace/projects-sorter/src/index.ts[19-27]
pnpm11/workspace/projects-sorter/src/index.ts[30-35]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The compliance requirement expects recursive execution order to be derived by topologically ordering the complete workspace dependency graph first, and only then filtering to selected projects. The current implementation instead builds a new graph over only the selected projects (`Object.keys(projectsGraph)`) and sequences that.
## Issue Context
This impacts the contract for `--filter`ed recursive commands: the relative order should come from the full workspace topological order, with filtering applied afterward.
## Fix Focus Areas
- pnpm11/workspace/projects-sorter/src/index.ts[15-35]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Prod filter pulls dev edges ✓ Resolved 🐞 Bug ➹ Performance
Description
sortedDependencies() seeds traversal from fullProjectsGraph[projectDir].dependencies, so when
callers pass allProjectsGraph it can reintroduce devDependency-based ordering even if
selectedProjectsGraph was constructed with ignoreDevDeps (e.g. --filter-prod). This can
unexpectedly serialize or reorder recursive command execution based on dev-only relationships,
diverging from the dependency semantics used to build the selected graph.
Code

pnpm11/workspace/projects-sorter/src/index.ts[R48-60]

+  const dependencies = new Set<ProjectRootDir>()
+  const visited = new Set<ProjectRootDir>()
+  const stack = [...(fullProjectsGraph[projectDir]?.dependencies ?? [])]
+  while (stack.length > 0) {
+    const dependencyDir = stack.pop()!
+    if (dependencyDir === projectDir || visited.has(dependencyDir)) continue
+    visited.add(dependencyDir)
+    if (sorted.has(dependencyDir)) {
+      dependencies.add(dependencyDir)
+    } else {
+      const transitiveDeps = fullProjectsGraph[dependencyDir]?.dependencies
+      if (transitiveDeps) stack.push(...transitiveDeps)
+    }
Evidence
Workspace filtering builds a prod-only graph (ignoreDevDeps: true) for prod selectors but still
returns allProjectsGraph from a graph built without ignoreDevDeps, and recursive runners pass
that allProjectsGraph to sortProjects(). The sorter then reads dependencies from the full graph,
which (by construction) includes devDependencies unless ignoreDevDeps is set, so dev-only edges
can affect ordering.

pnpm11/workspace/projects-sorter/src/index.ts[15-35]
pnpm11/workspace/projects-sorter/src/index.ts[43-60]
pnpm11/workspace/projects-filter/src/index.ts[107-147]
pnpm11/workspace/projects-graph/src/index.ts[37-43]
pnpm11/exec/commands/src/runRecursive.ts[58-61]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`sortedDependencies()` currently uses `fullProjectsGraph[projectDir]?.dependencies` as the starting point for dependency discovery. If `fullProjectsGraph` includes devDependencies, those edges can influence ordering even when `projectsGraph` (the selected graph) was built with prod-only semantics (via `ignoreDevDeps: true`).
## Issue Context
In workspace filtering, `selectedProjectsGraph` for `followProdDepsOnly` selectors is built from a graph created with `ignoreDevDeps: true`, but `allProjectsGraph` is still returned from a graph created without `ignoreDevDeps`. Recursive runners now pass `allProjectsGraph` into `sortProjects()`, which makes the sorter use the full (dev-including) graph for ordering.
## Fix Focus Areas
- pnpm11/workspace/projects-sorter/src/index.ts[15-62]
- pnpm11/workspace/projects-filter/src/index.ts[107-145]
- pnpm11/workspace/projects-graph/src/index.ts[37-43]
## Suggested fix approach
1. Ensure the sorter does not “override” the dependency semantics of `projectsGraph` when both graphs are provided.
- Option A (minimal behavioral preservation): seed traversal from `projectsGraph[projectDir].dependencies` (not `fullProjectsGraph`) and only use `fullProjectsGraph` when expanding *through* unselected nodes.
2. Additionally (to fully respect `--filter-prod` semantics), ensure the `fullProjectsGraph` passed for prod-only filtering also excludes devDependencies.
- This may require plumbing/returning a prod-only full graph from the filtering layer (or recomputing it when `filter-prod` is in effect) and using it when calling `sortProjects()`.
3. Add/extend tests covering `--filter-prod` where devDependency-only paths would incorrectly affect ordering.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Windows-unsafe test command 🐞 Bug ☼ Reliability
Description
The new test embeds an arrow function (=>) in an inline node -e "..." command string; in Windows
cmd.exe, > is parsed as a redirection operator, which can break the command and fail the test.
Since the repo runs TS tests on windows-latest, this can break CI on Windows.
Code

pnpm11/exec/commands/test/runRecursive.ts[R711-713]

+      scripts: {
+        build: `node -e "setTimeout(() => {}, 500)" && ${server.sendLineScript('project-c')}`,
+      },
Evidence
The added test contains the inline node -e command with an arrow function. The CI workflow
explicitly runs test shards on windows-latest, so a cmd.exe parsing issue can surface and fail CI.

pnpm11/exec/commands/test/runRecursive.ts[681-714]
.github/workflows/ci.yml[162-197]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A new test uses `node -e "setTimeout(() => {}, 500)"`, which contains `=>`. On Windows `cmd.exe`, `>` is treated as output redirection during command parsing, so the command can fail before Node receives it.
## Issue Context
This is in test code but can block CI because Windows test jobs run in this repo.
## Fix Focus Areas
- pnpm11/exec/commands/test/runRecursive.ts[681-714]
- .github/workflows/ci.yml[162-197]
## Suggested fix
- Replace the arrow function with a classic function expression to avoid `>` entirely, e.g.:
- `node -e "setTimeout(function () {}, 500)"`
- Alternatively, avoid shell parsing by moving the delay into a small JS file or using a node one-liner without `>` characters.
- Keep the timing intent (ensure `project-c` is slow enough to expose incorrect ordering).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Pack omits full graph 🐞 Bug ≡ Correctness
Description
pack.handler() passes opts.allProjectsGraph through to sortFilteredProjects(), but because
it’s optional, recursive pack invocations that only provide selectedProjectsGraph silently fall
back to subgraph-only sorting and won’t respect transitive dependencies through unselected workspace
projects. This also means the repo’s recursive-pack tests currently don’t exercise/validate the new
transitive-ordering behavior.
Code

pnpm11/releasing/commands/src/publish/pack.ts[R159-164]

+    const chunks = sortFilteredProjects({
+      selectedProjectsGraph,
+      allProjectsGraph: opts.allProjectsGraph,
+      prodAllProjectsGraph: opts.prodAllProjectsGraph,
+      prodOnlySelectedProjectDirs: opts.prodOnlySelectedProjectDirs,
+    })
Evidence
The recursive pack handler forwards an optional allProjectsGraph into sortFilteredProjects,
which itself falls back to the selected subgraph when the full graph is absent; the repo’s recursive
pack tests demonstrate a real call site that supplies only selectedProjectsGraph even though the
filter helper returns allProjectsGraph, so the new transitive-ordering behavior isn’t
applied/validated in that flow.

pnpm11/releasing/commands/src/publish/pack.ts[159-164]
pnpm11/workspace/projects-sorter/src/index.ts[36-46]
pnpm11/releasing/commands/test/publish/pack.ts[796-808]
pnpm11/workspace/projects-filter/src/filterProjectsFromDir.ts[7-37]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Recursive `pack` ordering only respects transitive dependencies through unselected workspace projects when `allProjectsGraph` (and, for `--filter-prod`, `prodAllProjectsGraph` + `prodOnlySelectedProjectDirs`) is provided to `sortFilteredProjects`. Today these fields are optional, and at least some in-repo recursive-pack call sites omit them, so the new behavior is not applied/covered.
### Issue Context
- `sortFilteredProjects()` explicitly falls back to `selectedProjectsGraph` when `allProjectsGraph` is absent, which prevents tunneling through unselected nodes.
- The recursive pack handler forwards `opts.allProjectsGraph` but does not ensure it is present.
- Existing recursive pack tests call `pack.handler()` with only `selectedProjectsGraph` even though `filterProjectsBySelectorObjectsFromDir()` returns `allProjectsGraph`.
### Fix Focus Areas
- pnpm11/releasing/commands/src/publish/pack.ts[159-164]
- pnpm11/releasing/commands/test/publish/pack.ts[796-808]
- pnpm11/workspace/projects-sorter/src/index.ts[36-47]
- pnpm11/workspace/projects-filter/src/filterProjectsFromDir.ts[7-37]
### Suggested fix
1. Update in-repo recursive-pack callers (tests at minimum) to pass `allProjectsGraph` (and prod metadata when relevant) from `filterProjectsBySelectorObjectsFromDir()` into `pack.handler()`.
2. Consider tightening the API contract to prevent silent fallback:
- make `allProjectsGraph` required for recursive pack when `selectedProjectsGraph` is provided, or
- add a runtime assertion/warning in `pack.handler()` when `opts.recursive` is true and `opts.allProjectsGraph` is missing (since the transitive-ordering fix cannot apply).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Selected graph node cloning 🐞 Bug ➹ Performance
Description
pacquet’s select_recursive_projects() clones ProjectGraphNodes into
RecursiveSelection.selected, duplicating each selected node’s dependencies: Vec while also
retaining the full graph(s) for transitive-edge tunneling, which can significantly increase
CPU/allocs and peak memory for large filtered selections.
Code

pacquet/crates/cli/src/cli_args/recursive.rs[R371-387]

+    if let Some(prod_all) = &prod_all {
+        let regular: HashSet<&PathBuf> = regular_selected.iter().collect();
+        for dir in &prod_selected {
+            if let Some(node) = prod_all.get(dir) {
+                selected.insert(dir.clone(), node.clone());
+                if !regular.contains(dir) {
+                    prod_only_selected.insert(dir.clone());
+                }
+            }
+        }
}
+    // Regular-selected projects keep their full (dev-inclusive) edges,
+    // overwriting the prod node for any project selected both ways.
+    for dir in &regular_selected {
+        if let Some(node) = all.get(dir) {
+            selected.insert(dir.clone(), node.clone());
+        }
Evidence
The selected graph is built by node.clone() inserts; ProjectGraphNode’s Clone necessarily
clones its dependencies: Vec, duplicating those vectors. The sorter’s sorted_dependencies()
consults the full graph only when a dependency is not in the selected set (sorted), meaning the
full graph can omit selected nodes and still support tunneling.

pacquet/crates/cli/src/cli_args/recursive.rs[360-390]
pacquet/crates/workspace-projects-graph/src/graph.rs[10-17]
pacquet/crates/cli/src/cli_args/recursive.rs[139-170]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`select_recursive_projects()` currently materializes `RecursiveSelection.selected` by doing `node.clone()` from `all` / `prod_all`. Since `ProjectGraphNode` contains a `Vec<PathBuf>` of dependencies, this duplicates dependency lists for every selected project while the full graph(s) are still kept around for sorting.
### Issue Context
The sorter’s tunneling logic only consults the full graph for nodes **outside** the sorted/selected set, so the full graphs do not need to retain selected nodes for correctness; they only need the unselected nodes to traverse through.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/recursive.rs[360-390]
- pacquet/crates/cli/src/cli_args/recursive.rs[150-170]
- pacquet/crates/workspace-projects-graph/src/graph.rs[10-17]
### Suggested fix
- While building `selected`, **move** nodes out of `all` / `prod_all` (e.g., via `swap_remove`/`shift_remove` on `IndexMap`) instead of cloning them.
- Keep the remaining (unselected-only) `all` / `prod_all` graphs in `RecursiveSelection` for tunneling.
- Preserve insertion order semantics (prod-selected first, then regular-selected overwrites) when moving nodes.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. Set(undefined) runtime crash 🐞 Bug ☼ Reliability
Description
sortFilteredProjects() constructs new Set(opts.prodOnlySelectedProjectDirs) even though
prodOnlySelectedProjectDirs is optional; if any caller provides prodAllProjectsGraph but omits
prodOnlySelectedProjectDirs, the sorter throws a TypeError before recursive execution starts.
In-repo call sites added/updated in this PR appear to pass both consistently, but this is a brittle
public API contract and can break out-of-tree or future internal callers.
Code

pnpm11/workspace/projects-sorter/src/index.ts[R59-70]

+  const fullProjectsGraph = opts.allProjectsGraph ?? opts.selectedProjectsGraph
+  const prodAllProjectsGraph = opts.prodAllProjectsGraph
+  if (!prodAllProjectsGraph) {
+    return sortProjects(opts.selectedProjectsGraph, fullProjectsGraph)
+  }
+  const prodOnlySelectedProjectDirs = new Set(opts.prodOnlySelectedProjectDirs)
+  return sequenceGraphByProject(
+    opts.selectedProjectsGraph,
+    (projectDir) => prodOnlySelectedProjectDirs.has(projectDir)
+      ? prodAllProjectsGraph
+      : fullProjectsGraph
+  ).chunks
Evidence
The function’s options type declares prodOnlySelectedProjectDirs as optional, but the
implementation uses it as an iterable without nullish handling, which will throw when undefined.

pnpm11/workspace/projects-sorter/src/index.ts[53-70]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`sortFilteredProjects()` calls `new Set(opts.prodOnlySelectedProjectDirs)` but `prodOnlySelectedProjectDirs` is optional. In JavaScript, `new Set(undefined)` throws because `undefined` is not iterable.
### Issue Context
This code runs in the recursive command scheduling path. While current in-repo integrations likely pass `prodOnlySelectedProjectDirs` whenever `prodAllProjectsGraph` is present, the function is exported and can be called with incomplete options (or regress in future call sites), causing a hard crash.
### Fix Focus Areas
- pnpm11/workspace/projects-sorter/src/index.ts[53-70]
### Suggested fix
- Change the Set construction to default to an empty array:
- `const prodOnlySelectedProjectDirs = new Set(opts.prodOnlySelectedProjectDirs ?? [])`
- (Optional, stronger contract) Enforce pairing at the type level via a discriminated union so `prodAllProjectsGraph` requires `prodOnlySelectedProjectDirs`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (8)
8. Full-graph sort allocs 🐞 Bug ➹ Performance
Description
sortProjects() now calls sortedDependencies() which allocates two Sets and clones a stack per
project even when sorting the full workspace graph, adding avoidable CPU/GC overhead. This hits
recursive install/update flows that topologically chunk the entire allProjectsGraph.
Code

pnpm11/workspace/projects-sorter/src/index.ts[R89-103]

+  const dependencies = new Set<ProjectRootDir>()
+  const visited = new Set<ProjectRootDir>()
+  const stack = [...(projectsGraph[projectDir]?.dependencies ?? [])]
+  while (stack.length > 0) {
+    const dependencyDir = stack.pop()!
+    if (dependencyDir === projectDir || visited.has(dependencyDir)) continue
+    visited.add(dependencyDir)
+    if (sorted.has(dependencyDir)) {
+      dependencies.add(dependencyDir)
+    } else {
+      const transitiveDeps = fullProjectsGraph[dependencyDir]?.dependencies
+      if (transitiveDeps) stack.push(...transitiveDeps)
+    }
+  }
+  return Array.from(dependencies)
Evidence
The new sorter always routes through sortedDependencies() for every node during sequencing, and
sortedDependencies() allocates Sets + a cloned stack per node. sortProjects(allProjectsGraph)
is used to sort the full workspace graph in installing recursive flows, so this overhead can scale
with workspace size (projects count).

pnpm11/workspace/projects-sorter/src/index.ts[22-35]
pnpm11/workspace/projects-sorter/src/index.ts[83-104]
pnpm11/installing/commands/src/recursive.ts[624-637]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`sortedDependencies()` always allocates `dependencies`/`visited` Sets and clones the initial stack, even when we are sorting a full graph (no unselected nodes to tunnel through). This adds avoidable allocations/GC to common paths that sort the whole workspace graph.
## Issue Context
When `fullProjectsGraph` is the same object as `projectsGraph` (the default for `sortProjects(projectsGraph)`), we can compute the dependency list without DFS/tunneling: just filter the project’s direct dependencies to those in `sorted` and not self.
## Fix Focus Areas
- pnpm11/workspace/projects-sorter/src/index.ts[22-35]
- pnpm11/workspace/projects-sorter/src/index.ts[83-104]
### Suggested approach
- In `sortedDependencies()`, add a fast-path when `fullProjectsGraph === projectsGraph`:
- Return `projectsGraph[projectDir]?.dependencies.filter(d => d !== projectDir && sorted.has(d)) ?? []`.
- Keep the current DFS/tunneling logic only for the `fullProjectsGraph !== projectsGraph` case.
- This preserves current behavior for filtered sorts while reducing per-project allocations for full-graph sorts.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Redundant graph construction 🐞 Bug ➹ Performance
Description
select_recursive_projects() may rebuild the workspace projects graph multiple times (via
run_filter() calling filter_projects()) and then build another prod-pruned graph for sorting,
which adds significant extra CPU/alloc work for filtered recursive commands. In large workspaces
this can noticeably increase latency and peak memory for exec/run/pack when
--filter/--filter-prod are used.
Code

pacquet/crates/cli/src/cli_args/recursive.rs[R284-301]

+    // Run the regular and prod-only selectors separately so the projects a
+    // `--filter-prod` selector contributes can be sorted through the
+    // prod-pruned graph (pnpm's `prodOnlySelectedProjectDirs`). Their union
+    // is the same set a single combined `filter_projects` call would return.
+    let regular_selected = run_filter(projects, &config.filter, false, prefix, graph_options)?;
+    let prod_selected = run_filter(projects, &config.filter_prod, true, prefix, graph_options)?;
+
+    let prod_all = if config.filter_prod.is_empty() {
+        None
+    } else {
+        Some(
+            create_projects_graph(
+                projects.iter().map(|project| GraphPkg { project }).collect(),
+                &CreateProjectsGraphOptions { ignore_dev_deps: true, ..graph_options },
+            )
+            .graph,
+        )
+    };
Evidence
select_recursive_projects() builds all, runs filtering twice, and (when --filter-prod is set)
builds prod_all; meanwhile filter_projects_by_selector_objects() shows that each
filter_projects() call constructs fresh graphs from the projects list for prod and/or regular
selectors, so the overall flow can create multiple full/prod graphs per invocation.

pacquet/crates/cli/src/cli_args/recursive.rs[265-327]
pacquet/crates/workspace-projects-filter/src/filter.rs[331-389]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`pacquet`'s recursive selection path can construct workspace graphs multiple times when filters are used. This is especially expensive for `--filter-prod` because it needs a prod-pruned full graph for ordering, but the current flow also builds graphs inside filtering.
### Issue Context
- `select_recursive_projects()` builds the full graph (`all`).
- It then calls `run_filter()` for regular and prod selectors; `run_filter()` uses `filter_projects()`, which internally builds one or more graphs from the full `projects` list.
- When `--filter-prod` is active, `select_recursive_projects()` additionally builds `prod_all` (prod-pruned full graph) for sorting, even though filtering already had to build a prod graph.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/recursive.rs[265-327]
- pacquet/crates/workspace-projects-filter/src/filter.rs[331-389]
### Suggested fix direction
- Avoid calling `filter_projects()` twice with separate selector groups if you can get the same metadata in one pass.
- Option A (best): extend `pacquet_workspace_projects_filter::filter_projects_by_selector_objects()` (or add a new API) to return:
- the union selection,
- the prod-only-selected subset,
- and/or the prod-pruned graph it already built,
so `select_recursive_projects()` can reuse that graph as `prod_all`.
- Option B: add a `filter_workspace_projects(graph, selectors, ...)` entrypoint usable by `select_recursive_projects()` so it can reuse the already-constructed `all` / `prod_all` graphs instead of rebuilding inside filtering.
- Ensure the final behavior remains identical: regular-selected nodes keep dev-inclusive edges; prod-only-selected nodes keep prod-pruned edges; sorting resolves transitive edges through the appropriate full graph per node.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. Unnecessary graph clone 🐞 Bug ➹ Performance
Description
In pacquet’s select_recursive_projects() no-filter path, the full workspace ProjectGraph is
cloned into both RecursiveSelection.selected and .all. This doubles peak memory and adds clone
CPU cost on common recursive runs where no --filter/--filter-prod is used.
Code

pacquet/crates/cli/src/cli_args/recursive.rs[R275-281]

if config.filter.is_empty() && config.filter_prod.is_empty() {
-        return Ok(graph);
+        return Ok(RecursiveSelection {
+            selected: all.clone(),
+            all,
+            prod_all: None,
+            prod_only_selected: HashSet::new(),
+        });
Evidence
The no-filter branch explicitly clones all into selected, and recursive exec then uses
selection.selected and selection.all for sorting, meaning the clone is paid on that common path.

pacquet/crates/cli/src/cli_args/recursive.rs[265-282]
pacquet/crates/cli/src/cli_args/exec/recursive.rs[61-85]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In `select_recursive_projects()`, when no filters are provided, the code returns `RecursiveSelection { selected: all.clone(), all, ... }`. This clones the full workspace graph even though `selected` and `all` are identical in this branch, increasing memory and CPU overhead.
## Issue Context
This code runs before sorting in recursive pacquet commands (e.g. recursive exec/run/pack), so the extra clone happens on a common path when users don’t pass filters.
## Fix Focus Areas
- pacquet/crates/cli/src/cli_args/recursive.rs[245-282]
- pacquet/crates/cli/src/cli_args/exec/recursive.rs[72-85]
## Suggested remediation approach
- Refactor `RecursiveSelection` to avoid needing two owned copies when graphs are identical (no-filter case). Options include:
- Store the full graph once and have `selected` be a reference/`Arc` to it in the no-filter case.
- Change `RecursiveSelection` to hold `all` as `Option<ProjectGraph<_>>` (or similar), where `None` means “same as selected”, and update `sort_filtered_projects` call sites to use `all.as_ref().unwrap_or(&selected)`.
- Alternatively, special-case no-filter callers to skip `RecursiveSelection` and sort directly on the single graph.
The goal is to keep behavior identical while eliminating the full-graph clone on the no-filter path.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. Pacquet sort extra allocations 🐞 Bug ➹ Performance
Description
In pacquet, sort_projects() now computes dependencies via sorted_dependencies() for every project,
allocating a new HashSet and cloning the dependency list even when sorting the full workspace graph
(the common path for recursive run/exec/pack). This adds avoidable CPU/alloc overhead in large
workspaces where recursive commands are performance-sensitive.
Code

pacquet/crates/cli/src/cli_args/recursive.rs[R103-116]

+    let mut dependencies: Vec<PathBuf> = Vec::new();
+    let mut visited: HashSet<PathBuf> = HashSet::new();
+    let mut stack: Vec<PathBuf> =
+        projects_graph.get(project_dir).map(|node| node.dependencies.clone()).unwrap_or_default();
+    while let Some(dependency_dir) = stack.pop() {
+        if dependency_dir.as_path() == project_dir || !visited.insert(dependency_dir.clone()) {
+            continue;
+        }
+        if sorted.contains(dependency_dir.as_path()) {
+            dependencies.push(dependency_dir);
+        } else if let Some(node) = full_projects_graph.get(&dependency_dir) {
+            stack.extend(node.dependencies.iter().cloned());
+        }
+    }
Evidence
The implementation allocates/clones per project inside sorted_dependencies(), and all pacquet
recursive command entrypoints call sort_projects(&graph, None), so the overhead is paid on the
common full-graph path.

pacquet/crates/cli/src/cli_args/recursive.rs[51-56]
pacquet/crates/cli/src/cli_args/recursive.rs[69-84]
pacquet/crates/cli/src/cli_args/recursive.rs[97-118]
pacquet/crates/cli/src/cli_args/run/recursive.rs[96-102]
pacquet/crates/cli/src/cli_args/exec/recursive.rs[80-86]
pacquet/crates/cli/src/cli_args/pack.rs[121-124]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`sort_projects(&graph, None)` is used by pacquet recursive commands to sort the full workspace graph. After this PR, `sequence_graph()` calls `sorted_dependencies()` for every project, which allocates a `HashSet` and clones dependency lists even though no transitive “tunneling” is needed when `projects_graph` == `full_projects_graph`.
### Issue Context
This is a performance concern in pacquet CLI recursive command paths (`run`, `exec`, `pack`) that always pass `None` today.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/recursive.rs[51-56]
- pacquet/crates/cli/src/cli_args/recursive.rs[69-84]
- pacquet/crates/cli/src/cli_args/recursive.rs[97-118]
### Implementation direction
- Add a fast path when `full_graph` is the same reference as `graph` (e.g. `std::ptr::eq(projects_graph, full_projects_graph)`): build the dependency map by iterating the node’s direct dependencies and filtering self-edges, without per-node DFS/`visited`.
- Alternatively (or additionally), reduce allocations in `sorted_dependencies()` by avoiding `node.dependencies.clone()` when possible and pre-sizing structures if needed.
- Keep the current tunneling behavior for the filtered/subgraph case.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


12. Debug output in tests 🐞 Bug ⚙ Maintainability
Description
The new Rust topological-sort test contains an unconditional dbg!(&result) call, which will print
to stderr on every test run and add noise to CI logs. This is test-only but should be removed to
keep output clean and stable.
Code

pacquet/crates/cli/src/cli_args/recursive/tests.rs[R92-94]

+    let result = sequence_graph(&select(&graph, &["a", "c"]), &graph);
+    dbg!(&result);
+    assert!(!result.safe, "a -> b -> c -> a is a cycle once b is tunneled through");
Evidence
The test detects_a_cycle_that_passes_through_unselected_projects contains an unconditional
dbg!(&result);, which prints during normal test execution.

pacquet/crates/cli/src/cli_args/recursive/tests.rs[89-95]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A `dbg!(&result);` statement was introduced into a `#[test]` and will emit debug output to stderr on every test run, cluttering CI logs.
### Issue Context
This is in the new cycle-detection test for the topological sequencing logic.
### Fix Focus Areas
- pacquet/crates/cli/src/cli_args/recursive/tests.rs[89-95]
### Suggested fix
Remove `dbg!(&result);`. If extra context is needed when debugging failures, prefer asserting with a helpful message (e.g. include `result.cycles` in the assertion message) or use conditional logging guarded behind a feature/env flag.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


13. Extra prod graph retention 🐞 Bug ➹ Performance
Description
When --filter-prod is used, filterProjectsBySelectorObjects() now returns prodAllProjectsGraph
and the CLI stores it on ConfigContext, keeping an additional full ProjectsGraph alive beyond
the filtering step. This increases memory/GC overhead for recursive commands in large workspaces
because ProjectsGraph nodes include the full package object for every project, not just
dependency edges.
Code

pnpm11/workspace/projects-filter/src/index.ts[R135-160]

+    let prodGraph: ProjectGraph<Pkg> | undefined
if (prodProjectSelectors.length > 0) {
-      const { graph } = createProjectsGraph<Pkg>(projects, { ignoreDevDeps: true, linkWorkspacePackages: opts.linkWorkspacePackages })
-      prodFilteredGraph = await filterWorkspaceProjects(graph, prodProjectSelectors, {
+      prodGraph = createProjectsGraph<Pkg>(projects, { ignoreDevDeps: true, linkWorkspacePackages: opts.linkWorkspacePackages }).graph
+      prodFilteredGraph = await filterWorkspaceProjects(prodGraph, prodProjectSelectors, {
workspaceDir: opts.workspaceDir,
testPattern: opts.testPattern,
changedFilesIgnorePattern: opts.changedFilesIgnorePattern,
useGlobDirFiltering: opts.useGlobDirFiltering,
})
}
+    const regularSelectedProjectDirs = new Set(Object.keys(filteredGraph?.selectedProjectsGraph ?? {}) as ProjectRootDir[])
+    const prodOnlySelectedProjectDirs = prodFilteredGraph == null
+      ? undefined
+      : (Object.keys(prodFilteredGraph.selectedProjectsGraph) as ProjectRootDir[])
+        .filter((projectDir) => !regularSelectedProjectDirs.has(projectDir))
return {
allProjectsGraph: graph,
selectedProjectsGraph: {
...prodFilteredGraph?.selectedProjectsGraph,
...filteredGraph?.selectedProjectsGraph,
},
+      prodAllProjectsGraph: prodGraph,
+      prodOnlySelectedProjectDirs,
unmatchedFilters: [
Evidence
The PR now constructs and returns a prod-pruned full graph (prodGraph) and stores it in global CLI
context, increasing retention time and memory footprint. ProjectsGraph is defined to include the
full package object per node, so retaining two full graphs (regular + prod-pruned) means retaining
two node-maps (even if they reference the same underlying Project objects).

pnpm11/workspace/projects-filter/src/index.ts[121-165]
pnpm11/pnpm/src/main.ts[284-308]
pnpm11/workspace/projects-graph/src/index.ts[19-35]
pnpm11/core/types/src/project.ts[9-12]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`prodAllProjectsGraph` is currently retained as a full `ProjectsGraph` (graph node includes `{ dependencies, package }`) whenever `--filter-prod` is present. This unnecessarily keeps a second full workspace graph alive (in addition to `allProjectsGraph`) even though the sorter only needs dependency edges.
### Issue Context
- `filterProjectsBySelectorObjects()` builds a prod-pruned full graph via `createProjectsGraph(..., { ignoreDevDeps: true })` and returns it as `prodAllProjectsGraph`.
- The CLI stores this on the long-lived `ConfigContext`, extending its lifetime.
- `ProjectsGraph` nodes include `package: Project` (manifest + helpers), which is heavier than needed for sorting.
### Fix Focus Areas
- pnpm11/workspace/projects-filter/src/index.ts[132-165]
- pnpm11/config/reader/src/Config.ts[44-56]
- pnpm11/pnpm/src/main.ts[284-308]
- pnpm11/workspace/projects-sorter/src/index.ts[53-70]
### Suggested approach
- Change the metadata threaded through `ConfigContext` to carry a lightweight adjacency structure for the prod-pruned graph (e.g. `Record<ProjectRootDir, ProjectRootDir[]>` or `Record<ProjectRootDir, { dependencies: ProjectRootDir[] }>`), rather than a full `ProjectsGraph`.
- Update `sortFilteredProjects()` to accept/use this lightweight structure for transitive traversal.
- Keep `prodOnlySelectedProjectDirs` as-is (or derive it) so per-project graph selection still works.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


14. Spread push crash risk 🐞 Bug ☼ Reliability
Description
sortedDependencies() uses stack.push(...transitiveDeps), which can throw due to JS argument limits
or create avoidable memory pressure when a node has a very large dependency list. This can crash or
significantly slow filtered recursive command sorting on very large/adversarial workspaces.
Code

pnpm11/workspace/projects-sorter/src/index.ts[R99-101]

+      const transitiveDeps = fullProjectsGraph[dependencyDir]?.dependencies
+      if (transitiveDeps) stack.push(...transitiveDeps)
+    }
Evidence
The new sorter traversal expands arrays into function arguments via spread, and the traversed
dependency arrays are generated from manifest dependency objects without any hard cap, so
pathological workspaces can create very large arrays and trigger argument-limit failures or heavy
overhead.

pnpm11/workspace/projects-sorter/src/index.ts[83-103]
pnpm11/workspace/projects-graph/src/index.ts[37-108]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`sortedDependencies()` expands `transitiveDeps` with `stack.push(...transitiveDeps)`. For very large `transitiveDeps` arrays this can hit V8/Node argument limits (throwing) and/or create avoidable allocation/CPU overhead.
### Issue Context
This function walks the full workspace graph to resolve transitive dependencies across unselected projects. The dependency arrays it traverses are produced from workspace manifests and can be large in big/automated workspaces.
### Fix Focus Areas
- pnpm11/workspace/projects-sorter/src/index.ts[83-103]
### Suggested fix
Replace the spread push with a loop (or chunked push) to avoid argument explosion:

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


15. Repeated graph traversal 🐞 Bug ➹ Performance
Description
sortedDependencies() performs a fresh DFS through fullProjectsGraph for every selected project,
which can multiply work for filtered recursive commands (worst-case ~O(k·(V+E))) and slow down large
workspaces. This is now on the hot path for pnpm -r run/exec/publish/rebuild/pack when --sort is
enabled.
Code

pnpm11/workspace/projects-sorter/src/index.ts[R43-60]

+function sortedDependencies (
+  fullProjectsGraph: ProjectsGraph,
+  projectDir: ProjectRootDir,
+  sorted: ReadonlySet<ProjectRootDir>
+): ProjectRootDir[] {
+  const dependencies = new Set<ProjectRootDir>()
+  const visited = new Set<ProjectRootDir>()
+  const stack = [...(fullProjectsGraph[projectDir]?.dependencies ?? [])]
+  while (stack.length > 0) {
+    const dependencyDir = stack.pop()!
+    if (dependencyDir === projectDir || visited.has(dependencyDir)) continue
+    visited.add(dependencyDir)
+    if (sorted.has(dependencyDir)) {
+      dependencies.add(dependencyDir)
+    } else {
+      const transitiveDeps = fullProjectsGraph[dependencyDir]?.dependencies
+      if (transitiveDeps) stack.push(...transitiveDeps)
+    }
Evidence
sequenceGraph() calls sortedDependencies() once per sorted (selected) project, and
sortedDependencies() performs an explicit stack-based traversal through fullProjectsGraph.
Recursive command handlers call sortProjects(selectedProjectsGraph, allProjectsGraph), so this
repeated traversal occurs in user-facing recursive command execution.

pnpm11/workspace/projects-sorter/src/index.ts[15-27]
pnpm11/workspace/projects-sorter/src/index.ts[43-62]
pnpm11/exec/commands/src/runRecursive.ts[58-60]
pnpm11/exec/commands/src/exec.ts[186-190]
pnpm11/releasing/commands/src/publish/recursivePublish.ts[117-118]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`sortedDependencies()` walks the full workspace graph for each selected project independently, which can repeatedly traverse the same unselected subgraphs when sorting filtered project sets. This can cause avoidable CPU time and allocations in recursive commands on large workspaces.
## Issue Context
The new behavior is correct, but the current implementation does not reuse intermediate results across roots.
## Fix Focus Areas
- pnpm11/workspace/projects-sorter/src/index.ts[15-62]
## Suggested fix
- Add a per-`sequenceGraph()` invocation memoization map, e.g. `Map<ProjectRootDir, Set<ProjectRootDir>>` (or array) keyed by projectDir.
- When exploring an unselected node, reuse its memoized "reachable selected dependencies" result instead of traversing its dependencies again.
- Keep the existing "stop traversal when reaching a selected node" behavior to avoid adding redundant edges.
- Ensure cycles are handled safely (e.g. a `visiting` set) so memoization doesn’t recurse infinitely.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

16. Unnecessary Set allocation 🐞 Bug ➹ Performance
Description
filterProjectsBySelectorObjects() always builds regularSelectedProjectDirs (a Set of
regular-selected project dirs) even when no --filter-prod selectors are present, but that Set is
only needed to compute prodOnlySelectedProjectDirs for mixed selections. This adds avoidable O(k)
time and memory on regular-only --filter runs in large workspaces.
Code

pnpm11/workspace/projects-filter/src/index.ts[R146-150]

+    const regularSelectedProjectDirs = new Set(Object.keys(filteredGraph?.selectedProjectsGraph ?? {}) as ProjectRootDir[])
+    const prodOnlySelectedProjectDirs = prodFilteredGraph == null
+      ? undefined
+      : (Object.keys(prodFilteredGraph.selectedProjectsGraph) as ProjectRootDir[])
+        .filter((projectDir) => !regularSelectedProjectDirs.has(projectDir))
Evidence
The Set is created before checking whether a prod-filtered graph exists, but
prodOnlySelectedProjectDirs becomes undefined when prodFilteredGraph == null, making the Set
unused for regular-only filtering.

pnpm11/workspace/projects-filter/src/index.ts[146-150]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`filterProjectsBySelectorObjects()` constructs `regularSelectedProjectDirs` unconditionally, but it is only used when `prodFilteredGraph` exists (i.e., when `--filter-prod` selectors are present). For regular-only filtering, the Set allocation and key scan are dead work.
### Issue Context
This code runs in the filtering step used by many commands. For large selections/workspaces, the extra `Object.keys(...)+new Set(...)` can add measurable overhead even though it doesn’t change behavior.
### Fix Focus Areas
- pnpm11/workspace/projects-filter/src/index.ts[146-150]
### Suggested fix
Move creation of `regularSelectedProjectDirs` into the `prodFilteredGraph != null` branch (or compute it lazily only when `prodProjectSelectors.length > 0`). For example:
- Keep `prodOnlySelectedProjectDirs` undefined when `prodFilteredGraph` is null.
- Only then compute `regularSelectedProjectDirs` and filter prod-selected dirs against it.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread pnpm11/exec/commands/test/runRecursive.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pnpm11/workspace/projects-sorter/src/index.ts`:
- Around line 15-25: The graph traversal in sequenceGraph is reintroducing
dependencies that selectedProjectsGraph has already pruned. Update the
sequencing logic so traversal begins from each selected node’s own dependencies
array, and only falls back to fullProjectsGraph/ProjectsGraph when descending
into an unselected node via sortedDependencies. Keep the fix localized around
sequenceGraph and sortedDependencies, and add a regression test for the
prod-pruned graph shape to ensure dev-only edges are not pulled back into the
order.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 926591f8-6574-40eb-afbb-7123086a71c7

📥 Commits

Reviewing files that changed from the base of the PR and between 103b99b and 486d13a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml, !pnpm-lock.yaml
📒 Files selected for processing (12)
  • .changeset/sort-filtered-transitive-deps.md
  • pnpm11/building/commands/src/build/rebuild.ts
  • pnpm11/building/commands/src/build/recursive.ts
  • pnpm11/exec/commands/src/exec.ts
  • pnpm11/exec/commands/src/runRecursive.ts
  • pnpm11/exec/commands/test/runRecursive.ts
  • pnpm11/releasing/commands/src/publish/pack.ts
  • pnpm11/releasing/commands/src/publish/recursivePublish.ts
  • pnpm11/workspace/projects-sorter/package.json
  • pnpm11/workspace/projects-sorter/src/index.ts
  • pnpm11/workspace/projects-sorter/test/index.ts
  • pnpm11/workspace/projects-sorter/test/tsconfig.json

Comment thread pnpm11/workspace/projects-sorter/src/index.ts Outdated
Comment thread pnpm11/workspace/projects-sorter/src/index.ts
Comment thread pnpm11/workspace/projects-sorter/src/index.ts
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 486d13a

@github-actions github-actions Bot added the reviewed: coderabbit CodeRabbit submitted an approving review label Jun 26, 2026
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 0ae5a7d

1 similar comment
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 0ae5a7d

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 210fccd

1 similar comment
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 210fccd

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

Commit: 633355316c9e

Each scenario reports direct installs and pnpr installs. Bencher consumes pacquet@HEAD and pnpr@HEAD.

Scenario: Isolated linker: fresh restore, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 3.834 ± 0.153 3.643 4.101 1.79 ± 0.11
pacquet@main 3.816 ± 0.157 3.613 4.043 1.78 ± 0.11
pnpr@HEAD 2.206 ± 0.123 2.058 2.473 1.03 ± 0.07
pnpr@main 2.141 ± 0.097 2.039 2.391 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 3.833785202599999,
      "stddev": 0.15336252551829532,
      "median": 3.7928080488999996,
      "user": 3.59345436,
      "system": 3.475130299999999,
      "min": 3.6429956838999997,
      "max": 4.1008534789,
      "times": [
        3.8213014819,
        3.7548715538999997,
        4.0161950009,
        3.6429956838999997,
        3.9835233878999996,
        3.7643146158999996,
        3.8511273558999997,
        3.7301725669,
        4.1008534789,
        3.6724968999
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 3.8164683631999994,
      "stddev": 0.15705815384546318,
      "median": 3.7716645309,
      "user": 3.6149122599999997,
      "system": 3.4277737999999998,
      "min": 3.6134131368999998,
      "max": 4.0434065459,
      "times": [
        3.6670132168999996,
        3.6134131368999998,
        3.7165001359,
        3.7554275639,
        3.7879014978999996,
        3.6865470379,
        4.042664207900001,
        4.0434065459,
        3.9117004228999996,
        3.9401098659
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 2.2064497176,
      "stddev": 0.12314806673189574,
      "median": 2.1824225743999994,
      "user": 2.6730495600000004,
      "system": 3.0792602,
      "min": 2.0583899668999996,
      "max": 2.4728960718999997,
      "times": [
        2.3247470759,
        2.4728960718999997,
        2.1525069299,
        2.0583899668999996,
        2.2184782649,
        2.0709012948999996,
        2.1793378558999996,
        2.1420634559,
        2.1855072928999997,
        2.2596689669
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 2.1406454684,
      "stddev": 0.09699068921237741,
      "median": 2.1214143894,
      "user": 2.66800326,
      "system": 3.0446864999999996,
      "min": 2.0393865328999996,
      "max": 2.3911926019,
      "times": [
        2.1342023318999996,
        2.1070162479,
        2.0592016179,
        2.0994007178999996,
        2.0393865328999996,
        2.1086264469,
        2.1430791859,
        2.1714669209,
        2.1528820799,
        2.3911926019
      ]
    }
  ]
}

Scenario: Isolated linker: fresh restore, hot cache + hot store

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 652.8 ± 10.0 636.6 669.6 1.00
pacquet@main 669.0 ± 41.2 642.2 779.0 1.02 ± 0.07
pnpr@HEAD 719.8 ± 19.0 684.1 750.3 1.10 ± 0.03
pnpr@main 727.9 ± 21.4 703.4 770.1 1.12 ± 0.04
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.65277479728,
      "stddev": 0.010035724908680814,
      "median": 0.6528855290800001,
      "user": 0.3890287,
      "system": 1.3351796999999999,
      "min": 0.63657481258,
      "max": 0.6696339745800001,
      "times": [
        0.6475405755800001,
        0.63657481258,
        0.6554923755800001,
        0.64464169658,
        0.65799508658,
        0.64349128058,
        0.6621046825800001,
        0.6599948055800001,
        0.6502786825800001,
        0.6696339745800001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.66904267928,
      "stddev": 0.041186870859110616,
      "median": 0.6504122495800001,
      "user": 0.3991192,
      "system": 1.3428187,
      "min": 0.6422206165800001,
      "max": 0.7790069355800001,
      "times": [
        0.6489153085800001,
        0.6823474345800001,
        0.66400106958,
        0.6499424665800001,
        0.6422206165800001,
        0.6450720505800001,
        0.6508820325800001,
        0.6477646745800001,
        0.6802742035800001,
        0.7790069355800001
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.7197881837800002,
      "stddev": 0.01895610708569031,
      "median": 0.7161338370800001,
      "user": 0.3963076,
      "system": 1.3790139,
      "min": 0.68409460858,
      "max": 0.7502761195800001,
      "times": [
        0.7067203865800001,
        0.7502761195800001,
        0.7370674995800001,
        0.7143696655800001,
        0.7139694475800001,
        0.68409460858,
        0.7196419905800001,
        0.7178980085800001,
        0.7409313725800001,
        0.71291273858
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.7279137053800001,
      "stddev": 0.021357600969765227,
      "median": 0.7195573500800001,
      "user": 0.3997613,
      "system": 1.3836737,
      "min": 0.7034056675800001,
      "max": 0.7700592955800001,
      "times": [
        0.7393842195800001,
        0.7545192835800001,
        0.7335956275800001,
        0.7176117685800001,
        0.7179761695800001,
        0.7034056675800001,
        0.7211385305800001,
        0.7700592955800001,
        0.70799338058,
        0.71345311058
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + cold store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 4.297 ± 0.066 4.236 4.458 1.96 ± 0.11
pacquet@main 4.280 ± 0.033 4.249 4.369 1.95 ± 0.11
pnpr@HEAD 2.192 ± 0.117 2.020 2.395 1.00
pnpr@main 2.215 ± 0.161 2.050 2.415 1.01 ± 0.09
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 4.297268311500001,
      "stddev": 0.06580595533371197,
      "median": 4.2844000576,
      "user": 3.77372732,
      "system": 3.43198218,
      "min": 4.2356838321,
      "max": 4.4577713481000005,
      "times": [
        4.2356838321,
        4.2696241371,
        4.2592508881,
        4.3017993651,
        4.2476039631,
        4.3153802941,
        4.2465142261,
        4.2991759781,
        4.4577713481000005,
        4.3398790831000005
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 4.2800351724,
      "stddev": 0.03307671273406055,
      "median": 4.2699534156,
      "user": 3.7615581199999992,
      "system": 3.4142474799999993,
      "min": 4.2485324251,
      "max": 4.3692386821,
      "times": [
        4.3692386821,
        4.2664876251,
        4.2640661301,
        4.2806156101,
        4.2651742111,
        4.2724817581000005,
        4.2840420441000004,
        4.2822881651,
        4.2674250731,
        4.2485324251
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 2.1919842029,
      "stddev": 0.11718136280720254,
      "median": 2.1962357506,
      "user": 2.48076442,
      "system": 2.9613381799999994,
      "min": 2.0199174851,
      "max": 2.3949898561,
      "times": [
        2.2606584641,
        2.1129751741000002,
        2.3063709081,
        2.1191452411,
        2.2120736071000002,
        2.0199174851,
        2.2576088921,
        2.1803978941,
        2.3949898561,
        2.0557045071
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 2.2152257851000003,
      "stddev": 0.1613794133521661,
      "median": 2.1684407855999996,
      "user": 2.5291655200000003,
      "system": 2.95335268,
      "min": 2.0504413721,
      "max": 2.4150853031,
      "times": [
        2.0548797851000002,
        2.3593359081000003,
        2.1003735531,
        2.3983797661,
        2.0504413721,
        2.0559610861,
        2.4150853031,
        2.2365080181,
        2.3938890111,
        2.0874040481000002
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, hot cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.364 ± 0.021 1.338 1.408 1.99 ± 0.03
pacquet@main 1.354 ± 0.020 1.324 1.401 1.98 ± 0.03
pnpr@HEAD 0.689 ± 0.044 0.663 0.813 1.01 ± 0.06
pnpr@main 0.685 ± 0.005 0.677 0.692 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.36401573176,
      "stddev": 0.021177715626198978,
      "median": 1.3579823205600001,
      "user": 1.33032598,
      "system": 1.7130967,
      "min": 1.33793260706,
      "max": 1.4076150520600001,
      "times": [
        1.4076150520600001,
        1.38484651006,
        1.3466256030600001,
        1.33793260706,
        1.35446361106,
        1.37228211506,
        1.3615010300600001,
        1.34601270606,
        1.35374966406,
        1.3751284190600002
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.35368285156,
      "stddev": 0.02041848983838078,
      "median": 1.35510552656,
      "user": 1.32829728,
      "system": 1.707379,
      "min": 1.32429010606,
      "max": 1.40140614706,
      "times": [
        1.36007552006,
        1.35540751506,
        1.35879533006,
        1.33844734706,
        1.32429010606,
        1.3506033880600001,
        1.40140614706,
        1.33663177606,
        1.35480353806,
        1.35636784806
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.6893910274599999,
      "stddev": 0.04410123826821697,
      "median": 0.67788733656,
      "user": 0.33463378,
      "system": 1.3180801999999998,
      "min": 0.6631561960600001,
      "max": 0.81269267706,
      "times": [
        0.6670305700600001,
        0.6715213840600001,
        0.6631561960600001,
        0.81269267706,
        0.68272140806,
        0.6767504950600001,
        0.67902417806,
        0.6798957050600001,
        0.6914096500600001,
        0.6697080110600001
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.68533327656,
      "stddev": 0.005338854928215828,
      "median": 0.6854411770600001,
      "user": 0.35575708,
      "system": 1.3051229,
      "min": 0.67738713206,
      "max": 0.69209701306,
      "times": [
        0.68601036406,
        0.69025173906,
        0.6914436540600001,
        0.67764138606,
        0.6818117380600001,
        0.67738713206,
        0.68333858806,
        0.6884791610600001,
        0.6848719900600001,
        0.69209701306
      ]
    }
  ]
}

Scenario: Isolated linker: fresh install, cold cache + hot store

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 3.046 ± 0.041 3.007 3.138 4.47 ± 0.13
pacquet@main 3.081 ± 0.047 3.020 3.163 4.52 ± 0.13
pnpr@HEAD 0.696 ± 0.016 0.673 0.725 1.02 ± 0.04
pnpr@main 0.682 ± 0.017 0.650 0.714 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 3.04622938404,
      "stddev": 0.04066335456548295,
      "median": 3.0396884143399996,
      "user": 1.7854619,
      "system": 2.0075180599999998,
      "min": 3.00694979784,
      "max": 3.13827748484,
      "times": [
        3.07727701884,
        3.00771952784,
        3.01111000684,
        3.00694979784,
        3.03533689384,
        3.02243460684,
        3.04403993484,
        3.06793183784,
        3.13827748484,
        3.05121673084
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 3.08147207944,
      "stddev": 0.047496047531306686,
      "median": 3.0805297833400003,
      "user": 1.8224636999999997,
      "system": 2.0088787599999995,
      "min": 3.02047117484,
      "max": 3.16256444984,
      "times": [
        3.10098283584,
        3.12274334284,
        3.03369369184,
        3.04371473384,
        3.02047117484,
        3.06332488284,
        3.12716121584,
        3.16256444984,
        3.09773468384,
        3.04232978284
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.69556389444,
      "stddev": 0.016246353110490903,
      "median": 0.69426291634,
      "user": 0.33688179999999995,
      "system": 1.3520729599999999,
      "min": 0.67267040784,
      "max": 0.7245183848400001,
      "times": [
        0.6843842358400001,
        0.69306685284,
        0.68316408984,
        0.71772410684,
        0.67267040784,
        0.68317201084,
        0.6968786638400001,
        0.7245183848400001,
        0.69545897984,
        0.70460121184
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.6822304904400001,
      "stddev": 0.017311952187210586,
      "median": 0.67933081884,
      "user": 0.3588369,
      "system": 1.2941326599999996,
      "min": 0.64980060784,
      "max": 0.7136003578400001,
      "times": [
        0.67720798884,
        0.67968174684,
        0.6748007848400001,
        0.67897989084,
        0.69591151484,
        0.64980060784,
        0.66875400484,
        0.7136003578400001,
        0.6943092228400001,
        0.68925878484
      ]
    }
  ]
}

Scenario: Isolated linker: fresh restore, cold cache + cold store + cold pnpr

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 7.264 ± 0.101 7.137 7.401 1.29 ± 0.02
pacquet@main 7.340 ± 0.130 7.090 7.513 1.30 ± 0.03
pnpr@HEAD 5.650 ± 0.103 5.493 5.863 1.00 ± 0.02
pnpr@main 5.639 ± 0.076 5.556 5.758 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 7.263752312700001,
      "stddev": 0.10061113008600972,
      "median": 7.2585692414,
      "user": 3.87677342,
      "system": 3.7708254400000003,
      "min": 7.1371803189,
      "max": 7.4011984679,
      "times": [
        7.2061849338999995,
        7.2692348418999995,
        7.3279484569,
        7.4011984679,
        7.3718187689,
        7.1371803189,
        7.1531808069,
        7.1506213959,
        7.3722514948999995,
        7.2479036409
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 7.339791829899999,
      "stddev": 0.12991448062615543,
      "median": 7.3534186739,
      "user": 3.92092502,
      "system": 3.75671324,
      "min": 7.0900948119,
      "max": 7.5129996139,
      "times": [
        7.2924375079,
        7.5129996139,
        7.2412266678999995,
        7.260405405899999,
        7.4143998399,
        7.0900948119,
        7.2675893739,
        7.4313037808999995,
        7.4600116649,
        7.4274496319
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 5.6495864597,
      "stddev": 0.1031863498137556,
      "median": 5.6540028834,
      "user": 2.825710419999999,
      "system": 3.35203204,
      "min": 5.4925726058999995,
      "max": 5.8625686328999995,
      "times": [
        5.6757849199,
        5.5862063659,
        5.6018493379,
        5.5475381959,
        5.8625686328999995,
        5.7131595769,
        5.4925726058999995,
        5.7081791949,
        5.6327624419,
        5.675243324899999
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 5.6388930785,
      "stddev": 0.0759856651270492,
      "median": 5.6123279774,
      "user": 2.82053942,
      "system": 3.3312157399999998,
      "min": 5.5560278918999995,
      "max": 5.758304745899999,
      "times": [
        5.5803553329,
        5.5777223379,
        5.5560278918999995,
        5.5700233559,
        5.6196653559,
        5.7425226979,
        5.6049905989,
        5.6621414769,
        5.7171769909,
        5.758304745899999
      ]
    }
  ]
}

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12688
Testbedpacquet
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
milliseconds (ms)
(Result Δ%)
Upper Boundary
milliseconds (ms)
(Limit %)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
🚷 view threshold
4,297.27 ms
(-7.42%)Baseline: 4,641.77 ms
5,570.13 ms
(77.15%)
isolated-linker.fresh-install.cold-cache.hot-store📈 view plot
🚷 view threshold
3,046.23 ms
(-0.34%)Baseline: 3,056.74 ms
3,668.08 ms
(83.05%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,364.02 ms
(+0.43%)Baseline: 1,358.15 ms
1,629.78 ms
(83.69%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
3,833.79 ms
(-16.46%)Baseline: 4,589.37 ms
5,507.24 ms
(69.61%)
isolated-linker.fresh-restore.cold-cache.cold-store.cold-pnpr📈 view plot
🚷 view threshold
7,263.75 ms
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
652.77 ms
(+3.64%)Baseline: 629.83 ms
755.80 ms
(86.37%)
🐰 View full continuous benchmarking report in Bencher

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12688
Testbedpnpr

⚠️ WARNING: No Threshold found!

Without a Threshold, no Alerts will ever be generated.

Click here to create a new Threshold
For more information, see the Threshold documentation.
To only post results if a Threshold exists, set the --ci-only-thresholds flag.

Click to view all benchmark results
BenchmarkLatencymilliseconds (ms)
isolated-linker.fresh-install.cold-cache.cold-store📈 view plot
⚠️ NO THRESHOLD
2,191.98 ms
isolated-linker.fresh-install.cold-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
695.56 ms
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
689.39 ms
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
⚠️ NO THRESHOLD
2,206.45 ms
isolated-linker.fresh-restore.cold-cache.cold-store.cold-pnpr📈 view plot
⚠️ NO THRESHOLD
5,649.59 ms
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
719.79 ms
🐰 View full continuous benchmarking report in Bencher

@adrian-niculescu

Copy link
Copy Markdown
Contributor Author

@qodo-free-for-open-source-projects the remaining open items don't need code changes. Going through them:

1. Not sorting full workspace graph (requirement gap). The relative order among selected projects already matches a full-graph topological sort: each selected project is sequenced after every selected project it transitively depends on, resolved through the full graph. Ordering the whole graph and then filtering its chunks would also serialize selected projects that have no dependency between them, because their unselected connectors fall into earlier chunks. That is the parallelism loss the original report asks to avoid, so the chunks are computed over the selected set on purpose.

3. Windows-unsafe test command. > inside a double-quoted string is not a redirection operator in cmd.exe, and the same node -e "setTimeout(() => ...)" pattern is already used by four other tests in this file. The three Windows test shards on the latest commit (210fccd) are green, which confirms it runs fine.

4. Repeated graph traversal (performance). sortedDependencies runs once per recursive command, at sort time, before any script runs. The traversal is bounded by the workspace size and is dwarfed by the command execution that follows, so it is not a hot path. The tunneling only happens when a filter leaves a connecting project unselected, which is the case this change exists to handle.

Item 2 (prod-only filter pulling dev edges) is fixed in 0ae5a7d with a regression test.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

free-for-open-source-projects the remaining open items don't need code changes. Going through them:

1. Not sorting full workspace graph (requirement gap). The relative order among selected projects already matches a full-graph topological sort: each selected project is sequenced after every selected project it transitively depends on, resolved through the full graph. Ordering the whole graph and then filtering its chunks would also serialize selected projects that have no dependency between them, because their unselected connectors fall into earlier chunks. That is the parallelism loss the original report asks to avoid, so the chunks are computed over the selected set on purpose.

3. Windows-unsafe test command. > inside a double-quoted string is not a redirection operator in cmd.exe, and the same node -e "setTimeout(() => ...)" pattern is already used by four other tests in this file. The three Windows test shards on the latest commit (210fccd) are green, which confirms it runs fine.

4. Repeated graph traversal (performance). sortedDependencies runs once per recursive command, at sort time, before any script runs. The traversal is bounded by the workspace size and is dwarfed by the command execution that follows, so it is not a hot path. The tunneling only happens when a filter leaves a connecting project unselected, which is the case this change exists to handle.

Item 2 (prod-only filter pulling dev edges) is fixed in 0ae5a7d with a regression test.

Thanks — that rationale is solid, and I agree the remaining items don’t need code changes.

  • Item 1: Sorting over the selected set is intentional here to preserve parallelism among unrelated filtered projects.
  • Item 3: The test command looks Windows-safe in practice, and the existing usage pattern + green Windows shards support that.
  • Item 4: The extra traversal isn’t on the hot path, so it’s reasonable to keep the implementation as-is.

Also, item 2 is already fixed in 0ae5a7d390 with a regression test.

So from my side, this looks good to go without further changes.

Comment thread pnpm11/workspace/projects-sorter/package.json
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 1d5430d

1 similar comment
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 1d5430d

@adrian-niculescu adrian-niculescu marked this pull request as draft June 26, 2026 22:48
@adrian-niculescu adrian-niculescu marked this pull request as ready for review June 27, 2026 14:43
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 9d30115

1 similar comment
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 9d30115

@adrian-niculescu

Copy link
Copy Markdown
Contributor Author

@qodo-free-for-open-source-projects the new items don't need a change either. 4 and 7 are the Windows node -e and traversal points from my earlier reply and are unchanged. The rest:

1. NODE_OPTIONS breaks Windows. This is the standard .test script used across more than 130 packages in this repo (cross-env NODE_OPTIONS="$NODE_OPTIONS ..." jest). This package adopts it unchanged, and the Windows test shards already run all of them, so nothing Windows-specific is introduced here.

5. Extra prod graph retention. prodAllProjectsGraph mirrors the existing allProjectsGraph, which is already a full graph on ConfigContext. Both are built from the same projects array, so the package objects are shared references, not copies; the only extra is the per-node edge lists, and only when --filter-prod is in use. pnpm is a short-lived CLI process, so it is released on exit. Shrinking just this one graph to an adjacency map while allProjectsGraph stays full would be inconsistent for no real saving.

6. Spread push crash risk. transitiveDeps is a single project's workspace dependencies, bounded by the number of workspace packages. Reaching the spread argument limit would take tens of thousands of direct workspace deps on one project, which does not happen in a real workspace.

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

free-for-open-source-projects the new items don't need a change either. 4 and 7 are the Windows node -e and traversal points from my earlier reply and are unchanged. The rest:

1. NODE_OPTIONS breaks Windows. This is the standard .test script used across more than 130 packages in this repo (cross-env NODE_OPTIONS="$NODE_OPTIONS ..." jest). This package adopts it unchanged, and the Windows test shards already run all of them, so nothing Windows-specific is introduced here.

5. Extra prod graph retention. prodAllProjectsGraph mirrors the existing allProjectsGraph, which is already a full graph on ConfigContext. Both are built from the same projects array, so the package objects are shared references, not copies; the only extra is the per-node edge lists, and only when --filter-prod is in use. pnpm is a short-lived CLI process, so it is released on exit. Shrinking just this one graph to an adjacency map while allProjectsGraph stays full would be inconsistent for no real saving.

6. Spread push crash risk. transitiveDeps is a single project's workspace dependencies, bounded by the number of workspace packages. Reaching the spread argument limit would take tens of thousands of direct workspace deps on one project, which does not happen in a real workspace.

Makes sense — I agree those don’t look like blockers here.

  • NODE_OPTIONS: this matches the repo’s existing test-script pattern, so Windows behavior isn’t newly introduced by this package.
  • prodAllProjectsGraph: keeping the full graph is consistent with allProjectsGraph, and the extra memory is limited to the CLI process lifetime.
  • spread / push risk: the fan-out is bounded by workspace package count, so this stays theoretical.

And if 4 and 7 are unchanged from the earlier discussion, then I don’t see a new issue in those paths either. The new tests also cover the transitive and mixed --filter / --filter-prod cases well.

@adrian-niculescu

Copy link
Copy Markdown
Contributor Author

@zkochan, read you changes in 9feb545 thanks for simplifying!

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 9feb545

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit bace374

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit bace374

@zkochan zkochan enabled auto-merge June 29, 2026 22:27
@zkochan zkochan added this pull request to the merge queue Jun 29, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to no response for status checks Jun 29, 2026
@adrian-niculescu

Copy link
Copy Markdown
Contributor Author

@zkochan,

@github-merge-queue bot "removed this pull request from the merge queue due to no response for status checks"

can you please add it back?

@adrian-niculescu

Copy link
Copy Markdown
Contributor Author

Now this has merge conflicts again. I'll resolve them.

adrian-niculescu and others added 12 commits July 1, 2026 12:04
When a recursive command (run, exec, publish, pack, rebuild) is filtered to a
subset of projects, the topological sort ran on the selected subgraph alone and
dropped edges that pass through unselected projects. Two selected projects
related only transitively could then run concurrently or in the wrong order.

The sort now resolves edges through the full workspace graph, so a project runs
after the selected projects it transitively depends on, while projects without a
real dependency relationship still run concurrently.

pnpm#8335
The sort seeded its traversal from the full workspace graph, so a prod-only
filter (followProdDepsOnly), which drops dev edges, had those edges reintroduced
into the order and serialized commands by dependencies the selection removed.

Seed each sorted project's own edges from the selected graph and consult the
full graph only to tunnel through unselected projects.
…r test

The new ordering test declared its inter-package deps with a plain version
range, which pnpm resolves from the registry when link-workspace-packages is
false (the default), so install -r failed with a 404 on CI. Use the workspace:
protocol so the packages link locally, matching the other install-based e2e
tests.
…rt order

The transitive ordering of --filtered recursive commands tunneled through the
full workspace graph, which keeps dev dependencies. For a prod-only filter
(--filter-prod), whose selected graph has dev edges pruned, that reintroduced
the dropped edges and could serialize commands by dependencies the filter
removed; with --bail, a failing dependency would then block its dependent.

Resolve order among the selected projects only when a prod-only filter is in
effect, which restores the previous behavior for that mode while keeping the
transitive fix for regular filters.
…graph

The filtered sort dropped the full workspace graph whenever a `--filter-prod`
was present. That regressed two cases:

- A regular `--filter` combined with any `--filter-prod` lost the transitive
  ordering for the regularly filtered projects, so `a -> b -> c` with only `a`
  and `c` selected could run in the same chunk.
- A pure `--filter-prod` selection dropped real transitive prod edges, so a prod
  dependency could run after its dependent.

The sort now resolves each selected project through the graph that matches how
it was selected: prod-only selected projects through the prod-pruned graph, and
regularly selected projects through the full graph. Keeping prod-only projects
on the full graph would let a dev-only reverse edge form a cycle and collapse a
real prod dependency and its dependent into one chunk, so the graph is picked
per project.
A regular filter reaching a selected project through a prod-only
intermediate was an untested combination. Lock in that a dev edge
pruned by the prod-only selection severs the path (the dependent stays
concurrent), while a prod edge kept in the prod graph still chains the
full order through.
The producer couples prodAllProjectsGraph and prodOnlySelectedProjectDirs:
both are gated on prodProjectSelectors.length > 0, so a prod graph never
arrives without a (possibly empty) prodOnlySelectedProjectDirs array. The
branch handling 'prod graph set, dirs null' was therefore unreachable and
uncovered. Removed it and let the per-project branch handle the empty set,
where every project resolves through the full graph. Also hoisted the
repeated allProjectsGraph ?? selectedProjectsGraph fallback into one local.
Port pnpm's sortFilteredProjects fix to the pacquet recursive run / exec /
pack runners.

The runners sorted the --filter-selected projects, but the sorter dropped
edges to projects outside the selection. Two selected projects related only
transitively (a -> b -> c with a and c selected, b filtered out) could then
land in the same chunk and run out of order. The sorter now resolves order
through the full workspace graph: each selected project walks its
dependencies, tunnels past unselected projects, and stops at selected ones,
so a transitive relationship becomes a direct edge. Projects with no real
dependency stay in one chunk, preserving concurrency.

A --filter-prod selection prunes dev edges. Its projects sort through the
prod-pruned graph and seed their own edges from it, so the dropped dev edges
are not pulled back into the order. select_recursive_projects returns the
full and prod-pruned graphs and the prod-only-selected dirs alongside the
selection, filters against those prebuilt graphs so no graph is rebuilt per
filter call, and lists a mixed --filter / --filter-prod selection prod-first
to match pnpm's { ...prodFilteredGraph, ...filteredGraph } merge — the
recursive runners execute and print a chunk's projects in insertion order.

The graphs honor the configured link-workspace-packages, so a bare-semver
range naming a sibling drives neither selection nor order under the default
policy, matching pnpm. An unfiltered run keeps a single graph without
cloning, and a regular --filter run skips the prod-only bookkeeping.

Fixes pnpm#8335.
sequenceGraph and sortProjects had grown an optional second graph argument that
no production code passed; only tests and sortFilteredProjects's no-prod branch
used it. Both are single-argument again, and sortFilteredProjects drives the
private sequenceGraphByProject engine directly, so full/prod-pruned-graph
resolution lives in one place and the package exposes only single-graph sorting
plus sortFilteredProjects.

No behavior change: single-argument sequenceGraph and sortProjects order
projects by their own edges as before, and sortFilteredProjects yields the same
chunks. Tests exercise single-graph sorting through sequenceGraph/sortProjects
and all tunneling behavior through sortFilteredProjects.
sequenceGraph and sortProjects sort a single graph (recursive install/update and
cycle-warning checks), where every edge is already among the sorted projects and
no tunneling is needed. Routing them through sequenceGraphByProject allocated two
Sets and a stack per project for nothing, and changed the chunk order on cyclic
graphs.

Restored the direct dependency filter on sequenceGraph and left the tunneling
engine to sortFilteredProjects. The full-graph sort sheds the per-project
allocations and matches the pre-change ordering again, including for cycles.
The "`all` is `None` on an unfiltered run because `selected` is already the
full graph" invariant was re-derived on the `RecursiveSelection.all` field,
the `full_graph` accessor, and `select_recursive_projects`. Keep it on the
field and have the accessor and producer reference it instead.
…n test

The mixed-filter ordering test cited the TypeScript
`{ ...prodFilteredGraph, ...filteredGraph }` spread; describe the ordering as
pacquet's own behavior, consistent with the parity paradigm (pnpm#12737)
and the already-de-cited twin in recursive.rs.
@adrian-niculescu adrian-niculescu force-pushed the fix/topo-sort-filtered-transitive-deps branch from bace374 to 6333553 Compare July 1, 2026 09:38
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Qodo is busy working

Check back in a few minutes. Qodo's code review agents are on it.

Grey Divider

@adrian-niculescu

adrian-niculescu commented Jul 1, 2026

Copy link
Copy Markdown
Contributor Author

@zkochan rebased onto main, conflicts resolved. Heads-up: your 9feb545a5a was superseded by #12737 (repo-wide citation cleanup), so I dropped it; the all=None doc commit stayed.

I also noticed the zizmor check is failing, but it is pre-existing on main and unrelated to this PR (no workflow files changed here).

@zkochan zkochan merged commit c121235 into pnpm:main Jul 2, 2026
32 of 33 checks passed
@welcome

welcome Bot commented Jul 2, 2026

Copy link
Copy Markdown

Congrats on merging your first pull request! 🎉🎉🎉

@adrian-niculescu adrian-niculescu deleted the fix/topo-sort-filtered-transitive-deps branch July 2, 2026 16:15
KSXGitHub pushed a commit that referenced this pull request Jul 3, 2026
Resolve the dispatch_query.rs import conflict (keep both the new prefix
command and this branch's publish command, alphabetically ordered) and
adapt publish's recursive handler to the recursive-selection API change
from #12688: select_recursive_projects now returns a
RecursiveSelection, and ordering goes through sort_filtered_projects
(selected + full/prod graphs) instead of sort_projects.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Flawed topological sort for subgraph selected with --filter

3 participants