你在輸入框打一個 @,再打 src/comp,那零點幾秒裡,是誰去把那串檔案清單撈出來的?

設定參考答得很乾脆。內建的建議走的是 fast filesystem traversal,當場走訪檔案系統。沒有索引。

這件事拿抽屜來想最快。有人問你「那份合約在哪」,你從第一格抽屜開始翻,一格一格往下。抽屜只有五格的時候這是最快的做法,因為你連目錄都不用先做,做目錄的時間都夠你翻完了。抽屜長成一整面牆之後,同一招就開始拖。

官方文件自己把邊界寫出來了:a large monorepo may do better with project-specific indexing such as a pre-built file index。多大算大?文件一個檔案數、一個毫秒數都沒給,我也不補。

把這件事接到你自己那份目錄上

fileSuggestion 的型別小到一眼看完:一個物件,type 永遠是 "command"command 是要跑的 shell 命令。預設 unset,不設就是內建那份。

1
2
3
4
5
6
{
"fileSuggestion": {
"type": "command",
"command": "~/.claude/file-suggestion.sh"
}
}

契約也短。Claude Code 用跟 hooks 一樣的環境變數跑這條命令,CLAUDE_PROJECT_DIR 在裡面。stdin 餵進來一份 JSON,只有一個 query 欄位,裝的是使用者打到目前為止的字:

1
{"query": "src/comp"}

你往 stdout 印換行分隔的檔案路徑。文件寫著兩個硬數字:Claude Code shows at most 15,以及 stops waiting after five seconds。

順帶一提,@ 撈的已經不只是檔案。互動模式的文件寫著,在有跨 session 訊息的 session 裡,@ 後面打至少一個字母,Claude Code 還會把這台機器上你其他還活著的 session 一起列出來,讓你叫 Claude 去跟選中的那個傳話(需 v2.1.232 或更新)。而 fileSuggestion 換掉的是其中檔案那一份,文件的寫法是 supply @ file path autocomplete instead of the built-in file suggestion。

官方範例腳本是這樣:

1
2
3
4
#!/bin/bash
query=$(cat | jq -r '.query')
# Replace your-repo-file-index with your own file search command
your-repo-file-index --query "$query" | head -20

head -20 配上 15 筆的顯示上限,多出來那 5 行不會有人看到。範例本身就先講了一件事:排序的責任整個在你的腳本身上。你吐 200 行,被看到的永遠是最前面那一截,Claude Code 不會幫你重排。

範例裡那個 your-repo-file-index 是佔位用的,實際塞什麼由你決定,而手邊現成的選項不少。rg --files 吐出全部再自己篩、git ls-files 只認被版控追蹤的檔案、公司內部那套 code index 開一個查詢端點出來,都接得上。選哪一個,決定的其實是「哪些檔案算數」這件事。git ls-files 天生就把 node_modules 跟建置產物擋在門外,而那些通常正是你打 @ 的時候最不想看到的東西。

排序也一樣得自己想。query 遞給你的只是使用者打到一半的字串,要不要做模糊比對、要不要把最近改過的檔案往前拉、同名檔案誰優先,全在這支腳本裡決定。

契約短,代表沒得商量的地方也多。能回的只有路徑,沒有欄位可以夾帶說明文字、圖示或權重。輸入只有 query,沒有游標位置、沒有你剛才選過什麼、沒有這場對話在聊什麼。5 秒同樣是硬的:索引冷啟動要 8 秒,這條路就不通,得先讓它常駐或先預熱。至於 5 秒過了畫面上會是什麼,文件只寫到 stops waiting 就沒了,後面我不替它補。

有一個開關不在你手上

設定參考裡有一個小節,同時管 statusLinefileSuggestionsubagentStatusLine。三個鍵共用一套規則,而規則是兩個照順序做的決定。

第一道閘門是要不要整個關掉。managed settings 設了 disableAllHooks,關掉。這個資料夾沒被信任(跟設定檔裡的 hooks 走同一條工作區信任規則),也關掉。

第二道閘門是要不要收窄成只吃 managed settings。allowManagedHooksOnly 開著算一種;設定優先序跑完之後,disableAllHooks 在非 managed 的檔案裡是 true 算第二種;用 --safe-mode 把 Claude Code 起起來算第三種。

這裡有個容易讀漏的地方。disableAllHooks 在兩道閘門裡都出現了,位置不同,做的事也不同:放在 managed settings 裡,它讓整組功能整個關掉;放在非 managed 的檔案裡,它只是把來源收窄成 managed 那一份。同一個鍵名,換個檔案就換了語意。

收窄之後,公司有部署 managed 的值,那份會跑。沒有的話,文件的原話是 it skips your value without warning。狀態列被停掉,@ 的自動完成退回內建的檔案建議。

沒有紅字。沒有 exit code。沒有一行 log。

把這個失敗放回你桌前想一遍。你設好了 fileSuggestion,打 @,跳出一串檔案。清單有東西,只是不是你索引裡那一份。合理的下一步是懷疑自己:路徑寫錯?忘了 chmodjq 沒吃到 stdin?於是你開始一行一行讀那支腳本。

而那支腳本從頭到尾沒有被呼叫過。

我猜大部分人第一個懷疑的會是自己的實作,不是設定層——這只是推論,我手上沒有任何使用行為的統計。但方向上說得通:報錯會把你推向錯誤訊息,沒報錯只會把你推向自己最近改過的東西。

所以順序要倒過來。@ 沒照你的意思跑的時候,先驗閘門,再讀腳本。照文件列出的條件,要確認的是這幾件事:managed settings 裡有沒有 allowManagedHooksOnly、有沒有 disableAllHooks、這次 session 是不是用 --safe-mode 起來的、還有這個資料夾到底有沒有被信任過。全部清白了,才輪到你那支 shell script 上場。順序顛倒的代價很具體:你會在一支完全正確的腳本上,找一個根本不存在的錯。

為什麼它選擇不出聲

不警告看起來像疏忽,仔細看比較像刻意。

觸發收窄的那三種情境有個共同點。公司政策、--safe-mode、沒被信任的資料夾,在這些情境下「不要跑使用者提供的命令」本身就是要的行為,不是意外。對一個預期之內的行為發警告,發久了就變成每次啟動都要滑過去的雜訊,而雜訊的代價是真正該看的那一則也一起被跳過。所以它閉嘴。

代價落在另一邊,而且不對稱。坐在終端機前面的人沒辦法從畫面上分辨「我的設定被政策收走了」跟「我自己寫壞了」。這兩件事的修法完全相反,一個要去敲 IT 的門,一個要去讀自己的 shell script。

我的看法是這個取捨選錯邊了。不主動跳警告可以接受,但總該有一個地方查得到現在的狀態,一行字就夠:你的 fileSuggestion 沒有在跑,原因是 X。什麼會讓我改口:只要有人指得出哪個指令已經印得出這一行,我這段就收回。我翻過的那幾個章節裡沒有看到。

那個換個檔案就換語意的鍵,名字也騙人。disableAllHooks 的說明是 Turn off hooks, any custom status line, and any custom file suggestion command。一個叫 All Hooks 的東西,順手關掉三個不是 hook 的設定。為了跑一份來路不明的 repo 而臨時加上它,狀態列跟 @ 的自訂索引會一起消失(同一段文件還寫了 hooks 關著的時候 /goal 不能執行)。

另一種「跑對了,你就是看不到」

閘門那種是根本沒跑。還有一種是跑了、也對了,結果被壓在下面。

repo 的 CHANGELOG 在 2.1.275 這版寫著:Fixed @-mention file suggestions being buried below MCP resources when using a custom fileSuggestion command or typing @./@./。在那之前,只要你設了自訂命令,或只是打了 @.,你的檔案建議會排在 MCP resources 後面。腳本跑對了,清單第一頁還是看不到你的東西。

兩種失敗的成因差很遠,在畫面上卻長成同一句話:@ 跳出來的不是我要的。截至 2026-09-18,最新版是 v2.1.276,排序那條至少已經修掉了,前提是你有更新。

這篇全部來自官方文件與 repo 的 CHANGELOG。我沒有實際建過那支 file-suggestion.sh、沒有在任何 repo 裡按 @ 驗證過自訂建議,5 秒逾時跟 15 筆上限的實際表現我也沒跑過。另外,fileSuggestion 是哪一版加進來的,設定參考的那個章節沒有標注版本需求(同一頁其他設定有標),所以我這邊填不出版本號。

同一個形狀,在別的地方也看得到

statusLinesubagentStatusLinefileSuggestion 綁在同一組規則上。哪天狀態列突然變回預設的樣子,查的順序跟這篇一樣:先問有沒有人在上游把它收窄了,再回頭問自己的腳本。

再往外推一格。一個功能如果失敗的方式是退回預設值,而不是丟出錯誤,那麼「它看起來還在動」就不再是任何證據。安全的預設值幾乎都長這樣,因為它們的任務是在受限的環境裡讓系統繼續可用,於是選擇安靜地降級。降級跟正常,在畫面上是同一張臉。

回到抽屜。你花時間做了一份目錄,某天發現大家又在一格一格翻。先別急著檢查目錄做壞了沒。先確認那份目錄,還在不在你手上。

原文來源:All settings - Claude DocsInteractive mode - Claude Docsclaude-code CHANGELOG