Target: pnpm/pnpm
Issue: pnpm/pnpm#9191
Pull request: pnpm/pnpm#12841
Field Lab record: pnpm #9191
This field test was about setup state, but not "just environment variables."
The public issue reported ERR_PNPM_NO_GLOBAL_BIN_DIR in GitHub Actions after running corepack enable, then pnpm setup, and then a later global pnpm command.
The reported workflow expected pnpm setup to make global pnpm commands usable in later GitHub Actions steps.
The visible behavior was different.
pnpm setup appended configuration to the shell profile.
But a later workflow step still saw PNPM_HOME as unset.
Then pnpm add --global failed because pnpm could not find the global bin directory.
That made this a boundary problem:
what does setup configure, and which setup state must cross a GitHub Actions step boundary?
Field Lab record
The public Field Lab record for this case is here:
https://github.com/scarab-systems/scarab-field-lab/tree/main/field-tests/pnpm-pnpm-9191
The record includes the public issue, the public pull request, the repair scope, validation summary, and non-claims.
It contains public links, status, validation, and claim boundaries only.
It does not contain SDS source code or non-public product material.
The case record is only the public evidence layer.
SDS result
SDS surfaced a setup-state persistence boundary in pnpm.
The important finding was not that pnpm needed a new global-bin-dir model.
It was not that GitHub Actions users should stop using corepack enable.
It was not that pnpm setup should redesign shell-profile behavior.
The finding was smaller:
pnpm setup could compute and apply PNPM_HOME, but GitHub Actions later workflow steps only inherit that state if setup writes to the GitHub Actions environment and path files.
That distinction matters because GitHub Actions steps are separate execution boundaries.
A command can update .bashrc.
A later step can still fail if the needed environment state was not persisted through GitHub Actions' step-to-step mechanism.
Failure shape
The failure shape was a mismatch between local shell setup and CI step persistence.
The public issue described a reasonable user expectation:
if pnpm setup runs successfully in a GitHub Actions workflow, then later global pnpm commands should be able to find the global bin directory.
The issue showed the opposite.
The setup command appended shell-profile lines for PNPM_HOME and PATH.
But echo $PNPM_HOME in a later step was unset.
Then pnpm add --global @upleveled/preflight failed with ERR_PNPM_NO_GLOBAL_BIN_DIR.
That is not just a missing variable.
It is an execution-boundary mismatch.
The setup command was doing work in one place.
The failing global command needed that work to be visible in another place.
Boundary
The boundary in this field test was:
GitHub Actions setup state should be persisted through GitHub Actions environment files when those files are present.
Those files are different from a shell profile.
They are the CI runner's public mechanism for carrying environment variables and path entries into later steps.
For pnpm setup, that distinction is important because the command is being used as a public setup contract.
Users do not normally think in terms of shell-profile writes versus runner environment-file writes.
They think:
I ran setup.
The next step should work.
If setup only updates a shell profile, the local-shell contract may be satisfied while the GitHub Actions contract is still broken.
What changed
The repair is a bounded setup persistence change.
The pull request is here:
https://github.com/pnpm/pnpm/pull/12841
The changed public files are:
text
.changeset/pnpm-setup-github-actions.md
pacquet/crates/cli/src/cli_args/setup.rs
pacquet/crates/cli/src/cli_args/setup/tests.rs
pnpm11/engine/pm/commands/src/setup/setup.ts
pnpm11/engine/pm/commands/test/setup/setup.test.ts
The repair does three things.
When GITHUB_ENV is present, setup writes PNPM_HOME to the GitHub Actions environment file.
When GITHUB_PATH is present, setup writes the setup bin directory to the GitHub Actions path file.
The behavior is implemented in both the TypeScript CLI path and the Rust pacquet/ path.
That is the whole patch.
No redesign of global-bin-dir detection.
No change to non-GitHub shell-profile setup behavior.
No replacement for pnpm/action-setup.
No claim that every global setup case is solved.
Just GitHub Actions setup-state persistence where the runner exposes the files for that purpose.
Why this was not a larger code repair
One of the risks with code-agent work is that every CI failure starts to look like a large behavior redesign.
This field test is a useful counterexample.
The public issue was real.
The CI failure was real.
The user experience was real.
But the repair boundary was not “invent a new global install model.”
The repair boundary was:
persist the setup state across the runner boundary that the user is actually crossing.
If a code agent treated the error string alone as the truth, it could chase global-bin-dir detection.
It could add new fallback logic.
It could change behavior outside GitHub Actions.
It could alter local setup semantics.
That would have been a much larger claim.
The narrow claim here is simpler:
when pnpm setup is already computing the needed setup state, and GitHub Actions provides env/path files, write the setup state there too.
That kind of restraint is part of the theory being tested with Scarab:
find the correct boundary before editing.
Sometimes the correct boundary is docs.
Sometimes it is tests.
Sometimes it is runtime behavior.
In this case, it was CI setup-state persistence.
Why the diagnostic result mattered
The larger point of this field test is not that two files are written.
The larger point is that a code agent should not decide the repair shape from the error message alone.
It should not start from:
“where can I suppress this error?”
It should start from:
“which public contract failed, and at what boundary?”
For this case, the public contract was setup.
The failing boundary was the GitHub Actions step boundary.
The missing bridge was GitHub Actions’ environment-file mechanism.
Once that was clear, the patch became small.
That is the pattern Scarab is trying to make repeatable:
find the truth,
find the boundary,
patch the boundary.
Validation
The repair was validated locally with targeted TypeScript and Rust checks before submission.
Validation recorded for this field test:
pnpm --filter @pnpm/engine.pm.commands test -- test/setup/setup.test.ts
Result:
7 passed, 0 failed
Rust validation:
cargo test -p pacquet-cli --lib cli_args::setup::tests
Result:
5 passed, 0 failed
Formatting and repository hygiene checks were also recorded:
rustfmt --edition 2024 --config-path rustfmt.toml --check ...
git diff --check
Result:
passed
At the time this draft was prepared on July 7, 2026, the public pull request was open and ready for review.
The public status is:
PR open
review required
one commit submitted
upstream checks still in progress or queued
some public checks already passing
This field report does not claim upstream acceptance.
It claims a public diagnostic record, a narrow setup-state repair, and a submitted PR.
Maintainers decide whether the persistence behavior belongs upstream.
Field test result
Result:
diagnostic proof and setup-state repair submitted.
The field test produced:
a public issue-to-boundary record
a narrow GitHub Actions persistence patch
focused TypeScript coverage
focused Rust coverage
repository hygiene checks
a changeset
a public upstream PR
a public status record in the Scarab Field Lab
The patch is deliberately small.
That is the point.
When the failure is a setup-state boundary, the best repair may be to carry the existing setup result across the boundary users are actually crossing.
Public claim
This field test supports a narrow public claim:
SDS identified a GitHub Actions setup-state persistence boundary in pnpm where pnpm setup needed to persist PNPM_HOME and the setup bin path through GitHub Actions environment files, and a human-submitted repair was prepared for that boundary.
It does not claim:
that pnpm accepted the patch
that all upstream CI completed
that pnpm’s global-bin-dir detection was redesigned
that non-GitHub setup behavior changed
that pnpm/action-setup was replaced
that Scarab repairs projects by itself
that SDS source or product details are public
that maintainers endorsed Scarab or the Field Lab
The Field Lab exists to keep those claims separate.
Disclosure:
This field report was prepared with AI-assisted editing from public field-test notes, public issue and PR records, and the public Field Lab record.
The diagnostic claim, repair boundary, and final wording were human reviewed.
Scarab Diagnostic Suite is proprietary.
The Field Lab publishes public case records, issue links, validation summaries, and claim boundaries only.
Top comments (0)