Skip to content

[BUG] npm exec fails with ECOMPROMISED with Node 24 on Windows #8710

Closed

Description

@dextersealy

Is there an existing issue for this?

  • I have searched the existing issues

This issue exists in the latest npm version

  • I am using the latest npm

Current Behavior

I have a Github action that runs npx --yes <package> and the command fails with ECOMPROMISED when run with Node 24 on Windows. The same command works on Ubuntu and also works on Windows with Node 22.

Expected Behavior

npx --yes <package> should not fail with ECOMPROMISED error

Steps To Reproduce

My action is attempting to run an internally developed package, but the issue happens with other packages. This small repo reproduces the problem with jest. See an example workflow run here: https://github.com/dextersealy/npx-error/actions/runs/18947726487

  1. Add example https://github.com/dextersealy/npx-error/blob/main/.github/workflows/test.yml workflow to repo
  2. Run the workflow
  3. See that npx --yes jest --version fails in job that uses Node 24 on Windows

Environment

  • npm: 11.6.2
  • Node.js: 24.10.0
  • OS Name: Windows Server 2025 10.0.26100 Datacenter
  • System Model Name: Github runner image: windows-2025 20251021.67.1
  • npm config:

; "global" config from C:\npm\prefix\etc\npmrc
cache = "C:\npm\cache"
; "env" config from environment
prefix = "C:\npm\prefix"
; node bin location = C:\hostedtoolcache\windows\node\24.10.0\x64\node.exe
; node version = v24.10.0
; npm local prefix = D:\a\npx-error\npx-error
; npm version = 11.6.2
; cwd = D:\a\npx-error\npx-error
; HOME = C:\Users\runneradmin
; Run npm config ls -l to show all defaults.

Activity

  1. flyingduck92 commented on Nov 3, 2025

    @flyingduck92

    I got same issue, when trying to make a new tanstack-router project the issue is similar -> TanStack/cli#221

  2. patrickledew commented on Nov 14, 2025

    @patrickledew

    I'm getting the same issue when running npx with the @asyncapi/cli package. It does work after a couple tries, but the majority of the time I get this ECOMPROMISED error. The log shows this stack trace:

    192 verbose stack Error: Lock compromised
    192 verbose stack     at AbortSignal.<anonymous> (C:\Program Files\nodejs\node_modules\npm\node_modules\libnpmexec\lib\with-lock.js:54:30)
    192 verbose stack     at [nodejs.internal.kHybridDispatch] (node:internal/event_target:845:20)
    192 verbose stack     at AbortSignal.dispatchEvent (node:internal/event_target:778:26)
    192 verbose stack     at runAbort (node:internal/abort_controller:488:10)
    192 verbose stack     at abortSignal (node:internal/abort_controller:459:3)
    192 verbose stack     at AbortController.abort (node:internal/abort_controller:507:5)
    192 verbose stack     at Timeout.touchLock (C:\Program Files\nodejs\node_modules\npm\node_modules\libnpmexec\lib\with-lock.js:176:18)
    193 error code ECOMPROMISED
    194 error Lock compromised
    

    To debug I added some console.logs to the with-lock.js file:

    async function maintainLock (lockPath) {
      console.log("maintainLock for ",lockPath);
      const controller = new AbortController()
      const stats = await fs.stat(lockPath)
      // fs.utimes operates on floating points seconds (directly, or via strings/Date objects), which may not match the underlying filesystem's mtime precision, meaning that we might read a slightly different mtime than we write. always round to the nearest second, since all filesystems support at least second precision
      let mtime = Math.round(stats.mtimeMs / 1000)
      const signal = controller.signal
    
      async function touchLock () {
        console.log("touchLock for ",lockPath);
        try {
          const ts = Date.now();
          console.log("fs.stat for ",lockPath);
          const currentStats = (await fs.stat(lockPath))
          console.log("fs.stat completed for ",lockPath, ". Took ", Date.now() - ts, "ms");
          const currentMtime = Math.round(currentStats.mtimeMs / 1000)
          if (currentStats.ino !== stats.ino || currentMtime !== mtime) {
            console.log("expected mtime: ", mtime, " current mtime: ", currentMtime);
            throw new Error('Lock compromised')
          }
          mtime = Math.round(Date.now() / 1000)
          // touch the lock, unless we just released it during this iteration
          if (currentLocks.has(lockPath)) {
            console.log("updating mtime for ",lockPath, " to ", mtime);
            await fs.utimes(lockPath, mtime, mtime)
    
          }
        } catch (err) {
          // stats mismatch or other fs error means the lock was compromised
          console.log("aborting...");
          controller.abort()
        }
      }
    
      const timeout = setInterval(touchLock, touchInterval)
      // timeout.unref()
      function cleanup () {
        clearInterval(timeout)
        deleteLock(lockPath)
    
      }
      currentLocks.set(lockPath, cleanup)
      return signal
    }
    

    This gives the following output:

    ⠼maintainLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠧touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠇fs.stat completed for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock . Took  52 ms
    updating mtime for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock  to  1763134096
    ⠏touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat completed for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock . Took  6 ms
    updating mtime for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock  to  1763134098
    ⠦touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat completed for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock . Took  1 ms
    updating mtime for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock  to  1763134100
    ⠸maintainLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠇touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat completed for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock . Took  2 ms
    updating mtime for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock  to  1763134103
    ⠏touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠸fs.stat completed for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock . Took  510 ms
    updating mtime for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock  to  1763134105
    ⠼touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠸touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠹touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠼touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠼touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠦touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠴touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠼touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠹touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠦touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠼touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠹touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠸touchLock for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    fs.stat for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock
    ⠴fs.stat completed for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock . Took  25809 ms
    updating mtime for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock  to  1763134132
    fs.stat completed for  C:\Users\patrick.ledew\AppData\Local\npm-cache\_npx\0929aae77d023606\concurrency.lock . Took  23778 ms
    expected mtime:  1763134132  current mtime:  1763134105
    aborting...
    aborting...
    aborting...
    aborting...
    ⠦aborting...
    aborting...
    aborting...
    aborting...
    aborting...
    aborting...
    aborting...
    aborting...
    aborting...
    npm error code ECOMPROMISED
    npm error Lock compromised
    npm error A complete log of this run can be found in: C:\Users\patrick.ledew\AppData\Local\npm-cache\_logs\2025-11-14T15_28_12_159Z-debug-0.log
    

    Seems like the fs.stat call works fine the first couple calls, but then balloons to 500ms then 23 seconds! While this is hanging, the interval for touchLock keeps getting called. When the next fs.stat call eventually succeeds it seems to use an old value for the current mtime which triggers the abort.

    For context, this is running on a Windows 365 VM so I'm unsure if that would affect fs.stat performance.

    Node version: v25.2.0

    NPM version: 11.6.2

    cc: @jenseng

  3. jenseng commented on Nov 14, 2025

    @jenseng
    Contributor

    Thanks for the ping and the detailed investigation @patrickledew! I'll take a stab at fixing this. The 23 second stat calls are definitely surprising 😱

  4. jenseng commented on Nov 14, 2025

    @jenseng
    Contributor

    Given that it only happens in windows + node 24, it sounds like there may be a bug in node that's causing the long stat calls. But hopefully we can protect against that (e.g. perhaps a higher staleThreshold, and change from setInterval -> setTimeout)

  5. jenseng commented on Nov 14, 2025

    @jenseng
    Contributor

    Looks like it's not specific to node 24, it happens in node 22 as well. The test.yml workflow in the PR description is actually using npm@10.x when testing node 22, which is why it passed.

    I've just about got a fix ready, which seems to do the trick 🥳

  6. added a commit that references this issue on Nov 15, 2025
    0bd071a
  7. added 2 commits that reference this issue on Jan 4, 2026
  8. added 2 commits that reference this issue on May 30, 2026
    6ccb051
    471feed
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

    Bugthing that needs fixingNeeds Triageneeds review for next steps

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions