Skip to content
This repository was archived by the owner on May 14, 2026. It is now read-only.

feat: propagate pnpm-workspace.yaml load errors#404

Merged
zkochan merged 11 commits into
mainfrom
claude/add-workspace-yaml-support-nXe35
May 9, 2026
Merged

feat: propagate pnpm-workspace.yaml load errors#404
zkochan merged 11 commits into
mainfrom
claude/add-workspace-yaml-support-nXe35

Conversation

@KSXGitHub

@KSXGitHub KSXGitHub commented May 7, 2026

Copy link
Copy Markdown
Contributor

Resolves #402.

What this PR does

Pacquet already reads pnpm-workspace.yaml (crates/npmrc/src/workspace_yaml.rs defines WorkspaceSettings and Npmrc::current walks up from cwd to layer it on top of .npmrc). The remaining divergence from pnpm 11 was on the error path. Upstream's workspace-manifest-reader silently treats ENOENT as "no manifest" and propagates every other failure (read errors, parse errors, EISDIR, permission denied, …). Pacquet swallowed all errors via if let Ok(Some(...)), and its find_workspace_manifest helper used an is_file() filter that also masked non-regular entries (e.g. a directory named pnpm-workspace.yaml).

This PR closes both gaps.

Changes

  • crates/npmrc/src/workspace_yaml.rs
    • Replace the bare Debug-only LoadWorkspaceYamlError with a Display + Error + Diagnostic enum that stores path + source directly on each struct variant, mirroring pacquet_modules_yaml::ReadModulesError. Only serde_saphyr::Error is boxed (io::Error fits the variant inline). The single-place path formatting keeps the miette Caused by: chain free of duplication.
    • Rewrite find_and_load as a single read-and-catch loop over start_dir.ancestors(): at each level read pnpm-workspace.yaml, treat only ErrorKind::NotFound as "no manifest here, walk up", and propagate every other error through LoadWorkspaceYamlError. This matches pnpm's readManifestRaw semantics, closes the previous TOCTOU window between the old is_file() check and the read, and surfaces non-regular entries (directory, EISDIR, permission denied) as hard errors instead of silently walking up.
  • crates/npmrc/src/lib.rs
    • Npmrc::current is now Result<Self, LoadWorkspaceYamlError>. The .npmrc parsing stays best-effort; the yaml parsing fails loud.
    • Two regression tests added in this round: invalid_workspace_yaml_propagates_error (parse error) and find_propagates_when_manifest_path_is_a_directory (EISDIR-class error). Existing tests adopt the fallible signature with .expect(...).
  • crates/cli/src/cli_args.rs
    • The npmrc closure now returns miette::Result<&'static mut Npmrc> and is wrapped with wrap_err("load configuration"). The state closure threads ?.
  • crates/cli/src/cli_args/store.rs
    • StoreCommand::run accepts impl FnOnce() -> miette::Result<&Npmrc> so Prune and Path get the new error path; Store and Add still panic before loading config.
  • crates/npmrc/Cargo.toml
    • Add derive_more and miette deps for the new error type.

Test plan

  • cargo fmt
  • cargo check --workspace --all-targets
  • cargo clippy --workspace --all-targets -- --deny warnings
  • typos
  • cargo test -p pacquet-npmrc (37 tests, including the two new regression tests)
  • cargo test -p pacquet-cli --lib and cargo test -p pacquet-cli --test store
  • cargo test -p pacquet-cli integration tests that need just registry-mock launch (not exercised in this sandbox; pre-existing on main)

Written by an agent (Claude Code, claude-opus-4-7).

Summary by CodeRabbit

  • Bug Fixes

    • Configuration loading now properly catches and reports errors when workspace manifest files are invalid or unreadable, preventing silent failures.
    • Enhanced error diagnostics to clearly indicate the specific configuration file location and nature of failure.
  • Tests

    • Added tests to verify proper error propagation for malformed configuration files and edge cases in manifest loading.

Pnpm's workspace-manifest-reader (workspace/workspace-manifest-reader/
src/index.ts at 8eb1be4988) treats only `ENOENT` as "no manifest" —
every other error (read failure, invalid yaml) fails the process.
Pacquet was silently swallowing all errors via `if let Ok(Some(...))`,
which left typos in `pnpm-workspace.yaml` undetected and silently
fell back to `.npmrc`/defaults, diverging from pnpm 11.

Make `Npmrc::current` fallible. `find_and_load` already returns
`Ok(None)` for the missing-file case (file presence is gated by
`is_file()`); read/parse errors now bubble up wrapped in a new
`LoadWorkspaceYamlError` that carries the offending path and
implements `miette::Diagnostic` so the CLI prints a clean message.
The variants box their inner payloads to keep the result small.

Update the CLI call sites and add a test that asserts invalid yaml
surfaces as `ParseYaml` instead of being silently dropped.

Resolves #402.

---
Written by an agent (Claude Code, claude-opus-4-7).
@KSXGitHub KSXGitHub requested a review from zkochan May 7, 2026 23:42
@coderabbitai

coderabbitai Bot commented May 7, 2026

Copy link
Copy Markdown

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 963fcddd-b928-465b-bc57-f34cd9105cad

📥 Commits

Reviewing files that changed from the base of the PR and between 62eac2a and 59168e1.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • crates/cli/src/cli_args.rs
  • crates/npmrc/src/workspace_yaml.rs
  • crates/npmrc/src/workspace_yaml/tests.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/cli/src/cli_args.rs
  • crates/npmrc/src/workspace_yaml.rs
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Lint and Test (ubuntu-latest)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Lint and Test (windows-latest)
  • GitHub Check: Run benchmark on ubuntu-latest
  • GitHub Check: Code Coverage
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rs

📄 CodeRabbit inference engine (AGENTS.md)

**/*.rs: Log emissions are part of 'match pnpm'—when porting a function that fires pnpm:<channel> events through globalLogger, logger.debug(...), or streamParser.write(...), mirror the call site, payload, and ordering so @pnpm/cli.default-reporter parses pacquet's NDJSON the same way
Declare a newtype wrapper for branded string types instead of collapsing into plain String or &str to preserve the distinction between validating and non-validating brands
If upstream always validates before construction of a branded string type, implement validation via TryFrom<String> and/or FromStr without providing an infallible public constructor
If upstream never validates a branded string type, expose an infallible From<String> constructor to provide type-safety without runtime validation
When upstream occasionally constructs a branded string type without validation via bare as assertion, expose a from_str_unchecked constructor so callers can opt into the same unchecked path explicitly
Match upstream serde behavior for branded types—use #[serde(try_from = "String")] for deserialization to go through the validator and #[serde(into = "String")] for serialization
Use #[derive(derive_more::From)] and #[derive(derive_more::Into)] for mechanical conversion impls on branded types rather than handwriting impl blocks
Model string-literal unions (like 'auto' | 'always' | 'never') as Rust enums instead of newtype wrappers
Template literal types (like ${string}@${string}) should be treated as branded string types with the same validation discipline applied
Follow the code style guide in CODE_STYLE_GUIDE.md covering code-level conventions not enforced by tooling: imports, modules, naming, ownership and borrowing, parameter type selection, trait bounds, pattern matching, pipe-trait, error handling, test layout, and cloning of Arc and Rc
Warnings are errors in cargo clippy --locked -- --deny warnings, and should not be silenced with `#[allow(....

Files:

  • crates/npmrc/src/workspace_yaml/tests.rs
🧠 Learnings (2)
📚 Learning: 2026-05-01T10:01:33.766Z
Learnt from: zkochan
Repo: pnpm/pacquet PR: 349
File: crates/reporter/src/tests.rs:121-121
Timestamp: 2026-05-01T10:01:33.766Z
Learning: In Rust test code, follow the repo’s CODE_STYLE_GUIDE test-logging rule: add logging (e.g., `eprintln!`/`eprintln!(...)`) so that useful diagnostic values are printed when a test fails, unless the assertion is `assert_eq!` (where the differing values are already included). Concretely, if you use assertions like `assert!`, `assert_ne!`, etc., ensure the test logs the relevant actual/expected values (or context) before/around the assertion so failures can be diagnosed without rerunning.

Applied to files:

  • crates/npmrc/src/workspace_yaml/tests.rs
📚 Learning: 2026-05-07T23:19:08.272Z
Learnt from: KSXGitHub
Repo: pnpm/pacquet PR: 401
File: tasks/integrated-benchmark/src/work_env.rs:343-344
Timestamp: 2026-05-07T23:19:08.272Z
Learning: When reviewing Rust code in pnpm/pacquet for deprecated API usage, do not automatically treat `serde_saphyr::to_string` as deprecated. In `serde-saphyr` v0.0.25, `serde_saphyr::to_string` has no `#[deprecated]` attribute (the `#[deprecated]` later in `serde-saphyr-0.0.25/src/lib.rs` applies to a different function). Only flag `serde_saphyr::to_string` as deprecated if the resolved dependency version’s source shows `#[deprecated]` on that specific function.

Applied to files:

  • crates/npmrc/src/workspace_yaml/tests.rs
🔇 Additional comments (2)
crates/npmrc/src/workspace_yaml/tests.rs (2)

1-4: Imports are clean and scoped to the new behavior under test.

Nice update pulling in LoadWorkspaceYamlError and WORKSPACE_MANIFEST_FILENAME explicitly for the regression path.


155-174: Regression coverage matches the ENOENT-only swallow rule.

This test is well-targeted: a directory at pnpm-workspace.yaml now correctly exercises the propagated ReadFile error path.


📝 Walkthrough

Walkthrough

Configuration loading now explicitly reports pnpm-workspace.yaml read and parse failures via Result. LoadWorkspaceYamlError carries file-path context and is a miette Diagnostic. CLI threads the fallible config through closures and StoreCommand propagates configuration errors.

Changes

pnpm-workspace.yaml Error Propagation

Layer / File(s) Summary
Error Type Definition and Dependencies
crates/npmrc/Cargo.toml, crates/npmrc/src/workspace_yaml.rs
New diagnostic LoadWorkspaceYamlError variants include manifest path and underlying source; adds derive_more and miette dependencies.
Workspace YAML Loading with Error Context
crates/npmrc/src/workspace_yaml.rs
WorkspaceSettings::find_and_load reads the manifest with fs::read_to_string, treats NotFound as absent, maps other read failures to ReadFile { path, source }, maps parse failures to ParseYaml { path, source }, and returns Ok(Some(...)) on success or Ok(None) if missing.
Npmrc Configuration Loading
crates/npmrc/src/lib.rs
Npmrc::current now returns Result<Self, LoadWorkspaceYamlError> and uses WorkspaceSettings::find_and_load(&start)? so YAML read/parse failures propagate; layering otherwise unchanged.
CLI Integration with Fallible Config Closure
crates/cli/src/cli_args.rs, crates/cli/src/cli_args/store.rs
CliArgs::run builds a fallible npmrc() closure returning miette::Result<&'static mut Npmrc>; State::init uses npmrc()?. StoreCommand::run accepts a fallible config closure and calls config()? in Prune/Path. Store invocation passes `npmrc().map(
Tests / Validation
crates/npmrc/src/lib.rs, crates/npmrc/src/workspace_yaml/tests.rs
Tests updated to .expect/unwrap the new Result return; added invalid_workspace_yaml_propagates_error and a test asserting a directory at the manifest path yields LoadWorkspaceYamlError::ReadFile { .. }.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CLI as CliArgs
  participant Npmrc
  participant Workspace
  participant StoreCmd as StoreCommand

  User->>CLI: run command
  CLI->>Npmrc: call npmrc() -> Result<&mut Npmrc>
  Npmrc->>Workspace: WorkspaceSettings::find_and_load(path)
  alt workspace read/parse error
    Workspace-->>Npmrc: Err(LoadWorkspaceYamlError)
    Npmrc-->>CLI: Err
    CLI-->>User: Report error via miette
  else workspace ok or missing
    Workspace-->>Npmrc: Ok(None) or Ok(Some)
    Npmrc-->>CLI: Ok(&mut Npmrc)
    CLI->>StoreCmd: command.run(config_closure)
    StoreCmd->>StoreCmd: call config()? before Prune/Path
    StoreCmd-->>CLI: Result
    CLI-->>User: success/failure
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • zkochan
  • anthonyshew

Poem

🐰 A typo in YAML hid in the hay,
Now Miette hops in and points the way,
Path and cause boxed so the trace is clear,
No more silent fallbacks, the error is near.
Hooray — the rabbit leaves breadcrumbs here.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and concisely describes the main change: propagating pnpm-workspace.yaml load errors instead of silently swallowing them, which aligns with the primary objective.
Linked Issues check ✅ Passed The pull request fully implements all coding requirements from issue #402: making Npmrc::current return Result with structured error type, propagating read/parse errors while treating ENOENT as missing, using miette::Diagnostic for CLI output, and adding regression tests.
Out of Scope Changes check ✅ Passed All changes are directly related to error propagation for pnpm-workspace.yaml loading: workspace_yaml.rs error handling, npmrc load chain, CLI call site updates, and Cargo.toml dependencies are all necessary for the stated objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/add-workspace-yaml-support-nXe35

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

@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown

Micro-Benchmark Results

Linux

group                          main                                   pr
-----                          ----                                   --
tarball/download_dependency    1.00     15.9±0.15ms   273.2 KB/sec    1.00     15.8±0.37ms   273.8 KB/sec

@codecov

codecov Bot commented May 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.18182% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 82.34%. Comparing base (b5962b8) to head (59168e1).

Files with missing lines Patch % Lines
crates/cli/src/cli_args/store.rs 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #404      +/-   ##
==========================================
- Coverage   82.48%   82.34%   -0.15%     
==========================================
  Files          65       65              
  Lines        3672     3705      +33     
==========================================
+ Hits         3029     3051      +22     
- Misses        643      654      +11     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown

Integrated-Benchmark Report (Linux)

Scenario: Frozen Lockfile

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 2.512 ± 0.098 2.387 2.760 1.03 ± 0.05
pacquet@main 2.447 ± 0.062 2.370 2.571 1.00
pnpm 6.291 ± 0.089 6.152 6.426 2.57 ± 0.07
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 2.51167743284,
      "stddev": 0.09843723563692615,
      "median": 2.49543235244,
      "user": 2.40606828,
      "system": 3.40550524,
      "min": 2.38698792694,
      "max": 2.75986619994,
      "times": [
        2.49675562194,
        2.50772369194,
        2.75986619994,
        2.49410908294,
        2.46416604694,
        2.55008624194,
        2.53537325494,
        2.45553110094,
        2.38698792694,
        2.4661751599399997
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.44693807484,
      "stddev": 0.062076324082915565,
      "median": 2.4470843474399997,
      "user": 2.42600218,
      "system": 3.39254184,
      "min": 2.37005200794,
      "max": 2.5705323509399998,
      "times": [
        2.50300709994,
        2.37471567394,
        2.47127673794,
        2.5705323509399998,
        2.46686938694,
        2.42994439794,
        2.46253733394,
        2.43163136094,
        2.38881439794,
        2.37005200794
      ]
    },
    {
      "command": "pnpm",
      "mean": 6.29126821024,
      "stddev": 0.0893190895790371,
      "median": 6.283166507440001,
      "user": 9.165774579999999,
      "system": 4.464745540000001,
      "min": 6.15235749194,
      "max": 6.426093299940001,
      "times": [
        6.339622851940001,
        6.426093299940001,
        6.30177855294,
        6.36796686894,
        6.26208118094,
        6.26455446194,
        6.385540301940001,
        6.15235749194,
        6.22999360894,
        6.18269348294
      ]
    }
  ]
}

Scenario: Frozen Lockfile (Hot Cache)

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 745.0 ± 70.2 695.7 937.3 1.02 ± 0.12
pacquet@main 727.4 ± 55.7 685.5 858.8 1.00
pnpm 2645.5 ± 90.0 2499.4 2802.3 3.64 ± 0.30
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.7449859527400001,
      "stddev": 0.07023571220062716,
      "median": 0.72233340864,
      "user": 0.25649112,
      "system": 1.3529243599999998,
      "min": 0.69574791814,
      "max": 0.93731430514,
      "times": [
        0.93731430514,
        0.74812659514,
        0.7016567261400001,
        0.71306245214,
        0.74737691214,
        0.71374858614,
        0.7481592151400001,
        0.71691640014,
        0.72775041714,
        0.69574791814
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.72737274444,
      "stddev": 0.05566466751436794,
      "median": 0.70493030914,
      "user": 0.25702711999999994,
      "system": 1.3552626600000002,
      "min": 0.68553284614,
      "max": 0.85878296714,
      "times": [
        0.77648746214,
        0.68875855214,
        0.75943014714,
        0.69120777814,
        0.68553284614,
        0.6912330881400001,
        0.85878296714,
        0.69813645614,
        0.71172416214,
        0.71243398514
      ]
    },
    {
      "command": "pnpm",
      "mean": 2.64553453084,
      "stddev": 0.09003315406655205,
      "median": 2.66457920864,
      "user": 3.1735991199999996,
      "system": 2.1635155600000004,
      "min": 2.49936765814,
      "max": 2.80234692014,
      "times": [
        2.58465570814,
        2.49936765814,
        2.7005349351400003,
        2.6257613611400004,
        2.5261441921400003,
        2.67383644514,
        2.68704667714,
        2.7003294391400003,
        2.6553219721400003,
        2.80234692014
      ]
    }
  ]
}

claude and others added 2 commits May 8, 2026 00:35
Apply CONTRIBUTING.md's writing-style guidance to the doc comments and
inline comments added in 7077200: drop em dashes, restructure
sentences so each clause stands alone, and shorten paragraphs that
restated information already obvious from the surrounding code.

https://claude.ai/code/session_015KiPxroFpb2pgBa92YeJM1
@KSXGitHub KSXGitHub marked this pull request as ready for review May 8, 2026 00:48
Copilot AI review requested due to automatic review settings May 8, 2026 00:48

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR aligns pacquet’s pnpm-workspace.yaml handling with pnpm v11 by treating a missing workspace manifest as non-fatal while propagating all other read/parse failures to callers, so configuration typos don’t silently fall back to defaults.

Changes:

  • Introduces a structured LoadWorkspaceYamlError that implements Display + Error + Diagnostic and carries the failing path.
  • Makes Npmrc::current fallible so YAML read/parse errors propagate, while .npmrc parsing remains best-effort.
  • Threads the new error path through CLI configuration loading and adds a regression test for invalid YAML.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
crates/npmrc/src/workspace_yaml.rs Defines the new diagnostic error type and propagates read/parse failures from pnpm-workspace.yaml.
crates/npmrc/src/lib.rs Makes Npmrc::current return Result and adds a regression test for invalid workspace YAML.
crates/npmrc/Cargo.toml Adds derive_more and miette dependencies needed for the new error type.
crates/cli/src/cli_args.rs Wraps config loading in miette and threads the new fallible Npmrc::current.
crates/cli/src/cli_args/store.rs Makes the config provider closure fallible so store subcommands surface YAML errors.
Cargo.lock Updates the lockfile for the added dependencies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread crates/npmrc/src/lib.rs
`LoadWorkspaceYamlError::ParseYaml` is a tuple variant. Using the
tuple-form `ParseYaml(_)` rather than `ParseYaml { .. }` matches
Rust convention for tuple variants. The struct-form pattern compiles
and the test passes either way.

https://claude.ai/code/session_015KiPxroFpb2pgBa92YeJM1
@KSXGitHub KSXGitHub marked this pull request as draft May 8, 2026 00:59

@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 `@crates/npmrc/src/workspace_yaml.rs`:
- Around line 109-119: The read_to_string error for path is being
indiscriminately wrapped into LoadWorkspaceYamlError::ReadFile which converts an
ENOENT (from a TOCTOU race) into a hard error; change the read_to_string
handling so that if the mapped error's kind() == std::io::ErrorKind::NotFound
you return Ok(None) (treat as "no manifest") instead of mapping to ReadFile,
otherwise continue to map the error to ReadFileError and Box it as before;
update the block around fs::read_to_string (and the subsequent mapping to
LoadWorkspaceYamlError::ReadFile) to inspect error.kind() and short-circuit for
NotFound.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 71a97a01-c17d-4ec0-8a89-04abc0c7a6b2

📥 Commits

Reviewing files that changed from the base of the PR and between b881962 and a6bf08e.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • crates/cli/src/cli_args.rs
  • crates/cli/src/cli_args/store.rs
  • crates/npmrc/Cargo.toml
  • crates/npmrc/src/lib.rs
  • crates/npmrc/src/workspace_yaml.rs

Comment thread crates/npmrc/src/workspace_yaml.rs Outdated
KSXGitHub and others added 3 commits May 8, 2026 08:02
`find_workspace_manifest` gates on `is_file()`, but the file can be
removed between that check and `fs::read_to_string`. Pnpm's
`workspace-manifest-reader` (8eb1be4988) treats every `ENOENT` from
the read path as "no manifest". Pacquet was wrapping the racy
`ENOENT` into `LoadWorkspaceYamlError::ReadFile` and erroring out.

Match pnpm: short-circuit `ErrorKind::NotFound` to `Ok(None)` and
keep the existing `pipe-trait` chain for every other read error and
for the parse step.

The early-return for `ENOENT` justifies wrapping the chain in a
`match`, per the "Preserve existing method chains" exception in
`AGENTS.md` for control-flow that the chain cannot express.

https://claude.ai/code/session_015KiPxroFpb2pgBa92YeJM1
@KSXGitHub KSXGitHub marked this pull request as ready for review May 8, 2026 01:23
Copilot AI review requested due to automatic review settings May 8, 2026 01:23
@KSXGitHub KSXGitHub enabled auto-merge (squash) May 8, 2026 01:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Comment thread crates/npmrc/src/workspace_yaml.rs Outdated
The previous shape wrapped every variant in a payload struct
(`ReadFileError`, `ParseYamlError`) whose own `Display` repeated
`{path}: {error}`. Rendered through miette this surfaced the path
twice — once in the variant's main message, once in the `Caused by:`
chain.

Drop the payload structs and store `path` + `source` directly on the
struct variants, mirroring `pacquet_modules_yaml::ReadModulesError`.
Only `serde_saphyr::Error` is large enough to need boxing; `io::Error`
fits the variant inline. The diagnostic now reads cleanly:

    Failed to parse pnpm-workspace.yaml at /…/pnpm-workspace.yaml: <yaml error>
      Caused by: <yaml error>

instead of repeating the path in the `Caused by:` line.

The test pattern moves back to struct-variant form
(`ParseYaml { .. }`) to match.

https://claude.ai/code/session_015KiPxroFpb2pgBa92YeJM1

@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 `@crates/npmrc/src/workspace_yaml.rs`:
- Around line 97-116: find_workspace_manifest currently filters candidates by
candidate.is_file(), which causes directory or non-regular entries named
pnpm-workspace.yaml to be treated as "no manifest" and hides read/parse errors;
update find_workspace_manifest so it returns the candidate path even if it's not
a regular file (remove or relax the is_file() filter) so that
read_to_string(&path) in this module can attempt the read and surface non-ENOENT
errors (which will then be mapped to LoadWorkspaceYamlError::ReadFile or
ParseYaml as intended for WorkspaceSettings parsing).
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9f8d72ef-e569-434e-bcef-0059ba7104e2

📥 Commits

Reviewing files that changed from the base of the PR and between ebcc254 and 62eac2a.

📒 Files selected for processing (2)
  • crates/npmrc/src/lib.rs
  • crates/npmrc/src/workspace_yaml.rs

Comment thread crates/npmrc/src/workspace_yaml.rs Outdated
@KSXGitHub KSXGitHub marked this pull request as draft May 8, 2026 12:36
auto-merge was automatically disabled May 8, 2026 12:36

Pull request was converted to draft

`find_and_load` previously routed candidate paths through
`find_workspace_manifest`, which gated each level on `is_file()` and
walked up otherwise. That filter swallowed cases where the entry
existed but was not a regular file (most notably a directory named
`pnpm-workspace.yaml`), so the stated "only `ENOENT` is silent"
contract — the one pnpm's `readManifestRaw` actually delivers — did
not hold.

Replace the find-then-read split with a single read-and-catch loop
over `start_dir.ancestors()`: at each level read
`pnpm-workspace.yaml`, treat `ErrorKind::NotFound` as "no manifest
here, walk up", and propagate every other error (`EISDIR`, permission
denied, etc.) through the existing `LoadWorkspaceYamlError`
variants. This also closes the TOCTOU window between the old
`is_file()` check and the read.

The private `find_workspace_manifest` helper stays in place because
`workspace_root_or` still uses it; only `find_and_load` switches to
the new shape.

Add a test that creates a directory named `pnpm-workspace.yaml` and
asserts the result is `Err(ReadFile { .. })` rather than the previous
silent `Ok(None)`.

https://claude.ai/code/session_015KiPxroFpb2pgBa92YeJM1
@KSXGitHub KSXGitHub marked this pull request as ready for review May 9, 2026 00:55
Copilot AI review requested due to automatic review settings May 9, 2026 00:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated no new comments.

@zkochan zkochan merged commit 3c6d5a9 into main May 9, 2026
18 of 19 checks passed
@zkochan zkochan deleted the claude/add-workspace-yaml-support-nXe35 branch May 9, 2026 20:11
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Propagate pnpm-workspace.yaml load errors instead of silently swallowing them

4 participants