## What
- preserve existing `workspace:` dependency specifiers when `updateProjectManifest` saves updated direct dependencies and `preserveWorkspaceProtocol` is enabled
- keep catalog specifiers taking precedence over resolver-normalized specs
- add focused coverage for preserved and normalized local spec behavior
- add a changeset for the published `@pnpm/installing.deps-resolver` change
### pacquet parity
Ported the same fix to pacquet's `update` command. Previously `pacquet update --latest` routed every direct dependency through a registry `latest` lookup, so a `workspace:` local-path dependency (e.g. `workspace:../packages/foo/dist`) was rewritten into a registry version — corrupting the manifest (in the regression test it became `0.0.1-security`). Both `--latest` rewrite sites now skip registry resolution for such specs via `is_workspace_local_path_specifier`, a faithful port of pnpm's `isWorkspaceLocalPathSpecifier`. The gate is unconditional in the `--latest` path because `preserveWorkspaceProtocol` is always on there (its only override derives from `linkWorkspacePackages` under `--workspace`, which cannot be combined with `--latest`).
Fixes#3902
---------
Co-authored-by: morning-verlu <258725120+morning-verlu@users.noreply.github.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
## Summary
Fixes lockfile churn where a package's `transitivePeerDependencies` (e.g. `supports-color` via `debug`) could be dropped — and shift between packages — when the package participates in a dependency cycle. Which packages carry a given transitive peer depended on resolution order, so upgrading an unrelated dependency churned the lockfile.
## Root cause
When peer resolution walks into a cycle, the cycle is broken by dropping the repeated package's subtree, so the re-entry occurrence resolves against truncated children and looks peer-free. That occurrence was then recorded as "pure" in `purePkgs` — a verdict keyed by package id, not by context. A later occurrence of the same package, reached through a different parent that *can* see the full subtree, hit the `purePkgs` short-circuit and returned an empty peer set, dropping the transitive peers it should have surfaced. Because the outcome depends on which occurrence is walked first, it was order-dependent.
## Fix
Don't record a cycle re-entry's resolution in `purePkgs` / `peersCache` (a re-entry is detected when the package id already appears in the ancestor chain). Its truncated peer sets aren't authoritative for the package as a whole, so leaving the caches untouched lets later occurrences resolve correctly — or reuse the package's authoritative, non-truncated entry via `findHit`. This is a minimal guard at the cache-population site: it adds no post-resolution pass and does not change `transitivePeerDependencies` for packages that aren't in cycles.
This PR also includes an independent fix: when collecting peer providers from a node's children, match each child's resolved package name in addition to its alias, so `pnpm add my-alias@npm:real-pkg` is visible to peer resolution when `real-pkg` is a peer dependency name.
Both the TypeScript pnpm CLI and the Rust (pacquet) port are updated in parity.
Fixespnpm/pnpm#5108
Related `transitivePeerDependencies`-instability reports: pnpm/pnpm#5552, pnpm/pnpm#5794, and the `transitivePeerDependencies` aspect of pnpm/pnpm#9992 (the out-of-scope version drift in pnpm/pnpm#9992 is a separate problem and is not addressed here).
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
* fix: prevent a pinned locked peer provider from leaking to sibling nodes
When the locked-peer-context pinning introduced in pnpm/pnpm#12083 runs for
a node that has no child dependencies, parentPkgs aliases the parent's
object, so writing the pinned provider into it exposed the provider to every
sibling resolved afterwards. Sibling order follows resolution completion
order, so optional peers of siblings resolved nondeterministically and
"pnpm dedupe --check" failed intermittently in CI.
Copy parentPkgs before pinning so the pin stays scoped to the node and its
own subtree.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* perf: copy parentPkgs only before the first pin write
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
## Summary
- Match pacquet peer-resolution and lockfile output to pnpm for transitive optional peer variants.
- Apply pnpm's Yarn compatibility package extensions in pacquet, with `ignoreCompatibilityDb` support.
- Add regression coverage on both the pnpm resolver test and pacquet resolver/install paths.
Fixespnpm/pnpm#12330.
## Summary
- Make shared package child resolution deterministic by choosing the owner by depth, importer order, and parent path instead of async completion timing.
- Keep non-owner and stale occurrences lazy while reusing the current owner children and missing-peer context.
- Port the same behavior to pacquet and add TypeScript plus Rust regression coverage.
## Verification
- `pnpm --filter @pnpm/installing.deps-resolver test test/resolveDependencyTree.test.ts`
- `cargo test -p pacquet-resolving-deps-resolver`
- Pre-push hook: TypeScript typecheck, pnpm bundle, lint, spellcheck, meta lint, cargo fmt, cargo doc, cargo dylint, taplo format check
Fixespnpm/pnpm#12358
* fix(deps-resolver): make peer-dependent deduplication deterministic
When a peer-suffixed package variant is a subset of two or more mutually
incompatible larger variants, `deduplicateDepPaths` chose which one to
collapse it into based on the order dep paths were inserted into the
per-pkgId set, which reflects importer/resolution order and varies between
platforms. The same workspace could then resolve to different lockfiles on
different machines, making `pnpm dedupe --check` alternate between pass and
fail.
The depth-count sorter `nodeDepsCount(a) - nodeDepsCount(b)` is not a total
order, so equal-count variants keep their (order-dependent) relative
position. Tie-break on the dep path string to give a deterministic winner
regardless of insertion order.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(deps-resolver): assert resolved depPath is defined before order check
The order-invariance assertion compared two undefined values, which would
pass silently if the depPath never resolved. Assert both are defined first.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(pacquet): port dedupePeerDependents collapse
Port pnpm's `dedupePeerDependents` pass (resolvePeers tail block +
`deduplicateAll` / `deduplicateDepPaths` / `nodeDepsCount` /
`isCompatibleAndHasMoreDeps`) into pacquet, carrying the pnpm/pnpm#12179
determinism fix: the collapse target is chosen by a total order over
`(dep count, dep path)` so it no longer depends on importer/resolution
order.
Runs in `resolve_peers_workspace` after `dedupe_injected_deps`, gated on
`config.dedupe_peer_dependents` (default true) threaded through
`WorkspaceResolveOptions`. Duplicate variant groups are reconstructed by
grouping the finished graph on `resolved_package_id` instead of threading
pnpm's `depPathsByPkgId` through the walk. Since pacquet has no unified
post-resolve lockfile pruner, the pass reuses
`dedupe_injected_deps::prune_unreachable` to drop collapsed orphans so
they don't surface in the lockfile.
Both unit tests from pnpm's dedupeDepPaths.test.ts are ported (the
version-mismatch collapse and the importer-order determinism case),
plus end-to-end remap+prune and incompatible-variant coverage.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Zoltan Kochan <z@kochan.io>
## Summary
Preserve compatible peer contexts already recorded in the lockfile during a
writable re-resolution.
A fresh install still resolves peers normally. When a lockfile already records
multiple valid peer contexts, pnpm keeps those contexts instead of collapsing
them into one compatible context and rewriting unrelated lockfile entries.
## Why
[#12075](https://github.com/pnpm/pnpm/pull/12075) fixed optional-peer candidate
selection: pnpm no longer discards a compatible optional-peer version merely
because it came from the lockfile.
This PR addresses a separate source of lockfile churn. A writable install could
still replace one valid peer context with another valid peer context even when
the existing provider remained present and satisfied the peer range.
Public reproduction:
<https://github.com/sharmila-oai/pnpm-optional-peer-lockfile-repro>
The nested reproduction starts with two valid `vitest@3.2.4` contexts:
```text
context-low -> vitest@3.2.4(jsdom@26.1.0)
context-high -> vitest@3.2.4(jsdom@27.4.0)
```
Running a writable lockfile regeneration should retain both contexts:
```sh
./reproduce-nested-context.sh
```
## Behavior
pnpm reuses a locked peer provider only when:
- The provider is still present in the current dependency graph.
- The provider still satisfies the peer range.
Current manifest choices remain authoritative. In particular, pnpm does not
replace:
- A newly added direct peer provider.
- An explicitly updated direct peer provider.
- A changed nested provider.
- A direct provider installed through an alias.
The reuse pass runs only when the dependency tree contains locked peer contexts,
so fresh installs do not pay for a second peer-resolution pass.
## Tradeoff
This change favors lockfile stability over reducing the number of peer
contexts. A writable install may retain multiple compatible peer contexts where
a fresh install would select one.
## Implementation
The resolver performs its normal peer-resolution pass first. When the
dependency tree contains locked peer contexts, it performs a second pass that
may reuse compatible provider paths from the lockfile while respecting current
manifest choices.
pacquet now mirrors this behavior. Its lockfile-reuse path rebuilds child
dependencies from the package manifest and skips peer dependencies recorded in
the snapshot, so the peer pass derives each dependency instance's peer context.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
## Summary
Preserve compatible optional-peer versions already recorded in the lockfile
when pnpm re-resolves a workspace.
## Reproduction
Public reproduction:
<https://github.com/sharmila-oai/pnpm-optional-peer-lockfile-repro>
The workspace contains:
```text
packages/uses-vitest -> vitest@3.2.4
packages/older -> jsdom@26.1.0
```
Vitest declares `jsdom` as an optional peer dependency.
The committed lockfile was generated when another workspace package also
depended on `jsdom@27.4.0`. At that time, pnpm selected the higher compatible
version for Vitest:
```text
vitest@3.2.4(jsdom@27.4.0)
```
That additional direct dependency was then removed from its `package.json`,
without regenerating the lockfile. This simulates a normal manifest edit.
Running:
```sh
pnpm install --lockfile-only --no-frozen-lockfile
```
unnecessarily rewrites Vitest's still-valid optional-peer context:
```diff
- version: 3.2.4(jsdom@27.4.0)
+ version: 3.2.4(jsdom@26.1.0)
```
Both versions satisfy Vitest's optional peer range. The existing `27.4.0`
resolution remains valid and should not be discarded while pnpm updates the
lockfile.
## Cause
Preferred versions loaded from the wanted lockfile are stored as weighted
selectors:
```ts
{ selectorType: 'version', weight: EXISTING_VERSION_SELECTOR_WEIGHT }
```
`getHoistableOptionalPeers()` only recognized the plain string form:
```ts
specType === 'version'
```
As a result, it ignored the compatible locked `27.4.0` candidate. It only saw
`26.1.0`, which was rediscovered from `packages/older/package.json`, and
rewrote the peer context.
## Fix
Normalize the selector before checking its type, matching the handling already
used by `hoistPeers()` for required peers:
```ts
const specType = typeof selector === 'string'
? selector
: selector.selectorType
```
This restores lockfile-seeded versions to the candidate set. It does not add a
new preference rule or force pnpm to keep every locked version. Optional-peer
auto-installation continues to choose the highest version satisfying every
recorded peer range.
The equivalent fix is included in pacquet, pnpm's Rust port.
## Validation
- Added matching TypeScript and Rust regression tests.
- Verified the public reproduction against `pnpm@11.4.0` and the patched CLI.
- Ran the focused TypeScript resolver checks and pacquet test, clippy, format,
and `cargo nextest` checks.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
* fix: preserve integrity of remote tarball dependencies on re-resolution
Re-resolving a remote tarball dependency without re-fetching it (e.g. `pnpm update`)
produced a resolution with no integrity, so the previously recorded integrity was
dropped from the lockfile, breaking later installs with ERR_PNPM_MISSING_TARBALL_INTEGRITY.
Carry the integrity over from the previous lockfile entry when the rebuilt tarball
resolution lost it and the URL is unchanged. This complements #12040, which fixes the
same class of bug in the package-requester layer but does not cover this re-resolution path.
Closes#12067.
* test: cover integrity carryover on tarball re-resolution
* refactor: check integrity before type in the tarball carryover guard
* perf(pacquet): reuse the warm-store tarball on re-resolution instead of re-downloading
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
* fix: inconsistent resolution of a peer shared through a diamond
When a package peer-depends both another package and one of that
package's own peer dependencies (e.g. @typescript-eslint/eslint-plugin
peer-depends both @typescript-eslint/parser and typescript, and
@typescript-eslint/parser peer-depends typescript), pnpm reused a
hoisted instance of the shared peer that was resolved against a
different version, producing an inconsistent resolution.
Close#12079
* test: cover the peer-diamond resolution in pnpm and pacquet
Add @pnpm.e2e/peer-diamond-* fixtures modeling #12079 (plugin
peer-depends both parser and ts; parser peer-depends ts) and
integration tests on both stacks. The pnpm test guards the fix; the
pacquet test confirms pacquet already resolves the diamond consistently
(its merge always prefers the node's own child).
* docs: fix grammar in changeset (peer-depends on both)
* fix: reject path-traversal segments in dependency aliases
A transitive registry package can use a dependency-alias key like
`@x/../../../../../.git/hooks` to make `pnpm install` create a symlink
outside the intended `node_modules` directory, since pnpm passes the
alias straight into `path.join(modulesDir, alias)` without checking
that the joined path stays inside `modulesDir`.
Reject aliases that aren't a single `name` or `@scope/name` shape at
manifest-read time (both the importer's manifest and every transitive
package manifest) and re-check at the symlink layer as defense in
depth. Mirror the fix in pacquet's deps-resolver.
---
Written by an agent (Claude Code, claude-opus-4-7).
* fix(pacquet): use raw strings in alias validator tests for dylint
Perfectionist's `prefer-raw-string` lint rejects the two
backslash-escaped test inputs.
---
Written by an agent (Claude Code, claude-opus-4-7).
* refactor: tighten dependency-alias validator to validate-npm-package-name
An alias is the directory name pnpm creates inside `node_modules`, so
the only valid shapes are a single `name` or `@scope/name` consisting
of URL-friendly characters with no leading `.` / `_`, and not equal to
reserved names such as `node_modules`. That's the same
`validForOldPackages` rule `parseWantedDependency` already applies to
CLI-given names — the manifest-read path should match. Route both
stacks through it so `.bin`, `.pnpm`, `node_modules`, `favicon.ico`,
whitespace, and non-URL-friendly characters are all rejected alongside
the path-traversal shapes the narrow validator caught.
---
Written by an agent (Claude Code, claude-opus-4-7).
* refactor: collapse symlink-layer assertion + path.join into safeJoinModulesDir
The two-step pattern of "assert the alias stays in the dir" then "join
the dir and the alias" left it possible for a caller to use the join
without the assertion. Fold them into a single `safeJoinModulesDir`
that returns the joined path and throws on escape, so the check is
unmissable.
---
Written by an agent (Claude Code, claude-opus-4-7).
* test(symlink-dependency): cover the path-equals-dir guard branch
The earlier tests only exercised the `!startsWith` branch with
`'../sibling'` and `'@x/../../../etc'`. Add `''` and `'.'` as alias
cases — both resolve to the modules dir itself and hit the
`resolvedLink === resolvedDir` branch of `safeJoinModulesDir`.
---
Written by an agent (Claude Code, claude-opus-4-7).
This is consistent with #9358, but implements support for the GitHub Packages npm registry and, more broadly, for vlt-style https://docs.vlt.sh/cli/registries for any registry.
This PR adds a built-in gh: specifier that resolves against the GitHub Packages npm registry, plus a namedRegistries config key so a project can map its own aliases to arbitrary registries. A project can mix public npm packages and private GitHub Packages (or self-hosted) ones without applying a scope-wide registry override to every @scope/* package.
- pnpm add gh:@acme/private writes "@acme/private": "gh:^1.0.0" and resolves from https://npm.pkg.github.com/.
- pnpm add gh:@acme/private@^1.0.0 (with or without an alias) is also supported. Aliased form writes "my-alias": "gh:@acme/private@^1.0.0".
- Auth comes from the existing per-URL .npmrc mechanism, e.g. //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}. No new auth surface.
- @github is intentionally not defaulted to https://npm.pkg.github.com/ - hardcoding that would hijack installs of the public @github/* packages on npmjs.org (e.g. @github/relative-time-element) for users without a scope-wide override. Use gh: to install from GitHub Packages, or configure @github:registry=... yourself if that's really what you want.
- Additional named registries (a self-hosted proxy, GitHub Enterprise Server, etc.) can be configured in pnpm-workspace.yaml:
```yml
namedRegistries:
gh: https://npm.pkg.github.example.com/ # optional: overrides the built-in `gh` alias for GHES
work: https://npm.work.example.com/
```
- Then work:@corp/lib@^2.0.0 resolves against https://npm.work.example.com/, and the built-in gh alias can be redirected to a GHES host.
- Env-var substitution (${VAR}) is supported in namedRegistries values, mirroring the .npmrc convention.
- Reserved alias names (npm, jsr, github, workspace, catalog, file, git, http, https, link, patch, and related git host shorthands) cannot be redefined as user-named registries - the resolver throws ERR_PNPM_RESERVED_NAMED_REGISTRY_ALIAS at startup rather than silently shadowing another protocol. Malformed URLs throw ERR_PNPM_INVALID_NAMED_REGISTRY_URL at startup too, instead of failing as a confusing 404 during resolution.
- On publish, createExportableManifest strips any named-registry prefix (both the built-in gh: and any user-configured alias) so npm and yarn consumers can still resolve the dependency via their own scope-registry configuration - mirroring the user-facing requirement when installing such a dep without the prefix.
The prefix is gh: rather than github: because github: is reserved by npm-package-arg / hosted-git-info as a git host shorthand (e.g. github:owner/repo) - reusing it would be a deviation from the specs used by the npm CLI. gh: is shorter, matches vlt's convention, and cannot collide with any existing npm scheme.
Unlike jsr:, gh: (and any other named-registry alias) does not rewrite the package name - gh:@acme/foo resolves @acme/foo from the GitHub Packages registry as-is. This also means npm/yarn consumers see the original name after the prefix is stripped on publish.
---------
Co-authored-by: Zoltan Kochan <z@kochan.io>
* chore: upgrade @typescript/native-preview to 7.0.0-dev.20260421.2
- Add explicit `types: ["node"]` to the shared tsconfig because tsgo
20260421 no longer auto-acquires `@types/*` from `node_modules`.
- Refactor test files to explicitly import jest globals (`describe`,
`it`, `test`, `expect`, `beforeEach`, etc.) from `@jest/globals`
instead of relying on `@types/jest` ambient declarations. Under the
new tsgo build, `import { jest } from '@jest/globals'` shadows the
ambient `jest` namespace, breaking `@types/jest`'s `declare var
describe: jest.Describe;` globals.
- Add `@jest/globals` to each package's devDependencies where tests
now import from it, and add `@types/node` to packages that need it
but were relying on hoisted resolution.
- Replace `fail()` calls with `throw new Error(...)` since `fail` is
no longer globally available.
* chore: fix remaining tsgo type-strictness errors
- Strip `as <PnpmType>` casts on objects passed to toMatchObject /
toStrictEqual / toEqual; @jest/globals rejects the typed objects
(which include AsymmetricMatchers) vs. the repo-specific type.
- Type `jest.fn<...>()` explicitly where the mock's signature matters
for toHaveBeenCalledWith.
- Replace `beforeEach(() => X)` with `beforeEach(() => { X })` so the
return value is void, as the stricter jest typing requires.
- Use `expect.objectContaining({...})` in one place where the full
expected object triggered stricter type resolution.
- Cast `prompt.mock.calls` arg through `as unknown as Record<...>[]`
for patch.test.ts's nested-array matchers.
- Fix off-by-one `<reference path>` in pnpm/test/getConfig.test.ts
that only surfaced now.
- Move `@jest/globals` from devDependencies to dependencies in the
two `__utils__` packages that import it from `src/`.
- Clean up unused imports from the @jest/globals migration.
* chore: address Copilot review on #11332
- Move misplaced `@jest/globals` imports to the top import block in
checkEngine, run.ts, and workspace/root-finder tests where the
script dropped them below executable code.
- Replace `try { await x(); throw new Error('should have thrown') } catch`
in bins/linker, lockfile/fs, and resolving/local-resolver tests with
`await expect(x()).rejects.toMatchObject({...})`. The old pattern
swallowed an unrelated `throw` if the under-test call silently
succeeded, which would fail on the catch-block assertion with a
misleading message.
* fix: preserve pnpm 10 peer suffix encoding for linked paths
The filenamify upgrade from v4 to v7 changed the peer-suffix "version"
token for linked dependency paths: `../packages/b` became `+packages+b`
instead of `packages+b`, causing lockfile churn for workspaces with
packages linked from outside the workspace root.
Replace the filenamify call with a small inline encoder that reproduces
v4's output for link paths, and drop the now-unused dependency.
Closes#11272.
* chore: avoid cspell-flagged word in peer suffix comment
* test: cover linkPathToPeerVersion and clarify its lossy encoding
Address Copilot review feedback on #11297:
- Correct the comment — any leading run of `.` is dropped, not just
`./` and `../` segments (so `.hidden/pkg` becomes `hidden+pkg`).
- Export the helper and add a focused test that pins the exact token
output for link paths, so future lockfile-breaking regressions get
caught by the test suite.
* refactor: extract linkPathToPeerVersion into its own file
* feat: add `dedupePeers` option to reduce peer dependency duplication
When enabled, this option applies two optimizations to peer dependency resolution:
1. Version-only peer suffixes: Uses name@version instead of full dep paths
(including nested peer suffixes) when building peer identity hashes.
This eliminates deeply nested suffixes like (foo@1.0.0(bar@2.0.0)).
2. Transitive peer pruning: Only directly declared peer dependencies are
included in a package's suffix. Transitive peers from children are not
propagated upward, preventing combinatorial explosion while maintaining
correct node_modules layout.
The option is scoped per-project: each workspace project defines a peer
resolution environment, and all packages within that project's tree share
that environment. Projects with different peer versions correctly produce
different instances.
Closes#11070
* fix: pass dedupePeers to getOutdatedLockfileSetting and use spread for lockfile write
The frozen install path (used by approve-builds) calls getOutdatedLockfileSetting
but was missing the dedupePeers parameter. This caused a false LOCKFILE_CONFIG_MISMATCH
error because the lockfile had the key written (as undefined/null via YAML serialization)
while the check function received undefined for the config value.
Fix: pass dedupePeers to the settings check call, and use spread syntax to only write
the dedupePeers key to lockfile settings when it's truthy (avoiding undefined keys).
* fix: write dedupePeers to lockfile like other settings
Write the value directly instead of spread syntax, and use the same
!= null guard pattern as autoInstallPeers in the settings checker.
* test: add integration test for dedupePeers in peerDependencies.ts
* fix: only write dedupePeers to lockfile when enabled
When dedupePeers is false (default), don't write it to lockfile settings.
This avoids adding a new key to every lockfile.
* test: simplify dedupePeers test assertions
* test: check exact snapshot keys in dedupePeers integration test
* test: add workspace test for dedupePeers with different peer versions
* fix: keep transitive peers in suffix with version-only IDs
Instead of pruning transitive peers entirely (which prevented per-project
differentiation), keep them but use version-only identifiers. This way:
- Packages like abc-grand-parent still get a peer suffix when different
projects provide different peer versions (correct per-project isolation)
- But the suffixes use name@version instead of full dep paths, eliminating
the nested parentheses that cause combinatorial explosion
* refactor: extract peerNodeIdToPeerId helper in resolvePeers
* refactor: simplify peerNodeIdToPeerId return
* fix: pin peer-a dist tag in dedupePeers tests for CI stability
* fix: address review comments
- Register dedupe-peers in config schema, types, and defaults so
.npmrc/pnpm-workspace.yaml settings are parsed correctly
- Use Boolean() comparison in settings checker so enabling dedupePeers
on a pre-existing lockfile triggers re-resolution
- Fix changeset text and test names: transitive peers are still
propagated, just with version-only IDs (no nested dep paths)
* test: add test for hoist peers when given all range version selectors
* fix: invalid specifiers for peers on non-string version selectors
In tests, the bare specifier for the `@pnpm.e2e/peer-a` dependency
became ` || 1.0.0`. This was because the `versions` array could be
empty, causing the `.join(' || ')` operation to execute on a holey
array.
This caused a test in `installing/commands/test/update/update.ts` to
fail.