Skip to content

fix: shorten CAFS temp directory names#12327

Merged
zkochan merged 1 commit into
pnpm:mainfrom
scarab-systems:scarab-systems/pnpm-12222-tmpdir-path
Jun 16, 2026
Merged

fix: shorten CAFS temp directory names#12327
zkochan merged 1 commit into
pnpm:mainfrom
scarab-systems:scarab-systems/pnpm-12222-tmpdir-path

Conversation

@scarab-systems

@scarab-systems scarab-systems commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • use fs.mkdtemp() with a short _tmp_ prefix for CAFS temporary package directories
  • remove path-temp from @pnpm/store.create-cafs-store
  • add git-fetcher regression coverage for short CAFS temp directories during git package preparation
  • add a patch changeset for @pnpm/store.create-cafs-store and pnpm

Why

This leaves more Unix socket path budget for lifecycle tools that create IPC sockets under TMPDIR during git-hosted dependency preparation.

Closes #12222.

Pacquet

No pacquet change is included because the Rust git fetcher already uses tempfile::tempdir() instead of the TypeScript CAFS path-temp suffix changed here.

Tests

  • pnpm --filter pnpm compile
  • pnpm --filter @pnpm/store.create-cafs-store test
  • pnpm --filter @pnpm/exec.prepare-package test
  • pnpm --filter @pnpm/fetching.git-fetcher test
  • pre-push hook completed: compile-only, spellcheck, meta-updater, TypeScript lint, cargo fmt, cargo doc; optional cargo-dylint and taplo were not installed and were skipped by the hook

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Reduced temporary package directory path lengths to provide sufficient space for lifecycle script IPC socket generation under TMPDIR.
  • Tests

    • Added Jest coverage to validate the temporary CAFS directory location, naming pattern, and maximum directory name length.
  • Chores

    • Updated package dependency configuration and version metadata to support the improved temporary directory behavior.

@scarab-systems scarab-systems requested a review from zkochan as a code owner June 11, 2026 14:32
@coderabbitai

coderabbitai Bot commented Jun 11, 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: 2c9ca5c3-c85f-4698-9197-b109da82b352

📥 Commits

Reviewing files that changed from the base of the PR and between bd9a428 and 0e25b7d.

📒 Files selected for processing (2)
  • .changeset/short-pipes-dance.md
  • fetching/git-fetcher/test/index.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/short-pipes-dance.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • fetching/git-fetcher/test/index.ts

📝 Walkthrough

Walkthrough

This PR addresses Unix domain socket path length violations when pnpm runs as root by shortening CAFS temporary directory names. The path-temp dependency is replaced with direct fs.mkdtemp under storeDir/tmp, and a test validates the shortened naming pattern.

Changes

Shortened CAFS temporary directories

Layer / File(s) Summary
Core temporary directory implementation and dependencies
store/create-cafs-store/package.json, store/create-cafs-store/src/index.ts
Removed path-temp dependency, removed pathTemp import, and replaced pathTemp(baseTempDir) with direct fs.mkdir and fs.mkdtemp calls under storeDir/tmp to generate shorter directory paths.
Test verification of short directory paths
fetching/git-fetcher/test/index.ts
Added test case verifying CAFS temp directory resides under <storeDir>/tmp, follows _tmp_<6-char-alnum> naming pattern, and basename length does not exceed 11 characters.
Changeset documentation
.changeset/short-pipes-dance.md
Documented patch version bumps for @pnpm/store.create-cafs-store and pnpm describing the shorter CAFS temporary directory adjustment to preserve space for IPC socket paths in lifecycle scripts.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

A rabbit hops through shorter paths today,
Where sockets dance and pipes don't stray too far,
No more 108-byte dismay! 🐰✨
Root's lifecycle scripts now skip and play,
With mkdtemp guiding the way.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: shortening CAFS temporary directory names to address the 108-byte Unix socket path limit issue.
Linked Issues check ✅ Passed All code changes directly address issue #12222: replacing path-temp with fs.mkdtemp using short prefixes, removing path-temp dependency, and adding regression test coverage.
Out of Scope Changes check ✅ Passed All changes are directly related to shortening CAFS temp directory names to fix the Unix socket path length issue; no unrelated modifications are present.

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

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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


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 and usage tips.

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

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

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (1)

Context used

Grey Divider


Remediation recommended

1. CAFS tmpdir test too narrow 📎 Requirement gap ☼ Reliability
Description
The added regression test only asserts the CAFS temp directory basename pattern/length and does not
validate the root-context pnpm install reproduction with a git-hosted dependency prepare script
that creates IPC sockets under TMPDIR. This leaves the rule’s required end-to-end root
TMPDIR/socket-path behavior unverified.
Code

fetching/git-fetcher/test/index.ts[R50-57]

+test('uses short CAFS temp directories for git package preparation', async () => {
+  const storeDir = temporaryDirectory()
+  const tmpDir = await createCafsStore(storeDir).tempDir()
+
+  expect(path.dirname(tmpDir)).toBe(path.join(storeDir, 'tmp'))
+  expect(path.basename(tmpDir)).toMatch(/^_tmp_[a-zA-Z0-9]{6}$/)
+  expect(path.basename(tmpDir).length).toBeLessThanOrEqual(11)
+})
Evidence
PR Compliance ID 2 requires validation against a root-context reproduction using a git-hosted
dependency prepare script that creates IPC files under TMPDIR. The newly added test only checks
the CAFS temp directory prefix and basename length, which does not demonstrate root TMPDIR behavior
or IPC socket/pipe path-length safety in the reproduction workflow.

Root-context pnpm TMPDIR behavior must be validated against reproduction scenario using git-hosted dependency prepare script
fetching/git-fetcher/test/index.ts[50-57]

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

## Issue description
PR Compliance requires validating the root-context reproduction scenario (git-hosted dependency with a `prepare` script that creates IPC sockets/pipes under `TMPDIR`). The current test only checks the CAFS temp directory name/length and does not exercise root-context lifecycle execution or verify that resulting IPC paths stay within the 108-byte `sockaddr_un` limit.

## Issue Context
The compliance objective is specifically about `pnpm install` as root (`unsafePerm=false`) and TMPDIR-based IPC artifacts created during git dependency preparation.

## Fix Focus Areas
- fetching/git-fetcher/test/index.ts[50-57]

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


2. Temp dir mode too strict 🐞 Bug ☼ Reliability
Description
createCafsStore().tempDir() now uses fs.mkdtemp(), which creates the temp directory with
restrictive permissions (0700 by default on Unix), unlike the prior fs.mkdir() flow whose
permissions followed the process umask. Because fetchers run git clone and preparePackage()
inside this temp directory, any lifecycle execution that drops privileges (the unsafePerm=false
path) will fail to write into the temp working directory with EACCES.
Code

store/create-cafs-store/src/index.ts[R142-143]

+      await fs.mkdir(baseTempDir, { recursive: true })
+      return fs.mkdtemp(path.join(baseTempDir, '_tmp_'))
Evidence
The CAFS temp dir is now created via fs.mkdtemp(), and the repo documents that mkdtemp creates
0700 temp dirs by default; this temp dir is used as the working directory for git clone/checkout and
is passed to preparePackage() where lifecycle execution receives unsafePerm (which is false by
default when running as uid 0). This combination makes restrictive permissions on the temp dir a
concrete failure mode for any privilege-dropping lifecycle execution path.

store/create-cafs-store/src/index.ts[122-145]
releasing/commands/src/pack-app/packApp.ts[224-229]
fetching/git-fetcher/src/index.ts[29-55]
exec/prepare-package/src/index.ts[52-59]
exec/lifecycle/src/runLifecycleHook.ts[115-144]
installing/deps-installer/src/install/extendInstallOptions.ts[357-361]

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

### Issue description
`createCafsStore().tempDir()` now returns a directory created by `fs.mkdtemp()`. In this repo, `mkdtemp()` is documented as creating temp directories with restrictive permissions (0700 by default), which differs from the previous `fs.mkdir()` behavior (umask-derived). This can make CAFS temp working directories unwritable when lifecycle tooling executes under different credentials (e.g. `unsafePerm=false` root flows).

### Issue Context
The returned `tempLocation` is used as the working directory for `git` operations and for `preparePackage()` (which runs package managers and lifecycle hooks), so it must be writable by the effective user that runs those steps.

### Fix Focus Areas
- store/create-cafs-store/src/index.ts[131-145]

### Suggested fix
After `mkdtemp`, explicitly align permissions with `mkdir` defaults, e.g.:
- `const tmpDir = await fs.mkdtemp(path.join(baseTempDir, '_tmp_'))`
- `await fs.chmod(tmpDir, 0o777 & ~process.umask())`
- `return tmpDir`

Alternatively, avoid `mkdtemp` and generate a short random suffix yourself, then `fs.mkdir(tmpDir)` so the mode naturally follows umask.

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


Grey Divider

Previous review results

Review updated until commit 0e25b7d

Results up to commit a980b73


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Great, no issues found!

Qodo reviewed your code and found no material issues that require review
Results up to commit bd9a428


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Great, no issues found!

Qodo reviewed your code and found no material issues that require review
Qodo Logo

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

Copy link
Copy Markdown

PR Summary by Qodo

Fix: shorten CAFS temp directory names
🐞 Bug fix 🧪 Tests 🕐 10-20 Minutes

Grey Divider

Walkthroughs

Description
• Generate CAFS temp dirs via fs.mkdtemp() with short _tmp_ prefix.
• Remove path-temp dependency from @pnpm/store.create-cafs-store.
• Add regression test covering short temp dirs during git package preparation.
Diagram
graph TD
  A["Git fetcher tests"] --> B["createCafsStore.tempDir()"] --> C[("storeDir/tmp")]
  B --> D["node:fs mkdtemp"]
  E["Git fetcher"] --> B
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Custom short random suffix + fs.mkdir
  • ➕ Even shorter names than mkdtemp (control exact length/charset)
  • ➕ Avoids mkdtemp prefix requirements/behavior differences across platforms
  • ➖ More custom code to generate secure uniqueness (race/entropy concerns)
  • ➖ Must carefully handle collisions and cleanup semantics
2. Make temp prefix/length configurable
  • ➕ Allows tuning for edge environments with extreme path-length constraints
  • ➕ Can be adjusted without code changes if exposed via env/config
  • ➖ Adds configuration surface area and support burden
  • ➖ Harder to ensure consistent behavior across pnpm components

Recommendation: Current approach (use fs.mkdtemp() with a short _tmp_ prefix under the existing storeDir/tmp) is the best tradeoff: it reduces path length without changing the temp directory location/semantics, removes an extra dependency (path-temp), and keeps uniqueness handling delegated to Node.

Grey Divider

File Changes

Bug fix (1)
index.ts Create short temp directories using fs.mkdtemp with '_tmp_' prefix +2/-4

Create short temp directories using fs.mkdtemp with 'tmp' prefix

• Replaces 'path-temp'-based temp path generation with 'fs.mkdtemp(path.join(baseTempDir, '_tmp_'))' after ensuring 'baseTempDir' exists. Produces shorter, predictable temp directory names under the store's 'tmp' directory.

store/create-cafs-store/src/index.ts


Tests (1)
index.ts Add regression test for short CAFS temp directory names +9/-0

Add regression test for short CAFS temp directory names

• Adds a Jest test that calls 'createCafsStore(storeDir).tempDir()' and asserts the directory is created under 'storeDir/tmp'. Verifies the basename matches '_tmp_' plus 6 alphanumerics and stays within an 11-character limit.

fetching/git-fetcher/test/index.ts


Documentation (1)
short-pipes-dance.md Add patch changeset for shorter CAFS temp dirs +6/-0

Add patch changeset for shorter CAFS temp dirs

• Introduces a changeset to release patch versions of '@pnpm/store.create-cafs-store' and 'pnpm'. Documents the motivation: leaving more path budget under TMPDIR for IPC socket creation.

.changeset/short-pipes-dance.md


Other (2)
pnpm-lock.yaml Remove 'path-temp' from lockfile importer deps +0/-3

Remove 'path-temp' from lockfile importer deps

• Updates the lockfile to reflect removal of the 'path-temp' dependency from '@pnpm/store.create-cafs-store'. Ensures repository dependency state matches package manifests.

pnpm-lock.yaml


package.json Drop 'path-temp' dependency from create-cafs-store +0/-1

Drop 'path-temp' dependency from create-cafs-store

• Removes 'path-temp' from '@pnpm/store.create-cafs-store' dependencies since temp dirs are now created via Node's built-in 'fs.mkdtemp()'. Reduces dependency surface area.

store/create-cafs-store/package.json


Grey Divider

Qodo Logo

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Integrated-Benchmark Report (Linux)

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 4.082 ± 0.165 3.918 4.474 1.91 ± 0.13
pacquet@main 4.226 ± 0.180 3.946 4.507 1.97 ± 0.14
pnpr@HEAD 2.143 ± 0.111 2.051 2.362 1.00 ± 0.08
pnpr@main 2.140 ± 0.118 1.958 2.316 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 4.08180669906,
      "stddev": 0.1653513131514235,
      "median": 4.02862475026,
      "user": 3.9326239599999995,
      "system": 3.48139066,
      "min": 3.9176072572600003,
      "max": 4.47408084826,
      "times": [
        4.115356398259999,
        3.9968011212600003,
        4.23826134526,
        4.47408084826,
        4.03000832526,
        4.06759294726,
        3.9176072572600003,
        4.0272411752599995,
        3.9310631542600003,
        4.02005441826
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 4.22580853936,
      "stddev": 0.17997262195543334,
      "median": 4.23840520776,
      "user": 4.010822760000001,
      "system": 3.4888501599999997,
      "min": 3.94620010726,
      "max": 4.50707519026,
      "times": [
        4.3287879692599995,
        4.50707519026,
        4.15093156026,
        4.35710668926,
        4.17718996926,
        4.04296699926,
        3.94620010726,
        4.39661433326,
        4.0515921292599995,
        4.29962044626
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 2.1428855379600007,
      "stddev": 0.11083272090378257,
      "median": 2.1010237827600005,
      "user": 2.7106796599999994,
      "system": 3.0304449599999996,
      "min": 2.05098536926,
      "max": 2.36173212026,
      "times": [
        2.09169626726,
        2.1457342122600003,
        2.05418895326,
        2.1365017442600003,
        2.3254434662600003,
        2.05098536926,
        2.0953708632600003,
        2.06052568126,
        2.36173212026,
        2.10667670226
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 2.1400536806600003,
      "stddev": 0.11786480976087328,
      "median": 2.1325446682600004,
      "user": 2.7069799599999995,
      "system": 3.02190636,
      "min": 1.9584658912600001,
      "max": 2.31573770526,
      "times": [
        2.15863165826,
        2.3036867952600004,
        2.03675233026,
        2.31573770526,
        2.03362918526,
        2.1644965162600003,
        2.2300396732600003,
        1.9584658912600001,
        2.1064576782600004,
        2.0926393732600004
      ]
    }
  ]
}

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

Command Mean [ms] Min [ms] Max [ms] Relative
pacquet@HEAD 631.9 ± 11.0 615.5 648.2 1.00
pacquet@main 641.4 ± 16.6 622.1 668.8 1.02 ± 0.03
pnpr@HEAD 682.6 ± 15.2 663.2 706.8 1.08 ± 0.03
pnpr@main 676.1 ± 20.1 640.2 699.6 1.07 ± 0.04
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 0.6318537173200001,
      "stddev": 0.010979494871669543,
      "median": 0.63110591212,
      "user": 0.37517696,
      "system": 1.32561168,
      "min": 0.61550196612,
      "max": 0.64821928212,
      "times": [
        0.61808611112,
        0.64648567912,
        0.61550196612,
        0.63489623212,
        0.64037008012,
        0.63095304212,
        0.64821928212,
        0.62510179012,
        0.63125878212,
        0.62766420812
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 0.64143087142,
      "stddev": 0.016586348945338433,
      "median": 0.64000065712,
      "user": 0.37670426,
      "system": 1.3115391799999998,
      "min": 0.62207425912,
      "max": 0.66878124712,
      "times": [
        0.66878124712,
        0.62613390412,
        0.66376222712,
        0.62207425912,
        0.63167392512,
        0.6458846271199999,
        0.64526105912,
        0.62346373212,
        0.65253347812,
        0.63474025512
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.68259529242,
      "stddev": 0.015176770351765456,
      "median": 0.68055915412,
      "user": 0.38230536000000004,
      "system": 1.36412058,
      "min": 0.66324851412,
      "max": 0.70678746012,
      "times": [
        0.67441227712,
        0.66446964412,
        0.67036408212,
        0.68180933512,
        0.66324851412,
        0.69507391612,
        0.70678746012,
        0.70162169712,
        0.68885702512,
        0.6793089731199999
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.6761023698199999,
      "stddev": 0.020107210589841287,
      "median": 0.68313435012,
      "user": 0.37386506,
      "system": 1.3500867799999998,
      "min": 0.64019498712,
      "max": 0.69961755712,
      "times": [
        0.68923419512,
        0.68828969312,
        0.69220703712,
        0.66752285012,
        0.67797900712,
        0.69961755712,
        0.64019498712,
        0.64656784212,
        0.66873532512,
        0.6906752041199999
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 4.208 ± 0.044 4.163 4.283 1.96 ± 0.10
pacquet@main 4.221 ± 0.089 4.116 4.396 1.96 ± 0.11
pnpr@HEAD 2.151 ± 0.109 1.996 2.382 1.00
pnpr@main 2.192 ± 0.115 2.037 2.388 1.02 ± 0.07
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 4.2083581401000005,
      "stddev": 0.04407802545208617,
      "median": 4.194311255600001,
      "user": 3.764365819999999,
      "system": 3.3404644600000006,
      "min": 4.1625217246,
      "max": 4.2834036996,
      "times": [
        4.2197599836,
        4.2278092026000005,
        4.2017663686,
        4.1868561426,
        4.1813664846,
        4.162984481600001,
        4.2834036996,
        4.1625217246,
        4.2792302506,
        4.1778830626
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 4.2206151925999995,
      "stddev": 0.08925608206533911,
      "median": 4.1836348201,
      "user": 3.7890516199999995,
      "system": 3.34032616,
      "min": 4.1161395406,
      "max": 4.3961720936,
      "times": [
        4.3492995636,
        4.3961720936,
        4.1818260776,
        4.2716824156,
        4.1730423876,
        4.1655138216,
        4.1850683296,
        4.1161395406,
        4.1852063856,
        4.1822013106
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 2.1514622583999996,
      "stddev": 0.10913976509998213,
      "median": 2.1459676616000003,
      "user": 2.5007577199999997,
      "system": 2.8681727600000007,
      "min": 1.9957455736,
      "max": 2.3822116526,
      "times": [
        2.1787549056,
        2.2293431236,
        1.9957455736,
        2.0628847736,
        2.0628396716,
        2.0961953996,
        2.2147121606,
        2.3822116526,
        2.1524798666000002,
        2.1394554566000004
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 2.1915211821000002,
      "stddev": 0.11537774484393201,
      "median": 2.1743880541000005,
      "user": 2.5205407199999996,
      "system": 2.89964996,
      "min": 2.0367507786,
      "max": 2.3880198396,
      "times": [
        2.3623952356,
        2.2500245216000003,
        2.3880198396,
        2.0812763886,
        2.0367507786,
        2.1933687076000004,
        2.1158472156,
        2.1554074006,
        2.1205773786,
        2.2115443546
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 1.344 ± 0.031 1.300 1.389 2.13 ± 0.06
pacquet@main 1.324 ± 0.021 1.289 1.357 2.09 ± 0.04
pnpr@HEAD 0.671 ± 0.081 0.627 0.899 1.06 ± 0.13
pnpr@main 0.632 ± 0.008 0.622 0.646 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 1.34447293388,
      "stddev": 0.03098791952681914,
      "median": 1.3430835219800001,
      "user": 1.3450211200000002,
      "system": 1.70776426,
      "min": 1.3001847209800002,
      "max": 1.3892047599800001,
      "times": [
        1.3770508779800001,
        1.32042438898,
        1.30999923998,
        1.32930213498,
        1.3389319749800002,
        1.3001847209800002,
        1.34831878798,
        1.38407738398,
        1.3472350689800001,
        1.3892047599800001
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 1.3237684765800002,
      "stddev": 0.020790849386291183,
      "median": 1.3207967549800002,
      "user": 1.3099624200000002,
      "system": 1.7139221599999999,
      "min": 1.28930156098,
      "max": 1.35665516798,
      "times": [
        1.3332582459800002,
        1.35455723198,
        1.35665516798,
        1.28930156098,
        1.3301705319800001,
        1.32288491698,
        1.31065696598,
        1.30926823898,
        1.31222331198,
        1.3187085929800002
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.67090703848,
      "stddev": 0.0807161054200225,
      "median": 0.65028016098,
      "user": 0.32482372000000004,
      "system": 1.2900561600000002,
      "min": 0.62695816198,
      "max": 0.89881550098,
      "times": [
        0.62695816198,
        0.64977754398,
        0.63515460398,
        0.65078277798,
        0.89881550098,
        0.64685019298,
        0.65293631898,
        0.66012623698,
        0.6352144399799999,
        0.65245460698
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.63201239438,
      "stddev": 0.0075829010155113495,
      "median": 0.63160946648,
      "user": 0.32204271999999995,
      "system": 1.24772756,
      "min": 0.62217036598,
      "max": 0.64609586298,
      "times": [
        0.62217036598,
        0.63321174898,
        0.64609586298,
        0.63161496998,
        0.62631867998,
        0.63160396298,
        0.63090121698,
        0.62248452798,
        0.63414123398,
        0.64158137398
      ]
    }
  ]
}

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

Command Mean [s] Min [s] Max [s] Relative
pacquet@HEAD 3.006 ± 0.069 2.919 3.174 4.68 ± 0.12
pacquet@main 2.984 ± 0.047 2.918 3.066 4.65 ± 0.10
pnpr@HEAD 0.659 ± 0.014 0.640 0.689 1.03 ± 0.03
pnpr@main 0.642 ± 0.008 0.626 0.656 1.00
BENCHMARK_REPORT.json
{
  "results": [
    {
      "command": "pacquet@HEAD",
      "mean": 3.00567046972,
      "stddev": 0.06922773311194935,
      "median": 2.98154389912,
      "user": 1.7517634199999996,
      "system": 1.9449801199999999,
      "min": 2.91881552012,
      "max": 3.1741893231200002,
      "times": [
        3.05305252012,
        2.96842141112,
        2.98141427412,
        2.97405275712,
        2.91881552012,
        2.97227410712,
        3.02128791712,
        3.01152334312,
        3.1741893231200002,
        2.98167352412
      ]
    },
    {
      "command": "pacquet@main",
      "mean": 2.9843242399200003,
      "stddev": 0.04747482056272787,
      "median": 2.96757068362,
      "user": 1.7675999199999999,
      "system": 1.95360652,
      "min": 2.91756918612,
      "max": 3.06592952612,
      "times": [
        2.94419023512,
        2.9637464491200003,
        3.06592952612,
        3.04641895712,
        2.99068263312,
        2.97139491812,
        2.91756918612,
        2.9571785411200002,
        3.02609481412,
        2.96003713912
      ]
    },
    {
      "command": "pnpr@HEAD",
      "mean": 0.6585417846200001,
      "stddev": 0.014389335598381143,
      "median": 0.65610944662,
      "user": 0.32924892000000006,
      "system": 1.30676962,
      "min": 0.63950563912,
      "max": 0.6888614081200001,
      "times": [
        0.6623162141200001,
        0.65817519512,
        0.64991407812,
        0.6701642051200001,
        0.6888614081200001,
        0.6540436981200001,
        0.64523712712,
        0.66756312612,
        0.63950563912,
        0.6496371551200001
      ]
    },
    {
      "command": "pnpr@main",
      "mean": 0.64219949182,
      "stddev": 0.008480588685803064,
      "median": 0.64104370562,
      "user": 0.32049062000000006,
      "system": 1.27558592,
      "min": 0.62622884912,
      "max": 0.65614882212,
      "times": [
        0.64165564412,
        0.64664567312,
        0.6493377461200001,
        0.64937861212,
        0.63668501012,
        0.62622884912,
        0.64043176712,
        0.63679379512,
        0.65614882212,
        0.63868899912
      ]
    }
  ]
}

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12327
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,208.36 ms
(+0.85%)Baseline: 4,172.95 ms
5,007.54 ms
(84.04%)
isolated-linker.fresh-install.cold-cache.hot-store📈 view plot
🚷 view threshold
3,005.67 ms
(+0.43%)Baseline: 2,992.85 ms
3,591.42 ms
(83.69%)
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
🚷 view threshold
1,344.47 ms
(+2.20%)Baseline: 1,315.54 ms
1,578.65 ms
(85.17%)
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
🚷 view threshold
4,081.81 ms
(+2.78%)Baseline: 3,971.44 ms
4,765.72 ms
(85.65%)
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
🚷 view threshold
631.85 ms
(+1.69%)Baseline: 621.36 ms
745.63 ms
(84.74%)
🐰 View full continuous benchmarking report in Bencher

@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

🐰 Bencher Report

Branchpr/12327
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,151.46 ms
isolated-linker.fresh-install.cold-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
658.54 ms
isolated-linker.fresh-install.hot-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
670.91 ms
isolated-linker.fresh-restore.cold-cache.cold-store📈 view plot
⚠️ NO THRESHOLD
2,142.89 ms
isolated-linker.fresh-restore.hot-cache.hot-store📈 view plot
⚠️ NO THRESHOLD
682.60 ms
🐰 View full continuous benchmarking report in Bencher

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

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

Copy link
Copy Markdown

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

@zkochan zkochan force-pushed the scarab-systems/pnpm-12222-tmpdir-path branch from a980b73 to 0e25b7d Compare June 16, 2026 17:25
@qodo-free-for-open-source-projects

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

Copy link
Copy Markdown

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

Comment on lines +50 to +57
test('uses short CAFS temp directories for git package preparation', async () => {
const storeDir = temporaryDirectory()
const tmpDir = await createCafsStore(storeDir).tempDir()

expect(path.dirname(tmpDir)).toBe(path.join(storeDir, 'tmp'))
expect(path.basename(tmpDir)).toMatch(/^_tmp_[a-zA-Z0-9]{6}$/)
expect(path.basename(tmpDir).length).toBeLessThanOrEqual(11)
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Cafs tmpdir test too narrow 📎 Requirement gap ☼ Reliability

The added regression test only asserts the CAFS temp directory basename pattern/length and does not
validate the root-context pnpm install reproduction with a git-hosted dependency prepare script
that creates IPC sockets under TMPDIR. This leaves the rule’s required end-to-end root
TMPDIR/socket-path behavior unverified.
Agent Prompt
## Issue description
PR Compliance requires validating the root-context reproduction scenario (git-hosted dependency with a `prepare` script that creates IPC sockets/pipes under `TMPDIR`). The current test only checks the CAFS temp directory name/length and does not exercise root-context lifecycle execution or verify that resulting IPC paths stay within the 108-byte `sockaddr_un` limit.

## Issue Context
The compliance objective is specifically about `pnpm install` as root (`unsafePerm=false`) and TMPDIR-based IPC artifacts created during git dependency preparation.

## Fix Focus Areas
- fetching/git-fetcher/test/index.ts[50-57]

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Accurate observation, but this is the right level for the test, so I'm leaving it as-is.

A faithful end-to-end reproduction of the original bug requires running as root on Linux, a real git-hosted dependency whose prepare script opens an AF_UNIX IPC socket, and hitting the 108-byte sockaddr_un limit — none of which is portable or feasible in a Jest unit test. The added assertions (_tmp_ + 6 chars, length ≤ 11) lock in the exact invariant that prevents the regression: the CAFS temp directory name stays short and bounded, which is what reclaims the socket-path budget for lifecycle tooling.


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

Comment on lines +142 to +143
await fs.mkdir(baseTempDir, { recursive: true })
return fs.mkdtemp(path.join(baseTempDir, '_tmp_'))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Temp dir mode too strict 🐞 Bug ☼ Reliability

createCafsStore().tempDir() now uses fs.mkdtemp(), which creates the temp directory with
restrictive permissions (0700 by default on Unix), unlike the prior fs.mkdir() flow whose
permissions followed the process umask. Because fetchers run git clone and preparePackage()
inside this temp directory, any lifecycle execution that drops privileges (the unsafePerm=false
path) will fail to write into the temp working directory with EACCES.
Agent Prompt
### Issue description
`createCafsStore().tempDir()` now returns a directory created by `fs.mkdtemp()`. In this repo, `mkdtemp()` is documented as creating temp directories with restrictive permissions (0700 by default), which differs from the previous `fs.mkdir()` behavior (umask-derived). This can make CAFS temp working directories unwritable when lifecycle tooling executes under different credentials (e.g. `unsafePerm=false` root flows).

### Issue Context
The returned `tempLocation` is used as the working directory for `git` operations and for `preparePackage()` (which runs package managers and lifecycle hooks), so it must be writable by the effective user that runs those steps.

### Fix Focus Areas
- store/create-cafs-store/src/index.ts[131-145]

### Suggested fix
After `mkdtemp`, explicitly align permissions with `mkdir` defaults, e.g.:
- `const tmpDir = await fs.mkdtemp(path.join(baseTempDir, '_tmp_'))`
- `await fs.chmod(tmpDir, 0o777 & ~process.umask())`
- `return tmpDir`

Alternatively, avoid `mkdtemp` and generate a short random suffix yourself, then `fs.mkdir(tmpDir)` so the mode naturally follows umask.

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the catch, but I believe this is a false positive — keeping fs.mkdtemp (0700) as-is.

unsafePerm defaults to false only when pnpm runs as root (config/reader/src/npmDefaults.ts:9-10), which is exactly this scenario. The temp dir is created by the root pnpm process, so it's root-owned under both the old and new code. The git clone and preparePackage() steps are driven by that root process, and writing into a root-owned 0700 dir as root succeeds.

The only case described here — a privilege-dropped non-owner user writing into the dir — already failed under the previous 0755: 0755 grants "others" read+execute but no write, so a non-owner could not write into the root-owned dir either way. So 0700 introduces no new failing scenario; the set of working cases is unchanged. 0700 is also the more appropriate mode for a temp dir holding unpacked package contents, so reverting to 0755 would be a small security regression with no functional gain.


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

@zkochan zkochan merged commit 30c7590 into pnpm:main Jun 16, 2026
20 of 21 checks passed
@scarab-systems scarab-systems deleted the scarab-systems/pnpm-12222-tmpdir-path branch June 16, 2026 20:54
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.

Long TMPDIR (set by pnpm) makes IPC pipe path exceed 108-byte limit when run as root

2 participants