Files
pnpm/cspell.json
T
Zoltan KochanandClaude Fable 5.1 2af480acf4 fix: signal the script's process group when pnpm has no terminal (#15119)
* fix: signal the script's process group when pnpm has no terminal

A signal aimed at pnpm alone is the only kind that reaches it without
a controlling terminal, and both runners relayed it to the shell that
runs the script. A shell that stays the script's parent, as dash does
for a command like `node server.js`, dies from SIGTERM at once and
holds a SIGINT until its child exits, so the script never heard that
it should stop, and a container runtime then killed it outright.

Without a controlling terminal each script now gets a process group of
its own, pnpm relays its signals to that group, and after a relayed
signal it waits for the group to empty before ending, so the script's
shutdown completes even though its shell is gone. With a terminal
nothing changes: the child stays in the foreground group, where the
terminal's own signals reach it and it may read the terminal.

pnpm v12 decides this in spawn_child and waits in SpawnedChild::wait,
which the output pump now goes through too; members that became pnpm's
children, as they do when pnpm is PID 1, are reaped along the way.
pnpm v11 spawns the shell detached and polls ps for live members of
the group.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(cli): decide the exec process-group test by the terminal it has

The process-group check ran under whatever terminal the harness had,
so it held on a developer's terminal and failed on CI, which has none.
It now runs pacquet on a pseudo-terminal for the foreground case and
in a session without one for the own-group case. The terminal helpers
of the interrupt tests move to the suite's utilities for that.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(cli): run the remaining foreground-group checks on a terminal

The filtered-run and serialized-task process-group checks made the
same assumption as the exec one: that the harness holds a terminal.
They now run pacquet on a pseudo-terminal too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(executor): assert the foreground group only at a terminal

The unit test assumed the test process holds a terminal. It now
checks the contract for whichever case it runs in: a shared group at a
terminal, the child's own group without one.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(npm-lifecycle): probe the process group without ps, and raise the death signal after the wait

The group wait asked ps and took any failure for an empty group, so a
container without a usable ps cut the shutdown short. The group is
now probed the way the kernel counts it, with a signal of 0, and on
Linux a member that exited but is not reaped yet, as happens when pnpm
is a container's PID 1, is told apart through /proc.

A shell killed by the relayed signal made the runner raise that
signal on pnpm at once, before the group wait, and pnpm's own listener
was already consumed, so pnpm died while the script was still shutting
down. The raise now comes after the wait. An onSpawn observer that
throws signals the whole group too.

The tests observe the runner's exit rather than its output: a script
that outlives the runner keeps the shared pipe open, which hid this.
A failed setsid in the test helper fails the spawn.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(npm-lifecycle): give up the group wait when the group cannot be observed

A probe error other than "no such process", such as a permission
error from a member that changed user, made the group count as live on
every poll, so the wait never ended. Such an error now rejects the
wait and the runner goes on. A /proc entry that vanished between the
listing and the read has exited; one whose state cannot be read counts
as live.

The runner's doc says that a script's death signal, raised after the
wait, ends pnpm before the callback unless something handles it. The
tests kill the detached process group whatever way they end.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(npm-lifecycle): leave unreadable /proc entries out of the group

An entry whose state cannot be read is not a member pnpm can observe:
it exited since the listing, or it belongs to another user under a
restricted process table. Counting it as live kept the wait going on
every poll.

The test helper kills the detached group as soon as the runner has
exited, before waiting for the output pipe a survivor would hold open,
and its timeout kills the group rather than the runner alone. The
cleanup helper is shared through `@pnpm/prepare` and accepts only a
group that is gone already. Two narrating comments in the Rust tests
are gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(npm-lifecycle): cover the process-table scan with a table of its own

The scan takes the process table's path, so a test can supply one with
a zombie, a live member and an entry whose state cannot be read, and
show that the wait ends for the first, goes on for the second, and does
not mistake the third for a member.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(npm-lifecycle): helpers after their use, and a bump for the shared cleanup helper

The process-table test's helpers follow the tests that call them, and
the one that writes the table states what it creates and returns. The
changeset lists `@pnpm/prepare`, which gained the shared cleanup helper.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(npm-lifecycle): keep only the non-obvious part of the table helper's doc

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(npm-lifecycle): clear the losing deadline timer

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-19 14:48:10 +00:00

518 lines
7.9 KiB
JSON

{
"ignorePaths": [
"**/nodeReleaseKeys.ts",
"**/nodeReleaseKeys.d.ts",
"**/node_release_keys.rs",
"bench-work-env/**"
],
"words": [
"abrt",
"adduser",
"adipiscing",
"agentkeepalive",
"agentkeepalive's",
"aliasless",
"amet",
"andreineculau",
"APFS",
"appdata",
"applyq",
"archy",
"argumentless",
"armv",
"AUTHTOKEN",
"autocheckpoint",
"autocompleting",
"autofix",
"autofixed",
"autoinstalled",
"autozoom",
"babek",
"Backblaze",
"badheaders",
"baires",
"basedirs",
"bdist",
"behaviour",
"blabla",
"Bluesky",
"brasileiro",
"bryntum",
"btrfs",
"buildx",
"cafile",
"cafs",
"camelcase",
"canonicalizer",
"cantopen",
"canva",
"CDPATH",
"cerbos",
"certfile",
"chcp",
"chmods",
"clonedeep",
"clonefile",
"cmds",
"Codeberg",
"codegen",
"codeload",
"codenames",
"codesign",
"colorterm",
"compdef",
"COMPREPLY",
"comver",
"cooldown",
"copyfiles",
"corejs",
"corepack",
"corge",
"cowsay",
"Creds",
"cryptiles",
"cves",
"CWORD",
"cwsay",
"cyclonedx",
"cygdrive",
"deburr",
"dedup",
"denoland",
"denolib",
"dependson",
"deptype",
"desugared",
"desugars",
"devextreme",
"devowl",
"dgimuvys",
"didyoumean",
"dirtyforms",
"diskusage",
"dislink",
"dpkg",
"drivelist",
"duplexify",
"eacces",
"eagain",
"ebadplatform",
"ebusy",
"eexist",
"ehrkoext",
"eintegrity",
"eisdir",
"elifecycle",
"elit",
"embedder",
"embedders",
"emfile",
"enametoolong",
"endregion",
"eneedauth",
"enoent",
"enotdir",
"enotempty",
"enten",
"eotp",
"eperm",
"epipe",
"erofs",
"errcode",
"esac",
"etamponi",
"exdev",
"execa",
"exploitability",
"fakehash",
"fellback",
"fetchings",
"filenamify",
"filesystem",
"filesystems",
"fnumber",
"foobarqar",
"foofoo",
"footgun",
"forgejo",
"fsevents",
"fslib",
"gabor",
"garply",
"gcttmf",
"getaddrinfo",
"getattr",
"ghes",
"ghsa",
"ghsas",
"gitdir",
"gitea",
"globalconfig",
"globstar",
"gnueabihf",
"gpgsign",
"grault",
"gruntfile",
"gwhitney",
"haptics",
"hardlink",
"hardlinked",
"hardlinking",
"hardlinks",
"hashbang",
"hashless",
"highmaps",
"hikljmi",
"hoistable",
"homepath",
"hosters",
"hostnames",
"htpasswd",
"hyperdrive",
"idempotency",
"imagetools",
"imurmurhash",
"inodes",
"invalidformat",
"ionicons",
"isexe",
"istvan",
"italiano",
"jega",
"jhcg",
"jnbpamcxayl",
"journaled",
"junyi",
"kebabcase",
"kevva",
"keyfile",
"keyid",
"keytype",
"killcb",
"kochan",
"koorchik",
"LASTEXITCODE",
"ldid",
"ldni",
"ledgered",
"leniolabs",
"libc",
"libnpmpublish",
"libnpx",
"libpnpm",
"libsql",
"libzip",
"licence",
"licences",
"lifecycles",
"linearization",
"linuxstatic",
"localappdata",
"lockfiles",
"loglevel",
"logstream",
"longlink",
"longpaths",
"loong",
"lowlink",
"luca",
"manylinux",
"martensson",
"maxsockets",
"maxtimeout",
"mdast",
"metacharacter",
"metafile",
"millis",
"minioadmin",
"mintimeout",
"mkfifo",
"mmap",
"monorepolint",
"montudor",
"moonrepo",
"mountpoint",
"msgpack",
"msgpackr",
"msvc",
"msys",
"MSYSTEM",
"mtimes",
"musleabihf",
"mycomp",
"mycompany",
"myorg",
"mypackage",
"mytoken",
"napi",
"ndjson",
"nerfed",
"newteam",
"newversion",
"nistp",
"NOASSERTION",
"nodepath",
"nodetouch",
"noent",
"nonexec",
"noninjected",
"nonvulnerable",
"noopener",
"nopadding",
"noprofile",
"noproxy",
"norc",
"noreferrer",
"nosystem",
"nothrow",
"npath",
"npmcli",
"npmignore",
"npmjs",
"npmmirror",
"npmx",
"ntfs",
"nushell",
"ofjergrg",
"oldteam",
"onclickoutside",
"oomol",
"openharmony",
"openpgp",
"openpyxl",
"ossl",
"outfile",
"overrider",
"packlist",
"packr",
"packument",
"packuments",
"pacquet",
"paralleljs",
"parallelly",
"parseable",
"partialmatch",
"pathext",
"pathsep",
"pegjs",
"pgid",
"pids",
"pidtree",
"pify",
"pkgid",
"pkgname",
"pkgs",
"plotly",
"plugh",
"pnpmfile",
"pnpmfiles",
"pnpmjs",
"pnpmrc",
"pnpmtest",
"pnpr",
"polyfilling",
"português",
"posix",
"postbuild",
"postfoo",
"postpack",
"postprepare",
"postpublish",
"postrestart",
"postshrinkwrap",
"poststart",
"poststop",
"posttest",
"postuninstall",
"postversion",
"preact",
"prebuild",
"prefoo",
"prefs",
"preinstall",
"premajor",
"preminor",
"prepatch",
"prepublish",
"prereleases",
"prerestart",
"preshrinkwrap",
"prestart",
"prestop",
"preuninstall",
"preversion",
"prioritizer",
"promisified",
"propfind",
"proxied",
"pwsh",
"pylock",
"pypi",
"pyproject",
"pythonhosted",
"qrcode",
"quux",
"rcompare",
"redownload",
"refclone",
"refetched",
"refetches",
"reflattened",
"reflink",
"reflinked",
"reflinks",
"rehoist",
"reimagining",
"reka",
"Rekor",
"relinks",
"renderable",
"reparses",
"replit",
"reqheaders",
"rescans",
"rescopable",
"rescope",
"rescoped",
"rescopes",
"rescoping",
"resolv",
"rimrafed",
"riscv",
"rmgr",
"rpmdevtools",
"rpmlint",
"rsort",
"rstacruz",
"rushstack",
"rustc",
"rustup",
"safecrlf",
"sccache",
"scopeless",
"scriptless",
"sdiff",
"searchexclude",
"searchlimit",
"searchopts",
"searchstaleness",
"secp",
"sels",
"semistrict",
"serde",
"serp",
"serverjs",
"SETLOCAL",
"shasums",
"sheetjs",
"Sherov",
"shlex",
"sigstore",
"sindresorhus",
"sirv",
"skopeo",
"SLSA",
"soporan",
"sopts",
"spdxdocs",
"SPDXID",
"sqld",
"srcset",
"ssri",
"stackblitz",
"stacktracey",
"stdtype",
"streamsearch",
"stringifying",
"strongconnect",
"subcmd",
"subdep",
"subdependencies",
"subdependency",
"subdeps",
"subdir",
"subdirs",
"subkey",
"subkeys",
"subpkg",
"subresource",
"supercede",
"Swatinem",
"syml",
"syncer",
"syscall",
"syscalls",
"systemroot",
"szia",
"tabtab",
"taffydb",
"taiki",
"tarballtemplate",
"Tarjan",
"taskkill",
"teambit",
"tempy",
"testcase",
"thesys",
"tlog",
"TLSV",
"toctou",
"todomvc",
"toplevel",
"TOTP",
"tsgo",
"tsparticles",
"turso",
"typecheck",
"typeflag",
"TYPELESS",
"unallowed",
"unconfigured",
"undeprecate",
"underperformance",
"undispatched",
"undollar",
"unextractable",
"uninstallation",
"unmigrated",
"unnest",
"unparseable",
"unreleasable",
"unreviewed",
"unskip",
"unstar",
"untracks",
"usecase",
"userconfig",
"userprofile",
"ustar",
"uuidv",
"valign",
"venv",
"versionless",
"vuln",
"waitpid",
"wbem",
"webauth",
"webcontainer",
"webdav",
"WIFEXITED",
"windir",
"windowspowershell",
"winst",
"workleap",
"worktree",
"worktrees",
"wrappy",
"wslpath",
"WTERMSIG",
"xattr",
"xattrs",
"xmarw",
"yazl",
"zkochan",
"zoli",
"zoltan"
]
}