Repository: vercel/next.js
File: packages/next/src/build/lockfile.ts (TypeScript)

Code before:
            ? nodePath.join(projectDir, relativeDistDir)
            : undefined
        const serverInfo = distDir ? readDevServerInfo(distDir) : undefined

        if (serverInfo) {
          Log.error(`Another ${cyan(processName)} server is already running.`)
          console.error()
          console.error(`- Local:        ${cyan(serverInfo.appUrl)}`)
          console.error(`- PID:          ${serverInfo.pid}`)
          if (projectDir) {
            console.error(`- Dir:          ${projectDir}`)
          }
          if (relativeDistDir) {
            console.error(
              `- Log:          ${nodePath.join(relativeDistDir, 'logs', 'next-development.log')}`
            )
          }
          console.error()
          console.error(`Run ${cyan(`kill ${serverInfo.pid}`)} to stop it.`)
        } else {
          Log.error(
            `Another ${cyan(processName)} process is already running in this directory.`
          )
          console.error(
            `Stop the other process or delete ${cyan(path)} if no process is running.`
          )
        }
      } else {
        // For build, show that a build is in progress
        Log.error(`Another ${cyan(processName)} process is already running.`)
        console.error()
        console.error(`  This could be:`)
        console.error(`  - A ${cyan('next build')} still in progress`)
        console.error(`  - A previous build that didn't exit cleanly`)
        console.error()
        Log.info(
          `${bold('Suggestion:')} Wait for the build to complete, or delete ${cyan(path)} if no build is running.`
        )
      }
      process.exit(1)
    }
    return lockfile
  }

  /**
   * Releases the lockfile and closes the file descriptor.
   *
   * If this is not called, the lock will be released by the operating system
   * when the file handle is closed during process exit.
   */
  async unlock(): Promise<void> {

Review comment (bug):
I don't think stale lockfiles are actually possible. We use the OS APIs that guarantee that the operating system will release the lock if the process exits and we fail to clean it up. Stale lockfiles are only ever an issue with applications that don't use the proper OS-level APIs.

You can have a stale file on disk if we get don't exit cleanly or if your computer loses power, but it won't actually be locked (the locking is tracked in-memory by the kernel), and we'll clean it up on the next restart, so this error won't happen in that case.

So I don't think we should tell users or agents to delete the lockfile, because we have extremely high confidence that they have a competing process and deleting the lockfile will almost certainly screw things up.

Code after:
          if (relativeDistDir) {
            console.error(
              `- Log:          ${nodePath.join(relativeDistDir, 'logs', 'next-development.log')}`
            )
          }
          console.error()
          // Use platform-appropriate kill command
          const killCommand =
            process.platform === 'win32'
              ? `taskkill /PID ${serverInfo.pid} /F`
              : `kill ${serverInfo.pid}`
          console.error(`Run ${cyan(killCommand)} to stop it.`)
        } else {
          // Fallback when we can't read server info from the lockfile
          Log.error(
            `Another ${cyan(processName)} server is already running in this directory.`
          )
          console.error(`Stop the other server before starting a new one.`)
        }
      } else {
        // For build, show that a build is in progress
        Log.error(`Another ${cyan(processName)} process is already running.`)
        console.error()
        console.error(`  This could be:`)
        console.error(`  - A ${cyan('next build')} still in progress`)
        console.error(`  - A previous build that didn't exit cleanly`)
        console.error()
        Log.info(`${bold('Suggestion:')} Wait for the build to complete.`)
      }
      process.exit(1)
    }
    return lockfile
  }

  /**
   * Releases the lockfile and closes the file descriptor.
   *
   * If this is not called, the lock will be released by the operating system
   * when the file handle is closed during process exit.
   */
  async unlock(): Promise<void> {
    const { nativeLockfile, listener } = this
    if (nativeLockfile !== undefined) {
      await this.bindings.lockfileUnlock(nativeLockfile)
    }
    if (listener !== undefined) {
      process.off('exit', listener)
    }
  }

  /**