はじめに
Claude Code 2.1.310 では、Git リポジトリのサブディレクトリから claude mcp add を実行すると、最後に次の行が出ます。
File modified: <HOME>/.claude.json [project: <HOME>/repo/sub/deep]
<HOME> は、検証で使った一時ディレクトリのパスを伏せたものです。この表示を素直に読むと、MCP サーバーが repo/sub/deep 専用のプロジェクトエントリに登録されたように見えます。2.1.311 の CHANGELOG には、この表示に関わる修正が入りました。
そこで気になるのが、2.1.310 で登録したサーバーは実際にはどこに入っていたのか、という点です。直ったのが表示だけなら手元の設定はそのままでよく、保存先まで変わったのなら登録し直す必要が出てきます。この問いに答えるため、2.1.310 と 2.1.311 を同じ手順で動かし、出力と ~/.claude.json の中身を突き合わせました。手順は本物の設定ファイルに触れない形にしてあるので、別のバージョン同士の比較にも流用できます。
対象読者は、Claude Code を日常的に使い、claude mcp で MCP サーバーを管理している開発者です。後半の claude self-hosted-runner の新オプションは、このサブコマンドを使っている方向けの補足として置いています。
この記事で再現できること
- 旧版の Claude Code を一時ディレクトリに入れ、現行版と呼び分ける
-
HOMEを一時ディレクトリに差し替え、本物の~/.claude.jsonを汚さずにclaude mcpを試す - 2.1.310 と 2.1.311 で、
claude mcpの出力と実際の保存先を比べる
前提環境
検証は 2026-10-27 に、クラウド上の Linux コンテナ(Linux 6.18・bash)で行いました。
- 2.1.311: 環境に最初から入っていた
claudeを使いました。claude --versionの出力は2.1.311 (Claude Code)で、npm view @anthropic-ai/claude-code versionも2.1.311を返しました - 2.1.310: 一時ディレクトリに
npm install @anthropic-ai/claude-code@2.1.310で入れました。node_modules/.bin/claude --versionの出力は2.1.310 (Claude Code)です - 実行したのは、認証も API 呼び出しも要らないコマンド(
claude mcp ...とclaude self-hosted-runner --help)だけです
TL;DR
- 旧版を npm で一時ディレクトリに入れ、スクリプトの中だけ
HOMEを差し替えると、2 つの版を本物の設定に触れずに同じ手順で比べられます - 2.1.311 では、
claude mcp listとclaude mcp getの失敗行でENOENT: ENOENT:と重なっていたエラーコードが 1 回になりました。サブディレクトリからmcp add/mcp removeしたときの[project: …]も、リポジトリ直下を指すようになっています - 2.1.310 の
[project: <HOME>/repo/sub/deep]は表示のずれでした。保存先は両方の版ともリポジトリ直下のエントリで、今回の手順の範囲では登録し直す必要は見当たりません
2.1.311 の変更点のうち、この記事で確かめたもの
公式の CHANGELOG の ## 2.1.311 節(2026-10-27 取得)から、実機で確かめた 3 項目を原文のまま抜き出します。なお、2.1.311 の公開日は CHANGELOG に書かれていません。
| CHANGELOG の記載(原文) | 確かめ方 | 結果 |
|---|---|---|
Fixed claude mcp list and claude mcp get showing the error code twice on failed-connection lines (for example ENOENT: ENOENT: …) |
実験 A | 確認済み |
Fixed claude mcp add, claude mcp remove and /mcp naming the current folder instead of the project entry in ~/.claude.json that holds a local-scope server when run below a repository root |
実験 A |
mcp add と mcp remove は確認済み(/mcp は未実行) |
Added --hold-idle-file and --max-hold-min to self-hosted-runner, to keep an idle session while your host reports processes it left running |
実験 B |
--help への追加は確認済み(実運用は未検証) |
1 つ目は、接続に失敗したサーバーの行でエラーコードが 2 回表示されていた件の修正です。2 つ目は少し読みにくい文ですが、「リポジトリのルートより下で実行したとき、local スコープのサーバーを保持している ~/.claude.json のプロジェクトエントリではなく、現在のフォルダを名指ししていた」件の修正です。どちらの文も保存先の変更には触れていないため、挙動は出力とファイルの両方を見ないと判断できません。
実験 A は claude mcp の失敗行と [project: …] 表示、実験 B は claude self-hosted-runner --help の差分です。CHANGELOG のそれ以外の項目は動作を確かめていないので、後半の別表に分けました。
手順
全体の流れは次のとおりです。2 つの版で同じスクリプトを流し、出力と ~/.claude.json の projects を並べます。
1. 旧版を一時ディレクトリに入れる
グローバルに入っている claude には手を付けず、比べたい旧版だけを使い捨てのディレクトリに入れます。
OLD_DIR="$(mktemp -d)"
(cd "$OLD_DIR" && npm install @anthropic-ai/claude-code@2.1.310)
CLAUDE_OLD="$OLD_DIR/node_modules/.bin/claude"
"$CLAUDE_OLD" --version # 2.1.310 (Claude Code)
claude --version # 2.1.311 (Claude Code)
検証環境では、グローバルの claude が最初から 2.1.311 でした。手元の版が違う場合は、2.1.311 も同じ要領で別の一時ディレクトリに入れ、CLAUDE_NEW のような変数に絶対パスで持たせると取り違えません。
2. HOME を差し替える比較スクリプトを書く
claude mcp add は、スコープを指定しないと local config に登録し、~/.claude.json を書き換えます。今回の出力でも to local config と File modified: <HOME>/.claude.json が表示されました。本物の設定を汚さないよう、スクリプトの中でだけ HOME を新しい一時ディレクトリに向けます。
#!/usr/bin/env bash
# 使い方: bash compare-mcp.sh <claude の実行ファイルの絶対パス>
set -u
CLAUDE_BIN="$1"
export HOME="$(mktemp -d)" # 実行のたびに空の HOME から始める
echo "HOME=$HOME"
"$CLAUDE_BIN" --version
mkdir -p "$HOME/repo/sub/deep" && cd "$HOME/repo" && git init -q
"$CLAUDE_BIN" mcp add broken -- /nonexistent/mcp-server # リポジトリ直下で登録
cd sub/deep
"$CLAUDE_BIN" mcp add broken2 -- /nonexistent/server2 # サブディレクトリから登録
"$CLAUDE_BIN" mcp list
"$CLAUDE_BIN" mcp get broken
"$CLAUDE_BIN" mcp remove broken2
# 実際の保存先を見るため、~/.claude.json の projects をそのまま表示する
python3 - <<'PY'
import json, os
path = os.path.join(os.environ["HOME"], ".claude.json")
print(json.dumps(json.load(open(path))["projects"], indent=2, ensure_ascii=False))
PY
ポイントは 3 つあります。
-
export HOME=...はスクリプトのプロセスの中でしか効きません。実行が終われば、呼び出し元のシェルのHOMEは元のままです -
git initを入れているのは、CHANGELOG の修正が「リポジトリのルートより下で実行したとき」の話だからです。repo/sub/deepがリポジトリのサブディレクトリになるように作ります -
/nonexistent/mcp-serverと/nonexistent/server2は存在しないパスです。わざと起動に失敗するサーバーを登録し、失敗行の表示を比べます
jq が入っている環境なら、Python の部分は jq '.projects' "$HOME/.claude.json" に置き換えても構いません。
3. 2 つの版で同じスクリプトを流す
bash compare-mcp.sh "$CLAUDE_OLD" > out-2.1.310.txt 2>&1
bash compare-mcp.sh "$(command -v claude)" > out-2.1.311.txt 2>&1
一時ディレクトリのパスは実行ごとに違うので、そのまま diff を取ると File modified: 行などがすべて差分になります。HOME= 行に出たパスを <HOME> に置き換えてから比べると、パスの違いを除いた差分だけを見られます。
for v in 2.1.310 2.1.311; do
h="$(sed -n 's/^HOME=//p' "out-$v.txt")"
sed "s#$h#<HOME>#g" "out-$v.txt" > "norm-$v.txt"
done
diff norm-2.1.310.txt norm-2.1.311.txt
結果を並べる
以下の出力は claude mcp のコマンド部分だけを抜き出し、一時ディレクトリのパスを <HOME> に置き換えたものです。カッコ書きは、どこで実行したかの注記です。
2.1.310 の出力
$ claude mcp add broken -- /nonexistent/mcp-server (リポジトリ直下)
Added stdio MCP server broken with command: /nonexistent/mcp-server to local config
File modified: <HOME>/.claude.json [project: <HOME>/repo]
$ claude mcp add broken2 -- /nonexistent/server2 (repo/sub/deep から)
Added stdio MCP server broken2 with command: /nonexistent/server2 to local config
File modified: <HOME>/.claude.json [project: <HOME>/repo/sub/deep]
$ claude mcp list
Checking MCP server health…
broken: /nonexistent/mcp-server - × Failed to connect — ENOENT: ENOENT: no such file or directory, posix_spawn '/nonexistent/mcp-server'
broken2: /nonexistent/server2 - × Failed to connect — ENOENT: ENOENT: no such file or directory, posix_spawn '/nonexistent/server2'
$ claude mcp get broken
broken:
Scope: Local config (private to you in this project)
Status: × Failed to connect
Issue: ENOENT: ENOENT: no such file or directory, posix_spawn '/nonexistent/mcp-server'
Type: stdio
Command: /nonexistent/mcp-server
Args:
Environment:
To remove this server, run: claude mcp remove broken -s local
$ claude mcp remove broken2 (repo/sub/deep から)
Removed MCP server "broken2" from local config
File modified: <HOME>/.claude.json [project: <HOME>/repo/sub/deep]
2.1.311 の出力
$ claude mcp add broken -- /nonexistent/mcp-server (リポジトリ直下)
Added stdio MCP server broken with command: /nonexistent/mcp-server to local config
File modified: <HOME>/.claude.json [project: <HOME>/repo]
$ claude mcp add broken2 -- /nonexistent/server2 (repo/sub/deep から)
Added stdio MCP server broken2 with command: /nonexistent/server2 to local config
File modified: <HOME>/.claude.json [project: <HOME>/repo]
$ claude mcp list
Checking MCP server health…
broken: /nonexistent/mcp-server - × Failed to connect — ENOENT: no such file or directory, posix_spawn '/nonexistent/mcp-server'
broken2: /nonexistent/server2 - × Failed to connect — ENOENT: no such file or directory, posix_spawn '/nonexistent/server2'
$ claude mcp get broken
broken:
Scope: Local config (private to you in this project)
Status: × Failed to connect
Issue: ENOENT: no such file or directory, posix_spawn '/nonexistent/mcp-server'
Type: stdio
Command: /nonexistent/mcp-server
Args:
Environment:
To remove this server, run: claude mcp remove broken -s local
$ claude mcp remove broken2 (repo/sub/deep から)
Removed MCP server "broken2" from local config
File modified: <HOME>/.claude.json [project: <HOME>/repo]
違い 1: 失敗行のエラーコードが 1 回になった
2.1.310 では、mcp list の失敗行と mcp get の Issue: 行の両方で ENOENT: ENOENT: no such file or directory とエラーコードが 2 回並んでいます。2.1.311 では ENOENT: no such file or directory と 1 回だけです。CHANGELOG の「error code twice」は、この重なりを指しています。
今回わざと起こしたのは ENOENT(実行ファイルが見つからない)だけなので、ほかのエラーコードで同じ変化が起きるかは確かめていません。
違い 2: [project: …] がリポジトリ直下を指すようになった
File modified: 行の [project: …] を、実行した場所ごとに並べると次のようになります。
| 操作(実行した場所) | 2.1.310 の表示 | 2.1.311 の表示 |
|---|---|---|
mcp add broken(リポジトリ直下) |
<HOME>/repo |
<HOME>/repo |
mcp add broken2(repo/sub/deep) |
<HOME>/repo/sub/deep |
<HOME>/repo |
mcp remove broken2(repo/sub/deep) |
<HOME>/repo/sub/deep |
<HOME>/repo |
2.1.310 は、サブディレクトリから操作すると現在のフォルダを表示していました。2.1.311 では、今回試した 2 か所(リポジトリ直下と repo/sub/deep)のどちらから操作しても、リポジトリ直下のパスが出ます。CHANGELOG の「現在のフォルダを名指ししていた」は、この表示のことです。
保存先は両方の版で同じだった
表示が変わったからといって、保存先が変わったとは限りません。そこで mcp remove broken2 まで終えたあとの ~/.claude.json の projects を読みました。両方の版で結果は同じで、プロジェクトのパスと、そこに残っているサーバー名の対応に要約すると次のとおりです。
{'<HOME>/repo': ['broken']}
repo/sub/deep というプロジェクトエントリは、2.1.310 でも存在しませんでした。つまり 2.1.310 でも実際の保存先はリポジトリ直下のエントリで、[project: …/sub/deep] という表示だけが実態とずれていたことになります。
mcp get の末尾は両方の版で同じだった
mcp get の Args: と Environment: の行、削除の案内 To remove this server, run: claude mcp remove broken -s local は、2.1.310 にも 2.1.311 にも出ています。2.1.311 で増えた行ではありません。案内にある -s local はスコープの指定で、local スコープに登録したサーバーを削除するコマンドとして CLI 自身が示しているものです。今回の検証では、この形での削除は実行していません。
並べて分かったこと
実際に 2 つの版で同じ手順を流したところ、次の 3 点がはっきりしました。
1 つ目は、表示と保存先を分けて確かめる必要があることです。2.1.310 の File modified: 行だけを見ると、repo/sub/deep という実在しないエントリに書き込まれたと読めてしまいます。結論の決め手になったのは表示ではなく、~/.claude.json の projects を直接読んだ結果でした。保存先を確かめたいときは、表示よりファイルの中身を見るほうが確実です。
2 つ目は、2.1.310 で登録した local スコープのサーバーを移し替える必要は、今回の手順の範囲では見当たらないことです。保存先は 2.1.310 の時点でリポジトリ直下のエントリだったので、2.1.311 に上げても置き場所は変わりません。2.1.310 の表示を信じて ~/.claude.json を手で編集すると、存在しないエントリを探すことになります。
3 つ目は、失敗行の文字列そのものが変わることです。claude mcp list の出力を文字列で照合しているスクリプトがあれば、ENOENT: ENOENT: を前提にした照合は 2.1.311 から一致しなくなります。
比較の仕組みとしては、npm の一時インストールと HOME の差し替えを組み合わせるだけで足りました。どちらの版の File modified: 行も一時ディレクトリ配下の .claude.json を指しており、本物の ~/.claude.json は書き換えていません。
self-hosted-runner に増えた 2 つのオプション
確かめた 3 項目のうち、残る 1 つは self-hosted-runner へのオプション追加です。ランナーの実運用は検証環境にシークレットが無いため試せず、ここではヘルプの差分だけを確かめました。手順は次のとおりです。
"$CLAUDE_OLD" self-hosted-runner --help > help-2.1.310.txt
claude self-hosted-runner --help > help-2.1.311.txt
diff help-2.1.310.txt help-2.1.311.txt
2.1.311 のヘルプにだけある行を抜粋します。
--hold-idle-file <path> Do not release an idle session (--release-idle-session-min) while
<path> is a regular file changed in the last 180s: your host
keeps it fresh while a process the session left running (nohup,
tmux) lives. Looked at again every 30s. Does not hold a
session at startup, at --retire-at, at the --defer-shutdown-max-min
ceiling or at --kill-session-after-min. Default: off.
[env: SELF_HOSTED_RUNNER_HOLD_IDLE_FILE]
--max-hold-min <n> With --hold-idle-file: release the session anyway N min after it
went idle. Default: 1440. 0 disables the hold.
Max: 10080. [env: SELF_HOSTED_RUNNER_MAX_HOLD_MS, in ms]
日本語で整理すると次のようになります。
| オプション | ヘルプに書かれた役割 | 既定値・上限 | 環境変数 |
|---|---|---|---|
--hold-idle-file <path> |
<path> が直近 180 秒以内に更新された通常ファイルである間、アイドルのセッションを --release-idle-session-min で解放しない(30 秒ごとに見直す) |
既定はオフ | SELF_HOSTED_RUNNER_HOLD_IDLE_FILE |
--max-hold-min <n> |
--hold-idle-file と併用し、アイドルになってから N 分たったら保持をやめて解放する(0 で保持を無効にする) |
既定 1440 分・上限 10080 分 |
SELF_HOSTED_RUNNER_MAX_HOLD_MS(ミリ秒) |
ヘルプによると、ファイルを新しく保つのはホスト側の役目です。セッションが残したプロセス(nohup や tmux で起動したもの)が生きている間、ホストがファイルを更新し続け、その間はアイドルになってもセッションが解放されません(--max-hold-min の時間まで)。ただし起動時、--retire-at の時点、--defer-shutdown-max-min の上限、--kill-session-after-min の時点では保持しないと書かれています。
ヘルプを読んで気をつけたいのは、--max-hold-min の単位です。フラグは分で指定しますが、対応する環境変数 SELF_HOSTED_RUNNER_MAX_HOLD_MS はミリ秒です。既定の 1440 分(24 時間)を環境変数で書くなら 86400000 になり、上限の 10080 分は 7 日にあたります。フラグと環境変数を混ぜて設定するときは、桁を取り違えないようにしてください。
--agent-proxy-url の説明にも追記がありました。要旨は「--exec-path やコマンドフックで起動する claude がこのバージョン以降でないと、プロキシ経由のとき git と gh が失敗する(古いと、マシン自身のネットワークと認証情報を使う)」というものです。この追記を除くと、--release-idle-session-min など既存オプションの説明は diff に出ておらず、2.1.310 から変わっていません。
この節の内容はヘルプの記載にもとづくもので、ランナーの登録やセッションの実行で挙動を確かめたものではありません。
CHANGELOG にあるが、動作を確かめていない項目
以下は CHANGELOG の ## 2.1.311 節にある記載の一部です。動作は確認していません。 内容は要旨で載せているので、正確な文言は CHANGELOG の原文を見てください。
| CHANGELOG の記載(要旨) | 実機確認 |
|---|---|
SubagentStop フックが 1 分に約 2 回走っていた件の修正 |
していない |
| 1M コンテキスト不足のエラーで止まる代わりに、コンパクションして継続するようにした修正 | していない |
| Write / Edit が数千行の変更で最大 5 秒止まる件の修正 | していない |
CLAUDE_CODE_RETRY_WATCHDOG(無人リトライモード)で、API が「再試行不可」とした要求を再試行し続ける件の修正 |
していない |
| シェルコマンドが timeout に達したとき、出力を表示してバックグラウンドで継続中だと伝える改善 | していない |
これらはセッションを実際に動かさないと再現できないものが中心で、今回の「認証の要らないコマンドだけで並べる」手順の対象から外れています。
トラブルシューティング
Q1. 2.1.310 で [project: …/sub/deep] と表示されたら、設定を移す必要はありますか
今回の手順では、保存先は両方の版ともリポジトリ直下のエントリで、移し替えが要る状況は見当たりませんでした。手で編集する前に、~/.claude.json の projects を読んで実際のキーを確かめてください。スクリプトの Python 部分は、HOME を差し替えずに実行すれば本物の設定の確認にも使えます。
Q2. 失敗行で ENOENT: ENOENT: のようにコードが 2 回出る
2.1.310 の表示です。2.1.311 では 1 回になります。今回確かめたのは ENOENT だけです。
Q3. 比較のあと、本物の ~/.claude.json に broken が残らないか心配
スクリプトの中で HOME を差し替えているので、登録先はすべて一時ディレクトリの .claude.json です。出力の File modified: 行が一時ディレクトリを指していることで確かめられます。実行後に呼び出し元のシェルで echo "$HOME" を実行すれば、元のパスのままであることも分かります。mktemp -d で作ったディレクトリは自動では消えないので、不要になったら rm -rf "$OLD_DIR" と、出力の HOME= 行に出た一時ディレクトリを削除してください。
Q4. mcp get の Args: や削除の案内は 2.1.311 の新機能?
いいえ、両方の版で同じ内容が出ます。2.1.310 と 2.1.311 の出力全体を diff で比べても、この部分に差分はありませんでした。
おわりに
2.1.311 の claude mcp まわりの修正は、今回確かめた範囲では表示の修正でした。失敗行のエラーコードは 2 回から 1 回になり、サブディレクトリから操作したときの [project: …] はリポジトリ直下を指すようになっています。一方で ~/.claude.json の保存先は 2.1.310 の時点からリポジトリ直下のエントリで、版を上げても変わりません。
確かめられていないのは、対話セッションの /mcp、ENOENT 以外のエラーコード、self-hosted-runner の実運用、CHANGELOG のほかの項目です。compare-mcp.sh は渡す実行ファイルを替えるだけで別の版にも使えるので、次のリリースで claude mcp の挙動が気になったときは、旧版を一時ディレクトリに入れて同じように並べてみてください。
関連記事
- Claude Code 2.1.299入門、MCP許可ルールの一致範囲を実測した
- Claude Codeのハマりどころ14件、8件はドキュメント1行と食い違った
- Claude Codeのhookマッチャー、ハイフン名がv2.1.195で動かなくなった
参考リンク
-
Claude Code CHANGELOG(
## 2.1.311節・2026-10-27 取得。記事中の原文引用と「動作を確かめていない項目」の出典)