Skip to content

[Bug?]: EISDIR when reading from a directory that has the same path as a previously existing zip file #7241

Description

@Seinzu

Self-service

  • I'd be willing to implement a fix

Describe the bug

In a long-running process using the pnp linker an error like EISDIR: illegal operation on a directory, read can be emitted when a zip file is removed and replaced by a directory with the same name. For example some.zip is a zip file and is removed. It is then replaced with a directory called some.zip (with some contents) and a read is attempted on a subpath (e.g. some.zip/file). This appears to be due to a caching issue in the pnp FS code as it does not happen when there was never a file called some.zip (or some.zip was never accessed from the process).

To reproduce

Package.json:

{
  "name": "yarn-pnp-zip-mount-cache-demo",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "packageManager": "yarn@4.18.0"
}

.yarnrc.yml:

enableGlobalCache: false
nodeLinker: pnp
npmMinimalAgeGate: 0

repro.mjs

// Run with
//   node --require ./.pnp.cjs repro.mjs

import fs from 'node:fs'
import Path from 'node:path'

const dir = Path.join(import.meta.dirname, 'work')
fs.rmSync(dir, { recursive: true, force: true })
fs.mkdirSync(dir, { recursive: true })

const zipPath = Path.join(dir, 'demo.zip')
const nestedPath = Path.join(zipPath, 'nested.txt')

console.log('--- Step 1: create demo.zip as a plain (non-archive) file ---')
fs.writeFileSync(zipPath, 'just some ordinary file content, not a real zip')
console.log('created:', zipPath)

console.log('\n--- Step 2: touch it with a plain fs call while it is a file ---')
fs.statSync(zipPath)
console.log('stat succeeded, isFile:', fs.statSync(zipPath).isFile())

console.log('\n--- Step 3: delete the file, replace it with a real directory ---')
fs.unlinkSync(zipPath)
fs.mkdirSync(zipPath)
console.log(
  'demo.zip is now a directory:',
  fs.statSync(zipPath).isDirectory()
)

console.log('\n--- Step 4: write a file nested under the new directory ---')
try {
  fs.writeFileSync(nestedPath, 'hello from inside the real directory')
  console.log('SUCCEEDED - wrote', nestedPath)
  console.log('contents:', fs.readFileSync(nestedPath, 'utf8'))
} catch (err) {
  console.log('FAILED:', err.message)
  console.log('code:', err.code)
  console.log(err.stack)
}

console.log('\n--- Control: same thing, but demo2.zip was not a file first ---')
const zipPath2 = Path.join(dir, 'demo2.zip')
fs.mkdirSync(zipPath2)
try {
  fs.writeFileSync(Path.join(zipPath2, 'nested.txt'), 'hello')
  console.log('SUCCEEDED (as expected - a directory that was always a directory works fine)')
} catch (err) {
  console.log('FAILED (unexpected):', err.message)
}

Error appears in step 4 when run with node --require ./.pnp.cjs repro.mjs. No error when called with node repro.mjs

Environment

System:
    OS: macOS 26.5.1
    CPU: (11) arm64 Apple M3 Pro
  Binaries:
    Node: 24.14.1 - /private/var/folders/3f/5p9h4mx96q3gj7k8cc_l5qp80000gn/T/xfs-7194d65c/node
    Yarn: 4.18.0 - /private/var/folders/3f/5p9h4mx96q3gj7k8cc_l5qp80000gn/T/xfs-7194d65c/yarn
    npm: 11.11.0 - <my home>/.nvm/versions/node/v24.14.1/bin/npm
    bun: 1.3.1 - <my home>/.bun/bin/bun
    Deno: 2.1.6 - <my home>/.deno/bin/deno

Additional context

No response

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions