Claude Media
「cannot be safely resolved」の原因と対処 — Claude Code

「cannot be safely resolved」の原因と対処 — Claude Code

worktreeで隔離されたClaude Codeのセッションが、シンボリックリンク経由の書き込みをブロックする理由と直し方を解説します。

Claude Codeでファイルの書き込みやコマンド実行が「path is spelled in a form that cannot be safely resolved」で止まることがあります。これはworktreeで隔離されたセッションが、シンボリックリンクなど解決しきれないパスを経由して共有チェックアウトへ書き込むのを防ぐガードの動作です。エラーの全文はClaudeにも渡り、Claudeはメッセージが示す直接パスで再試行します。通常はこちらで何もしなくて済み、同じファイルで繰り返すときだけ後半の切り分けが必要です。

ブロックされたとき、裏で何が起きているか

画面に出るのは、worktreeの隔離ガードが「このパスの行き先を1か所に決められない」と判断した、という結果です。ガードは操作の前にシンボリックリンクを解決し、行き先が共有チェックアウトに届かないことを確かめます。解決そのものに失敗した場合は、通すのではなく操作を止めます。

実際のメッセージは次の内容です。

コマンド実行がブロックされた場合も原因は同じで、対象が作業ディレクトリになります。メッセージの末尾は「re-run the command from its direct symlink-free path」に変わります。

手順

ブロックから再試行までの流れ

  1. 1

    Claudeがパスを指定する

    ファイルの編集先、またはコマンドの作業ディレクトリとして、シンボリックリンクなどを含むつづりのパスを使います。

  2. 2

    ガードがリンクを解決する

    行き先が共有チェックアウトに届かないかを調べます。解決に失敗すると、その操作は実行されません。

  3. 3

    全文がツールエラーとしてClaudeに返る

    メッセージには、worktreeのパスと、使うべき直接パスの形が入っています。

  4. 4

    Claudeが直接パスで再試行する

    多くの場合はここで解消し、人が介入する場面はありません。

ファイル編集がブロックされたときの会話画面には、短い Error editing file の1行しか出ません。全文を読みたいときは Ctrl+O でトランスクリプト表示を開きます。コマンドがブロックされた場合は、コマンドの出力にそのまま全文が出ます。

どの場面でこのガードが働くか

ガードは、worktreeに隔離されているセッションにかかります。claude --worktree で始めたセッション、Claudeが EnterWorktree で入ったセッション、worktreeのセッションを再開したときが同じ扱いです。対話セッションかバックグラウンドかは問われません。

そのセッションが起動するサブエージェントも同じ検査を受けます。isolation: worktree で独自のworktreeを持つサブエージェントも対象です。v2.1.287の claude --help には、-w, --worktree [name] が「Create a new git worktree for this session」として載っています。

バックグラウンドセッションは、少し事情が違います。claude --bg やagent viewから起動したセッションは、まず作業ディレクトリで始まります。ファイルを編集する前に、.claude/worktrees/ 以下の隔離されたgit worktreeへ自分で移ります。隔離が始まるのはこの移動の後で、ガードもそこから働きます。

次の場合、Claudeはworktreeに移りません。

  • ← や /background で、すでに開いていたセッションをバックグラウンドへ移したとき
  • セッションがすでにリンク済みのgit worktreeの中にあるとき(git worktree add で別の場所に作ったものも含む)
  • 編集対象のファイルが、リンク済みのgit worktreeの中にあるとき
  • 作業ディレクトリがgitリポジトリではなく、WorktreeCreate フックも設定されていないとき
  • 書き込み先が作業ディレクトリの外にあるとき

「うちのセッションでは出ない」という場合は、この一覧のどれかに当たっているかもしれません。

同じガードが持つ4つの検査

この仕組みは、パスの解決だけを見ているわけではありません。隔離中のセッションには、次の4種類の検査がかかります。

検査

隔離ガードの4つの検査

  • ファイル編集

    Edit / Write / NotebookEdit が共有チェックアウトのパスを対象にしたときに止めます。この記事のエラーはここで出ます。

  • コマンドの作業ディレクトリ

    Bash / PowerShell / Monitorの作業ディレクトリが共有チェックアウトに解決されるとき、または外にあると確かめられないときに止めます。これも、この記事のエラーに当たります。

  • gitの向き先の変更

    git -C、--git-dir、GIT_DIR や GIT_WORK_TREE、共有チェックアウトへの cd でgitを向け直すコマンドを止めます。

  • コマンドの形

    実行時に初めて決まる語(${!name} など)があり、gitが隔離の外に出ないと文面から確かめられないコマンドを止めます。この検査は無効にできません。

下の2つは別のエラーとして表に出ます。「too complex to verify that it stays inside the worktree」や「Refusing to run it」と書かれていたら、この記事の対象ではありません。作業ディレクトリでなくコマンドの書き方が問題なので、メッセージの最後は、コマンドを単純な別々のコマンドに分けて、worktreeの中から実行するよう求めます。繰り返し拒否されるときは、変数展開を値に置き換え、gitを単独のコマンドとして実行します。

PowerShellのコマンドに適用されるのは、作業ディレクトリの検査だけです。

どんな形のパスがブロックされるか

メッセージが例示するのは3パターンです。1つ目は、生の .. セグメントを保持したシンボリックリンク経由のパス。2つ目は、ネットワーク共有やデバイス名前空間の形をしたパス。3つ目は、途中の階層に読み取れないディレクトリを含むパスです。いずれも、たどった先が共有チェックアウトの外にあるかをガードが機械的に確かめきれない形です。

パターン具体例ガードが確かめられないこと
ドットセグメント付きシンボリックリンク具体例docs/current -> ../README.mdガードが確かめられないことリンクの先が共有チェックアウトの外に留まるかどうか
ネットワーク共有・デバイス名前空間具体例\\server\share\file、/net のautomountパスガードが確かめられないこと共有チェックアウトの外に留まるかどうか
読み取れない祖先ディレクトリ具体例途中の階層のディレクトリが読めないガードが確かめられないことそこから先が安全な範囲かどうか

3つ目の行について、メッセージ上の呼び名は「unreadable ancestor directory」です。

コミットされたリンクは、git ls-files -s で見ると種別が 120000 として並びます。一時ディレクトリで docs/current と docs/readme-link を ../README.md へのリンクとしてコミットし、実際に出た結果が次の出力です。

git ls-files -s | awk '$1 == 120000'
# 120000 32d46ee883b58d6a383eed06eb98f33aa6530ded 0	docs/current
# 120000 32d46ee883b58d6a383eed06eb98f33aa6530ded 0	docs/readme-link

2つのリンクは同じ内容(同じハッシュ)で、Gitの中では「リンク先のパスを書いた小さな文字列ファイル」として保存されています。この文字列に .. が入っているものが、1つ目のパターンに当たります。

ネットワーク共有の形は別のメッセージでも出ます

ネットワーク共有の形は、先ほどの原因文のかっこ内にも出てきますが、独立した文面でも出ます。

This write was blocked because the path is network-shaped (a UNC share or /net automount spelling) while this session's checkout is local. Isolating cannot unblock it.

訳すと、UNC共有や /net のautomountのつづりのパスだが、このセッションのチェックアウトはローカルだ、という内容です。ローカルにないドライブを指すパスも、同じ文面で止まります。「Isolating cannot unblock it」とあるとおり、隔離しても解消しません。メッセージの末尾は、ファイルがworktreeの中に本当にあるなら「local, plainly-spelled path」(ローカルの素直なつづりのパス)で指定するよう求めます。コマンドの場合は「re-run the command from its local, plainly-spelled path」で終わります。

くらべる

2つのメッセージの見分け方

解決できない

cannot be safely resolved

リンク・デバイス名前空間・読めない祖先ディレクトリのどれかで、行き先が決まらない。

ネットワークの形

network-shaped

UNC共有や /net のつづりのパスで指している。

なぜこのガードが必要なのか

Claude Codeのバックグラウンドセッションは、同じチェックアウトを複数のセッションが同時に読みつつ、書き込みだけは各自のworktreeに閉じ込める作りです。並列実行中の競合を防ぐためです。この隔離が成り立つには、「このパスは自分のworktreeの中か、それとも共有チェックアウトに届くか」を正確に判定できなければなりません。

シンボリックリンクやネットワークパスは、見た目のパス文字列と実際の行き先が一致しないため、文字列の比較だけでは判定を誤ります。解決に失敗するケースは、「分からないので通さない」という安全側の判断でブロックされます。

あゆみ

v2.1.217の前後でガードが変わった点

  1. v2.1.217より前つづりだけを比べる

    シンボリックリンクやUNC・/net のつづりでも止まらず、リンク経由の書き込みが共有チェックアウトに届くことがありました。

  2. v2.1.217以降解決してから判定する

    リンクを解決し、行き先を確かめられなければ止めます。このときに出るのが「cannot be safely resolved」と「network-shaped」です。

以前は通っていた操作が、更新後に初めて止まることがある、ということです。「昨日まで書けたファイルに書けない」という状況では、この変更が原因候補になります。

同じファイルで繰り返すときの切り分け

Claudeが直接パスで再試行しても繰り返す場合は、対象ファイルの経路がコミット済みのシンボリックリンクを通っていて、そのリンク先に .. が含まれている可能性が高いです。たとえば docs/current -> ../README.md のようなリンクです。

メッセージが「network-shaped」なら、UNC共有や /net のパスで指していないかを見て、ローカルの素直なパスで指し直します。「cannot be safely resolved」なら、リポジトリ内のコミット済みリンクを一覧し、リンク先に .. を含むものが対象ファイルの経路にないか調べます。

この確認には、git ls-files -s が向いています。コミット済みのリンクだけを拾えるためです。

# コミット済みのシンボリックリンクだけを列挙し、リンク先を表示する
for f in $(git ls-files -s | awk '$1 == 120000 {print $4}'); do
  echo "$f -> $(readlink "$f")"
done
# docs/current -> ../README.md
# docs/readme-link -> ../README.md

find . -type l -not -path "./.git/*" でも、作業ツリー上のリンクを洗い出せます。こちらは未コミットのリンクも拾うので、リンクの出どころがコミットかどうかを区別したいときは git ls-files -s のほうが絞り込めます。

リンク先に .. が含まれていれば、それが原因の候補です。Claudeには、リンクを経由せず実体ファイルの直接パスを編集するよう指示します。

worktree隔離の設定との関係

worktreeによる隔離そのものは、worktree.bgIsolation 設定で切り替えられます。デフォルトの "worktree" では、EnterWorktree ツールを呼ぶまで、共有チェックアウトへの Edit と Write がブロックされます。"none" にすると、バックグラウンドジョブは作業コピーを直接編集するようになります。

"none" を選ぶと、バックグラウンドセッションはworktreeに移りません。ガードは隔離中のセッションにかかる検査なので、隔離に入らないセッションでは働きません。ただし、サブエージェントに isolation: worktree を付けた場合は話が別です。そのサブエージェントは自分のworktreeを持ち、同じ検査を受けます。

"none" は、gitのworktreeを使いにくいリポジトリで選ぶ設定です。プロジェクトの .claude/settings.json に書きます。

{
  "worktree": {
    "bgIsolation": "none"
  }
}

設定の全項目はClaude Code settings.json完全ガイドにまとめています。

サブエージェントを独自のworktreeに隔離したいときは、frontmatterに isolation: worktree を書くか、起動時に isolation: "worktree" を渡します。worktree運用全体の考え方はClaude Code Worktree実践ガイドで解説しています。

worktree.symlinkDirectoriesとの切り分け

設定名が似ているため、worktree.symlinkDirectories と混同しないようにします。この設定は、node_modules や .cache のような大きなディレクトリを、各worktreeに複製せず、メインリポジトリからシンボリックリンクとして張るためのものです。デフォルトでは、どのディレクトリもリンクされません。

{
  "worktree": {
    "symlinkDirectories": ["node_modules", ".cache"]
  }
}

この設定が張るのは、worktreeの作成時にClaude Codeが置くリンクです。コミット済みのリンクとは出どころが違います。「cannot be safely resolved」で疑うのは、リポジトリにコミットされていて、リンク先に .. を含むリンクのほうです。

まとめ

1回目のブロックはClaudeの再試行で収まるので、そのままで構いません。同じファイルで繰り返すときに初めて、コミット済みのリンクを疑います。古いバージョンから上げた直後に出始めたなら、v2.1.217のガード変更が原因候補です。

この記事を共有:XはてブLinkedIn