エラーの内容
Error: EPERM: operation not permitted, rename 'C:\project\file.tmp' -> 'C:\project\file.js'
Node.jsがファイルの名前変更を試みました。Windowsが拒否しました。簡単に言えばそれだけです。
詳しく説明すると、別の何かがファイルをロックしていました — バックグラウンドスキャナー、ゾンビプロセス、またはストリームを閉じ忘れた自分のコードが原因です。このエラーはビルド時、npm install時、または一時ファイルを介して書き込みを行うツール(webpack、rollup、esbuild、fs-extraなど)を使用している際に最もよく発生します。
根本原因
Windowsは強制的なファイルロックを使用しています。Linuxでは開いているファイルの名前変更は問題なく行えますが、Windowsではできません — ファイルを開いているプロセスがある限り、そのプロセスが手放すまで名前変更がブロックされます。主な原因として以下が挙げられます:
- アンチウイルス / Windows Defender — ファイルがディスクに書き込まれた瞬間にスキャンを行い、ちょうどNode.jsが名前変更を試みるタイミングと重なる
- 別のNodeプロセス — ウォッチャー、開発サーバー、またはハンドルを保持したままのゾンビプロセス
- Windows検索インデクサー — バックグラウンドでプロジェクトフォルダを静かにスキャンしている
- 自分のコード — 書き込みストリームが完全に閉じる前に
fs.renameを呼び出している - 権限不足 — Node.jsが対象ディレクトリへの書き込みアクセス権なしで実行されている
修正方法1: プロジェクトフォルダをアンチウイルスの除外リストに追加する(最も一般的な解決策)
ほとんどの場合、Windows Defenderが原因です。新しいファイルがディスクに書き込まれた瞬間にスキャンを行い、ちょうどNode.jsが名前変更を試みるタイミングと重なります。この時間は非常に短い(多くの場合50ms未満)ですが、EPERMを引き起こすには十分です。
プロジェクトフォルダを除外リストに追加してください:
- Windowsセキュリティ → ウイルスと脅威の防止 → 設定の管理を開く
- 除外 → 除外の追加または削除までスクロール
- プロジェクトのルートフォルダ(例:
C:\projects\myapp)をフォルダの除外として追加 - プロジェクトルート外にある場合は
node_modulesおよびdist/buildディレクトリも除外に追加
サードパーティのアンチウイルスツール(Kaspersky、Bitdefenderなど)でも、リアルタイム保護の除外設定で同様の設定ができます。
修正方法2: ゾンビNodeプロセスを終了する
クラッシュした開発サーバーは常に自分自身をクリーンアップするとは限りません。タスクマネージャーからプロセスは消えていても、Windowsはそのファイルハンドルを使用中としてマークしたままにしていることがあります。すべてを終了して新たにやり直しましょう:
taskkill /F /IM node.exe
より細かく制御したい場合は、PowerShellで停止するプロセスを選択できます:
Get-Process node | Stop-Process -Force
クリアな状態になったら、ビルドまたはインストールコマンドを再実行してください。
修正方法3: 自分のコードにリトライロジックを追加する
DefenderやインデクサーによるロックはたいていI200ms以内に解除されます。クラッシュさせるよりも、指数バックオフでリトライする方が賢明です — 試行のたびに遅延を倍にすることで、まだロックされているファイルへの過剰なアクセスを防げます:
const fs = require('fs');
function renameWithRetry(oldPath, newPath, retries = 5, delay = 100) {
return new Promise((resolve, reject) => {
fs.rename(oldPath, newPath, (err) => {
if (!err) return resolve();
if (err.code === 'EPERM' && retries > 0) {
setTimeout(() => {
renameWithRetry(oldPath, newPath, retries - 1, delay * 2)
.then(resolve)
.catch(reject);
}, delay);
} else {
reject(err);
}
});
});
}
// 使用例
await renameWithRetry('output.tmp', 'output.js');
5回のリトライと初期遅延100msで、諦めるまでの合計待機時間は最大約3.1秒になります — 一時的なスキャナーロックには十分な時間です。
修正方法4: プロジェクトフォルダのWindows検索インデックスを無効にする
検索インデクサーは目立ちにくい原因ですが、大規模なモノレポへのnpm installのような大量書き込み操作で同様のEPERMを引き起こします:
- エクスプローラーを開く → プロジェクトフォルダを右クリック → プロパティ
- 全般タブで詳細設定をクリック
- このフォルダのファイルをインデックスに登録してコンテンツ検索を可能にするのチェックを外す
- プロンプトが表示されたらすべてのサブフォルダにも適用
修正方法5: 管理者権限で実行する(最終手段)
C:\Program Filesやシステムが所有するディレクトリなど保護されたパスに書き込む場合は、Node.jsに管理者権限が必要です:
# 管理者としてコマンドプロンプトを実行してから:
node build.js
npmスクリプトの場合は、ターミナルを右クリックして「管理者として実行」を選択してください。これは最終手段として扱ってください — 対象フォルダが自分で作成したものであれば、権限を昇格させるのではなく、根本原因を修正してください。
修正方法6: 名前変更前にファイルが閉じていることを確認する
自分のコードがロックを引き起こしている場合、修正は簡単です:renameを呼び出す前にストリームが完全に閉じるのを待つだけです。endコールバックがそのシグナルです:
const fs = require('fs');
const stream = fs.createWriteStream('output.tmp');
stream.write('hello world');
stream.end(() => {
// ここでファイルハンドルが解放される — 名前変更しても安全
fs.rename('output.tmp', 'output.js', (err) => {
if (err) console.error('Rename failed:', err);
});
});
endコールバック内でfs.renameを呼び出すことで、名前変更を試みる前にハンドルが確実に解放されます。
修正の確認
失敗したコマンドを再度実行して、問題が解決したことを確認してください:
# 失敗したコマンドを再実行
npm run build
# または名前変更を直接テスト
node -e "require('fs').renameSync('test.tmp', 'test.out'); console.log('OK')"
元のエラーがnpm installによるものだった場合は、再試行前にnode_modulesとロックファイルを削除してください — 不完全なインストールは古いハンドルを残すことがあります:
rd /s /q node_modules
del package-lock.json
npm install
予防策
- プロジェクトフォルダは最初からアンチウイルスの除外リストに追加する — エラーが出るまで待たないでください。Windows開発マシンでは標準的な対応であり、設定に30秒もかかりません。
- 長時間稼働するサーバーにはPM2のようなプロセスマネージャーを使用する — プロセスを強制終了してファイルハンドルを孤立させるのではなく、再起動時に適切にファイルハンドルを閉じます。
- 可能な場合は一時ファイルを
os.tmpdir()に書き込む — システムの一時ディレクトリは通常スキャナーの除外対象になっているため、名前変更の競合が発生しにくいです。 - 本番スクリプトにリトライロジックを組み込む — Windows CIサーバーや共有ネットワークドライブは一時的なロックが発生しやすいです。3回のリトライループにコストはかからず、ランダムなビルド失敗を防げます。
プロジェクトがLinuxでも動作していて、両方のプラットフォームでファイル権限を管理している場合は、ToolCraftのUnix権限計算ツールを使用すると、8進数を暗記することなく正しいchmodの値を視覚的に確認できます。

