Claude Codeのclaude -pがWindowsで全滅する — oauth_refresh.lockが解放されないバグ

Claude Codeのclaude -pがWindowsで全滅する — oauth_refresh.lockが解放されないバグ

Windowsでclaude -pをバッチ実行すると「another Claude Code process is refreshing it or exited mid-refresh」で一斉に失敗する。stale判定の設定は入っているのに解放されないoauth_refresh.lockの正体と、今すぐできる対処法を整理した。

エンジニアのゆとです。

Windowsでclaude -pを使った一括処理を組んでいる人から、Xで「急に全部のリクエストが同じエラーで落ちるようになった」という報告を見かけて、内容を追ってみた。1回だけの失敗ならまだしも、48回実行したうちの全部が同じメッセージで死んでいたらしい。しかも180秒待って再試行しても直らず、直せたのは「隠しフォルダを手で1個消す」という、およそエラーメッセージの案内とは違う方法だった。

原因を辿ると、Windows版Claude Codeの認証情報まわりに前からある構造的な弱さに行き着く。しかも今回の不具合、3ヶ月前に一度「直った」ことになっていたバグと同じ現象らしい。

何が起きたか — 48回中48回、同じエラーで失敗する

github.com
[BUG] Windows: .oauth_refresh.lock directory survives a mid-refresh exit and is never reclaimed despite stale: 60000 2026年9月17日に報告されたIssue。stale判定の設定値が入っているのに、ロックが一度も解放されない不具合を扱っている。

報告者の環境は、claude -pを1回あたり最大3並列・合計48回まわすバッチ処理。ある日、実行したリクエストが軒並み次のエラーで落ちた。

Failed to refresh OAuth token: another Claude Code process is refreshing it or exited mid-refresh.
This is usually transient; retry in a minute, and if it persists close other Claude Code windows.

「一時的なものだから1分待って再試行してくれ」という案内どおりに180秒待って再実行しても、結果は同じ。エラーの発生時、%USERPROFILE%\.claude\.oauth_refresh.lockを確認すると、中身が空のディレクトリとして存在していた。認証情報ファイル(.credentials.json)の最終更新は2日前で、そろそろトークンのリフレッシュが走ってもおかしくないタイミングだった。

このディレクトリを手動で削除したところ、次のclaude -pは3.3秒で成功し、その日の残り約200回の呼び出しでは再発しなかった。

# 原因のロックディレクトリを確認
Get-Item $HOME\.claude\.oauth_refresh.lock | Select-Object Mode, LastWriteTime

# 手動で削除すると即座に解消する
Remove-Item $HOME\.claude\.oauth_refresh.lock

「隠しディレクトリを1個消したら直った」というだけなら単発のトラブルで済む話だ。だが、報告者がバイナリの中身まで追ったことで、これが単純な一時障害ではなく、公式が用意した自動復旧の仕組みそのものが機能していないという話だとわかった。

stale: 60000という設定は入っている。なのに解放されない

Claude Codeが配布しているバイナリの文字列を抜き出すと、このロックの挙動を決める設定がそのまま出てくる。報告者は不具合が起きた2.1.272と、報告時点の最新版2.1.274の両方でこの値が同一であることを確認している。

{
  lockfilePath: <configDir>/.oauth_refresh.lock,
  realpath: false,
  stale: 60000,
  update: 5000,
  onCompromised: (err) => log(`OAuth refresh lock compromised: ${err.message}`, { level: 'error' })
}

stale: 60000は「ロックのタイムスタンプが60秒以上更新されていなければ、次に取得しようとしたプロセスがそのロックを奪い取ってよい」という意味の設定だ。今回のケースでは、48回のリクエストは数分おきに実行される短命なclaude -pの子プロセスなので、どのプロセスから見てもロックの経過時間は60秒をとっくに超えている。理屈のうえでは、最初に踏んだプロセスがロックを奪い取って処理を続けられるはずだった。

報告者は、外からは切り分けられない仮説を2つ挙げている。

  • 実際にエラーを出しているのは、この設定を持つロックとは別に存在する「レガシー版」の${credentialsPath}.lockで、ELOCKEDを検知すると即座に例外を投げる実装になっており、新しい方のstale設定がそもそも効く経路を通っていない
  • ロックの「stale判定による経過時間チェック」自体が、Windows特有の「ディレクトリを1個作ることでロックの代わりにする」という実装(mkdir-as-mutexパターン)には適用されていない

どちらが正しいにせよ、公式のドキュメントやエラーメッセージが約束している「1分待てば自動で直る」が、少なくともこのケースでは成立していなかったことになる。

実は6月に一度「直った」ことになっていたバグ

ここが今回のIssueを個別のバグ報告で終わらせたくないと思った理由で、報告者自身が過去のIssueとの関係を明示している。

github.com
Windows: stale lock directories block credential persistence after abnormal exit 2026年6月27日にクローズ済みのIssue。Windows版Claude Codeが.oauth_refresh.lockを含む複数のロックをディレクトリとして作り、異常終了後に残り続ける不具合を扱っていた。

このIssueが報告していた症状は今回とは違う形をしていた。Claude Codeを強制終了すると、.oauth_refresh.lock・.storage-write.lock・history.jsonl.lockがディレクトリとして残留し、その後/loginを実行すると画面上は「Login successful」と出るのに、実際には.credentials.jsonが書き込まれず、ステータスバーが即座に「Not logged in」に戻ってしまうというものだった。原因は同じ「ディレクトリ形式のロックが異常終了後に片付かない」現象で、当時の回避策も同じく手動削除だった。

find ~/.claude -maxdepth 1 -name "*.lock" -type d -exec rm -rf {} +

このIssueは6月末にクローズされている。だが今回のIssueは、同じ「ディレクトリ形式のロックが片付かない」という土台の上に、症状だけを変えて9月に再浮上したケースだ。今回は/loginの表示不整合ではなく、claude -pを使った自動化・バッチ処理という、対話画面を見ていないと気づけない場面で牙を剥いた。クローズされたIssueが「直った」と評価された時に検証されていたのがおそらく対話的な/loginのシナリオだけで、ヘッドレス実行の経路までは踏んでいなかったのではないか、というのが自分の見立てだ。

根っこにあるのは「Windowsの認証情報ストアは並行アクセスに弱い」という構造

さらに視野を広げると、Windows版のOAuthリフレッシュ周りには、今回とは別口の競合バグも報告されている。

github.com
Windows/VSCode: concurrent sessions race on OAuth refresh against the file credential store (400 → forced re-login) 2026年9月3日に報告。Windowsのファイルベース認証情報ストアを使う環境で、複数セッションが同時にトークンをリフレッシュしようとして競合し、片方が強制ログアウトさせられる不具合。

こちらはmacOSのKeychainストアで先に報告されていた同種の不具合(2026年5月に一度クローズ)が、Windowsのプレーンテキスト認証情報ストア(.credentials.jsonを複数プロセスで直接共有する方式)でも同じ形で起きることを指摘したものだ。OAuthのアクセストークンは有効期限の5分前から「そろそろ更新すべき」と判定される仕組みになっているが、そのタイミングの重複を防ぐロックはプロセス内にしかなく、プロセス間では効かない。リフレッシュトークンは使うたびに新しいものへローテーションする設計なので、最初にリフレッシュへ到達したプロセスが勝ち、他のプロセスは「すでに使用済みのリフレッシュトークン」を提示することになる。これがOAuthの仕様上「トークンの再利用」とみなされ、トークンファミリー全体が無効化される——つまり1回のリクエストの負けが、27日間有効なリフレッシュトークンそのものを吹き飛ばす。

claude -pを並列実行するという使い方は、まさにこの「複数プロセスが同時に認証情報を触る」状況そのものだ。今回のロック解放漏れのIssueと、この競合バグのIssueは別々に報告されているが、Windowsでの複数プロセス運用における認証まわりが全体的に脆いという同じ根っこを指しているように見える。

ちなみに今回報告されている経路はネイティブWindows環境(PowerShell・npm globalインストール)の話で、WSL2上でLinux側の認証情報ストアを使うように環境を組んでいる場合は事情が変わってくる可能性がある。WSLとネイティブWindowsのどちらで動かすかを迷っている場合は、環境ごとの違いを比較した別記事も参考にしてほしい。

Claude Code Windows完全ガイド 2026 — WSL・Git Bash・PowerShell徹底比較
Claude Code Windows完全ガイド 2026 — WSL・Git Bash・PowerShell徹底比較Claude CodeをWindowsで使う3つの方法(WSL/Git Bash/PowerShell)を徹底解説。インストール手順・パフォーマンス比較・トラブルシュート・各環境の使い分けを2026年最新情報で網羅。読む →
Claude Codeにログインできない・認証エラーが出る——症状別に公式ドキュメントで対処法を整理した
Claude Codeにログインできない・認証エラーが出る——症状別に公式ドキュメントで対処法を整理したClaude CodeのOAuthログインが失敗する、403 Forbiddenが出る、WSL2/SSHでログインできないといった症状別に、公式トラブルシューティングドキュメントをベースに原因と対処法を整理した。読む →

今すぐできる対処法

原因の切り分けはAnthropic側の対応待ちになるが、claude -pをWindowsでバッチ運用している人が今日からできる手当てはいくつかある。

1. ロックディレクトリの存在と経過時間を監視してから実行する

エラーが起きてから手動で消すのではなく、バッチ処理を起動する直前にロックの状態を確認し、明らかに古い場合は先回りして消してしまう方法。今回のケースでは60秒どころか2日近く放置されていたので、安全マージンを大きめに取っても実害は小さい。

$lockPath = "$HOME\.claude\.oauth_refresh.lock"
if (Test-Path $lockPath) {
    $age = (Get-Date) - (Get-Item $lockPath).LastWriteTime
    if ($age.TotalSeconds -gt 120) {
        Write-Warning "stale oauth_refresh.lock (age: $($age.TotalSeconds)s) を削除します"
        Remove-Item $lockPath -Recurse -Force
    }
}
claude -p "実行したいプロンプト"

120秒という閾値は、Issueに出てくるstale: 60000(60秒)に少し余裕を持たせた値にしている。実行中の別プロセスを誤って巻き込まないよう、必ず「バッチの起動直前」に1回だけ実行する運用にしておきたい。

2. リトライにバックオフと上限を入れる

エラーメッセージの「1分待って再試行」は今回のケースでは効かなかったが、原因がタイミングの偶発的な競合である可能性も残っているので、リトライ自体は無駄ではない。ただし今回のように「待っても直らない」パターンがある以上、無限リトライにはしない。

$maxRetries = 3
for ($i = 0; $i -lt $maxRetries; $i++) {
    $result = claude -p "実行したいプロンプト" 2>&1
    if ($LASTEXITCODE -eq 0) { break }
    if ($result -match "another Claude Code process is refreshing") {
        Write-Warning "oauth refreshロック競合を検知。ロックを確認して次のリトライへ"
        Start-Sleep -Seconds 15
    } else {
        break
    }
}

3. 並列数を絞る、もしくは直列実行に倒す

根本原因が「複数プロセスが同時に認証情報へアクセスする」ことである以上、一番手堅い回避策は同時実行数を減らすことだ。今回の報告も3並列という決して多くない条件で発生している。バッチ処理のスループットとのトレードオフになるが、認証まわりの不具合で処理全体が止まるコストと比べると、並列数を1〜2に絞る方が結果的に安定することが多い。

Claude Codeのスケジュール実行・定期実行ガイド — /scheduleコマンド・/loop・cronの使い分け【2026年版】
Claude Codeのスケジュール実行・定期実行ガイド — /scheduleコマンド・/loop・cronの使い分け【2026年版】Claude Codeで定期実行・スケジュール実行する方法を3パターン解説。/scheduleコマンドでクラウドcron、/loopでセッション内ポーリング、LaunchAgent/systemdでローカルcron。cron式の書き方とユースケース別の選び方も紹介。読む →

4. claude -pの失敗を握りつぶさない

claude -pの呼び出し元スクリプトが、認証エラーによる失敗を「1件のタスク失敗」として握りつぶしてリトライキューに戻すだけの実装になっていると、この不具合が起きても気づかないまま処理が延々と滞留し続ける。エラーメッセージの文字列(another Claude Code process is refreshing)を明示的に検知して、通常のタスク失敗とは別扱いでログに残すか、Slack通知などで即座に気づける仕組みにしておくといい。

まとめ

Windows版Claude Codeでclaude -pを使ったバッチ処理・自動化を組んでいる場合、another Claude Code process is refreshing it or exited mid-refreshというエラーで一斉に失敗することがある。原因は.oauth_refresh.lockディレクトリがstale: 60000という自動解放の設定を持ちながら、実際には解放されない不具合で、%USERPROFILE%\.claude\.oauth_refresh.lockを手動削除すれば即座に復旧する。

このディレクトリ形式のロックが片付かないという症状自体は6月に一度クローズされたIssueと同じ土台のもので、対話的な/loginのシナリオでは検証されても、claude -pのヘッドレス実行という別の経路では再発していた可能性が高い。さらに視野を広げると、Windowsの認証情報ストアが複数プロセスからの同時アクセスに弱いという、もう一段大きい構造的な課題も別のIssueで並行して報告されている。

対応が入るまでは、ロックの経過時間を見て先回りで削除する・リトライに上限を設ける・並列数を絞る、といった運用側の防御を組んでおくのが現実的な対処になる。

github.com
anthropics/claude-code Issues 今回のIssue群の続報や、同じ症状の報告を探すならここ。
← 記事一覧に戻る