
Windowsで「EPERM: operation not permitted」「Lock file is already being held」が出てOpenCodeクライアントが落ちる原因と、今すぐできる回避策
Windows環境でOpenCodeを使っていると、バックグラウンドのコンテキスト保存中に EPERM(リネーム失敗)や「Lock file is already being held」(ロック取得失敗)が連発し、最終的にクライアントがクラッシュするケースがあります。ログ上は antigravity.plugin が antigravity-accounts.json を一時ファイルから置き換えようとして転び続ける状態です。 GitHub
- Windowsで「EPERM: operation not permitted」「Lock file is already being held」が出てOpenCodeクライアントが落ちる原因と、今すぐできる回避策
何が起きているか:Windows特有の「置き換えリネーム」と「ロック」の相性問題
問題の中心は、設定ファイル(例:~\.config\opencode\antigravity-accounts.json)を安全に保存するための「一時ファイルに書く → 最後にリネームで差し替える」という定番手法です。ところがWindowsでは、保存先ファイルが他プロセスに少しでも掴まれている(ウイルス対策、検索インデックス、同期ソフト、別インスタンスなど)と、rename tmp -> 本体 が EPERM で失敗しやすくなります。 GitHub+1
この失敗が起点になり、次にロックファイル(もしくは内部ロック)を見に行った瞬間「すでに保持されている」と判断され、保存処理がループ→ログ爆増→クラッシュ、という流れになりがちです。 GitHub
典型的な引き金:見落としがちな5つ
今回の条件(Windows 10 / OpenCode 1.1.50 / Node.js 24.13.0 / Googleアカウント4+)だと、特に次が刺さります。 GitHub
-
リアルタイム保護・EDR・ウイルス対策
.jsonや.tmpを短時間で開閉する挙動を監視し、ハンドルが一瞬残ることがあります。 -
クラウド同期(OneDrive/Dropbox等)
設定ディレクトリが同期対象だと、保存直後に同期側が掴みに来ます。 -
Windows Search のインデックス
ユーザーディレクトリ配下で頻繁に更新されると、読み取りが競合しやすいです。 -
OpenCodeを複数起動している(気づかない二重起動)
“ロックが保持されている”の最頻出パターンです。 -
保存頻度が高い(バックグラウンド永続化が短い間隔)
1回の遅延が次の保存と衝突し、連鎖的に悪化します。
今すぐできる回避策(ユーザー側):上から順に効きやすい
1) まず「二重起動」を潰す
-
タスクマネージャーでOpenCode関連プロセスが複数いないか確認
-
常駐や自動復帰で増えていないか確認
これだけで「Lock file is already being held」が止まることがあります。
2) 設定フォルダを“同期・監視”の外へ逃がす
問題ファイルは c:\Users\<you>\.config\opencode\... にありました。 GitHub
もしユーザープロファイル配下がOneDrive同期や企業EDRの監視対象なら、設定の保存先を同期外ディレクトリに移す(例:C:\opencode-config\)のが強力です。アプリが環境変数や設定でパス変更に対応している場合はそこを使い、難しい場合は同期設定側で除外します。
3) ウイルス対策・EDRの除外に「設定ディレクトリ」を追加
除外対象の例:
-
C:\Users\<you>\.config\opencode\ -
antigravity-accounts.jsonおよび*.tmp
監視が原因なら、EPERMの頻度が目に見えて下がります。
4) 「保存が走るきっかけ」を減らす(負荷を下げる)
-
大きなプロジェクトを開いた直後など、落ちやすいタイミングでは一度再起動
-
可能ならプラグインのバックグラウンド永続化の頻度を下げる(設定がある場合)
5) Node.jsのバージョンを安定系へ寄せる(運用回避)
ログには Node.js 24.13.0 が出ています。 GitHub
特定の組み合わせで挙動が荒れる場合があるため、運用としては LTS系(例:20/22系) へ寄せると落ち着くことがあります(OpenCode側の推奨があるならそれに従うのが最優先)。
開発側の対策(実装修正の方向性):Windowsで落ちない永続化へ
ユーザー側回避で止まらない場合、根本は「Windowsではリネームが失敗しうる」前提で設計することです。
1) リネームを“即死”にしない:リトライ+指数バックオフ
EPERM / EACCES は一時的な競合が多いので、数百ms〜1秒程度のリトライで救えるケースが多いです。実際、Windowsのリネームをリトライする実装方針は周辺ライブラリでも採用されています。 NPM
擬似コード例:
async function renameWithRetry(src, dst, tries = 10) {
let delay = 25;
for (let i = 0; i < tries; i++) {
try {
await fs.promises.rename(src, dst);
return;
} catch (e) {
if (!['EPERM', 'EACCES', 'EBUSY'].includes(e.code)) throw e;
await new Promise(r => setTimeout(r, delay));
delay = Math.min(delay * 2, 1000);
}
}
// 最後に例外を投げる or フォールバック(copy+replace等)
throw new Error('rename retry exhausted');
}
2) ロックファイルは「永遠に保持」にならない仕組みにする
“Lock file is already being held” が出た時、
-
どのプロセスが保持しているのか
-
いつから保持しているのか(TTL)
を判断できないと、事故時に永久ループになります。
対策の定番:
-
ロックファイルに
pidとtimestampを書く -
一定時間(例:30秒〜数分)を超えたら stale lock とみなして回収
-
取得できない場合は保存処理をスキップし、クラッシュさせずに次回へ回す
3) 失敗時に“ループ加速”しない:サーキットブレーカー
ログが短時間に連発しているなら、保存処理がエラーのたびに即再実行されている可能性があります。 GitHub
-
連続失敗回数で一時停止(例:10回失敗で30秒停止)
-
その間はメモリ上に保持し、次に成功したタイミングでまとめて保存
これで「ログ爆増→クラッシュ」を防げます。
まとめ:落ちる原因は「Windowsのファイルハンドル競合」を前提にしていないこと
今回の症状は、Windowsでありがちな「ファイルが一瞬掴まれる」状況で、tmp→本体 の置き換えが EPERM になり、さらにロック判定が絡んで保存処理が破綻するタイプです。 GitHub+1
対処は、ユーザー側なら 二重起動の排除・同期/監視の除外・設定ディレクトリの退避 が即効性。開発側なら リネームのリトライ、ロックのTTL、失敗時の暴走防止 を入れるのが決定打になります。 NPM