提示寫 Fetch,規則叫 WebFetch:Claude Code 抓網頁之前,到底是誰在說不
一次 deep-research 跑下來,權限對話框跳了幾百次,每一個的標題都寫著 Fetch · from the "deep-research" workflow。
issue #80621 的回報者當時在跑 Claude Code 內建的 deep-research workflow。他 7 月 23 日開的這份 issue,標題就把整件事講完了:中途加的 allow 規則沒用、對話框寫「Fetch」但規則叫「WebFetch」、沒有批次允許。
他第一步的反應很合理。對話框說 Fetch,那就在 /permissions 加一條叫 Fetch 的 allow 規則。UI 回他「Added allow rule Fetch」,看起來成功了。提示照跳。這條規則永遠不會匹配任何東西,因為工具的真名是 WebFetch。
第二次他把名字改對,把裸的 WebFetch 寫進 .claude/settings.local.json。之後才啟動的 subagent,照他的說法還是「still prompted for every fetch」。他用 TaskStop 停掉,再用 resumeFromRunId 重跑,新規則一樣沒套上。他自己的推測是 settings 檔只在 session 啟動時讀一次,這是回報者的猜測,我在官方文件裡沒找到這種說法。最後他受不了 permission fatigue,把整個 workflow 中止。
兩次都卡在同一個想像上:「讓 Claude 抓網頁」是一個開關,找到它、打開就好。照官方文件的寫法,它比較像一棟大樓的門口,站了好幾個彼此不太講話的人。
一棟大樓的門口站了幾個人
想像你要進一棟有管理的社區大樓找朋友。
櫃台管理員會打電話上樓問住戶:這位要讓他上來嗎?住戶可以說「這次讓他上來」,也可以說「這個人以後都直接放行」。這就是提示。WebFetch 在 Manual 和 acceptEdits 模式下預設會先問,選項有三個:Yes 只放行這一次,下一次就算是同一個網域也會再問;「Yes, and don’t ask again for <domain>」會替那個網域寫一條 WebFetch(domain:...) allow 規則,存到該 repo 的 .claude/settings.local.json;第三個是拒絕,並告訴 Claude 換個做法。auto 和 bypassPermissions 模式會跳過提示,除非有明確的 ask 規則命中那個網域。
住戶事先交給管理員的名單,是 allow/deny 規則。管理員手上另外有一份大樓自己的常客名單,物業公司早就訂好,水電行、快遞那種,對應的是內建的預核准文件網域,不用問就放行。住戶的名單優先:明確寫在 deny、ask、allow 裡的 WebFetch(domain:...) 規則會壓過預核准清單,所以你可以封掉一個預核准網域,或要求它也得先問。這個優先順序是 2.1.162 才修好的,CHANGELOG 寫的是之前「WebFetch permission rules not being applied to built-in preapproved domains」。
規則的寫法像對門牌。domain: 後面比對的是 URL 的主機名稱,不分大小寫,可以用 *,規則和主機名稱尾巴的 . 都會先剝掉。WebFetch(domain:*.example.com) 匹配任何深度的子網域,api.example.com、a.b.example.com 都算,偏偏不含 example.com 本身。WebFetch(domain:example.*) 匹配 example.org,卻不匹配 example.evil.com,因為那個 * 得跨過一個點才能變成 evil.com。文件說這樣設計,是為了不讓尾端萬用字元配到攻擊者註冊得到的網域。
萬用字元要 v2.1.172(2026-06-10 上 npm)以後才真的會匹配。那一版修掉的,正是子網域規則在 allow、deny、ask 三個位置都從來沒配中過。
回報者的第一次失敗就卡在這裡:名單上寫的是「Fetch 先生」,大樓裡沒有這個人。
封住大門,還是派人站在門口搖頭
這段是我讀文件時最意外的地方。
裸的 WebFetch(工具名稱後面不接 domain:)和 WebFetch(domain:*) 都涵蓋每一個 URL。聽起來像同一件事的兩種寫法,文件卻特地講明 Claude Code 處理它們的方式不同,差在兩個地方。
第一個在 deny。"deny": ["WebFetch"] 會讓 Claude Code 把 WebFetch 工具整個移除,Claude 根本抓不了。換成 WebFetch(domain:*) 放進 deny,工具留著,每一次抓取都被拒絕。一個是把大門封死,一個是門還開著,門口站一個人對每位訪客搖頭。
第二個在沙箱。只有 domain: 這種形態,會順手把網域加進沙箱的允許或拒絕清單。官方舉的例子是這條:
1 | {"permissions": {"allow": ["WebFetch"]}} |
設了它,Claude 抓網頁不再問你。可是當你叫它在沙箱裡用 curl 打一個不在沙箱允許清單上的主機,Claude Code 還是會問,因為裸規則沒有把那個主機加進清單。換成 WebFetch(domain:*) 放 allow,沙箱裡的指令就能連到任何主機;放 deny,沙箱裡的指令哪裡都連不到。
反方向也不通。沙箱裡的指令不繼承 WebFetch 的預核准文件網域,要讓它不經提示連到某個網域,得把網域加進 allowedDomains,或寫一條沙箱也認得的 WebFetch(domain:...) 規則。WebFetch 那一側則永遠不讀沙箱的允許清單,你把網域加進沙箱或組織的網路允許清單,WebFetch 照樣會為它跳提示。
換回大樓:停車場柵欄和櫃台各有一本名單,只有寫成 domain: 的那幾行兩邊都認得。
我自己的寫法會是這樣:allow 那邊逐一列 WebFetch(domain:...),不寫裸的 WebFetch,也不寫 domain:*;deny 那邊要整個關掉,就用裸的 WebFetch,讓工具消失比讓它每次被拒來得乾淨。
想放行一個網域連同它所有子網域,要寫兩條,因為 *.example.com 不含 example.com 本身。下面這段是我照文件的比對規則組出來的示意,不是官方範例:
1 | {"permissions": {"allow": ["WebFetch(domain:example.com)", "WebFetch(domain:*.example.com)"]}} |
想整個關掉,則是另一份設定:
1 | {"permissions": {"deny": ["WebFetch"]}} |
deny 那半句有個例外。你開了沙箱,而且就是要 Bash 裡的 curl 一起斷網。這時 WebFetch(domain:*) 放 deny 反而是你要的,因為只有它會同步到沙箱的拒絕清單。
順帶一提,WebSearch 沒有這些分岔。它的規則不接任何 specifier,裸的 WebSearch 放 allow 或 deny 是唯一寫法,搜尋後端也不能換。
另一個跟裸規則有關的變化出在 2.1.268(2026-09-10)。在那之前,裸的 WebFetch deny 會擋掉所有 Artifact 讀取;那一版之後,裸的 deny 和 ask 規則不再套用到 Artifact 工具的讀取與更新,要擋,就得寫涵蓋 claude.ai 或 *.claudeusercontent.com 的 domain: 規則。
你的名單管不到的那個人
前面幾關,至少都歸你的設定檔管。有一關不是。
每次抓 URL 之前,WebFetch 會把主機名稱送到 api.anthropic.com,對照一份 Anthropic 維護的安全封鎖清單。只送主機名稱,不送完整 URL、路徑或頁面內容。通過的主機名稱快取五分鐘,被擋或檢查失敗的,下一次請求會重查。tools-reference 的說法很直白:「Whatever your rules allow, a fetch also passes the WebFetch domain safety check first」。
這像大樓警衛每來一位訪客,就打電話去派出所問這個人有沒有被通緝。住戶名單寫得再寬,這通電話照打。不管你用哪個模型供應商它都會跑,CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC 也關不掉它。
所以有一種情況會讓 WebFetch 全部失敗:你的網路擋了 api.anthropic.com。文件給兩條路,一是把那個網域加進允許清單,二是在 settings 裡設 skipWebFetchPreflight: true。第二條等於不打那通電話,WebFetch 會去抓任何 URL 而不查封鎖清單,所以文件建議搭配 WebFetch 權限規則,限制 Claude 能去哪些網域。這個情境我目前只在文件上看到,沒看到真實的回報。
旁邊還有幾道固定的檢查。WebFetch 碰到 localhost 或任何沒有點的主機名稱(例如內網的裸名稱),在發出請求前就直接拒絕。HTTP 會自動升成 HTTPS。轉址到別的主機時它不跟過去,而是回一段文字,寫明原本的 URL 和轉址目標。
Team 和 Enterprise 方案再多一層。WebFetch 也取決於組織政策,那份政策是 session 啟動時向 api.anthropic.com 要的。2.1.285 起,Team、Enterprise,以及 Claude Code 判斷不出登入方案的 session,如果啟動時沒載到政策,會先把 WebFetch 扣住,等政策載入。session 裡突然沒有 WebFetch,文件建議跑 /status,看 Organization policy 那一行怎麼說。
直接把它關掉
2.1.285(2026-09-29 上 npm)加了一個環境變數 CLAUDE_CODE_DISABLE_WEB_FETCH。環境變數表的原文是「Set to 1 to turn off the WebFetch tool. The WebSearch tool stays available.」
在這之前,想讓 session 完全不抓網頁,最直接的工具是 deny 規則。現在多了一條不用碰權限設定的路,而且 WebSearch 還留著。
這篇我只讀了文件與 issue,沒有實際驗證這些規則的行為。上面每一句「放 deny 會怎樣」「沙箱會不會問」,都是照官方文件轉述,我沒有在自己的機器上設過任何一條。
CLAUDE_CODE_DISABLE_WEB_FETCH=1 設下去之後,WebFetch 是像裸的 deny 規則那樣被整個移除,還是工具留著但每次都拒絕,文件沒說清楚是哪一種。如果你的流程會因為「工具不存在」和「工具被拒」走不同的分支,別照我的大樓比喻去猜,自己確認一次。
permissions 文件裡還有一句提醒,跟上面這些都不衝突:只用 WebFetch 擋不住網路存取。Bash 只要被允許,Claude 還是能用 curl、wget 或其他工具去任何 URL。這條路怎麼漏,我在〈你寫的 deny 規則,可能根本沒擋到〉拆過;--restricted 會連 WebFetch 一起拿掉的事,在〈畫面看起來沒變,它卻不能跑指令了〉。這裡都不重複。
還卡在那裡的部分
#80621 的第二次失敗,我解不了。中途寫進 settings.local.json 的規則,到底會不會套用到之後才啟動的 workflow subagent,我讀到的文件段落沒有明寫。回報者說不會,推測是設定只在啟動時讀;我沒辦法替他確認,也沒辦法替他否認。這份 issue 到 10 月 2 日還是 OPEN。
雲端那一側有另一道牆。#96260 的回報者把定時執行的雲端 routine 的 Network access 設成 Full,WebFetch 還是對每個外部網域回 EGRESS_BLOCKED,連拿來當對照的 en.wikipedia.org 也一樣,WebSearch 卻正常。那是雲端 egress proxy 的事,跟本機規則不同層,同樣還 OPEN。
回到那幾百個對話框
下次你在 workflow 裡看到 Fetch · from the "deep-research" workflow 跳出來,可以依序問三個問題。
規則的名字對不對?對話框寫 Fetch,規則一律寫 WebFetch。
你要的是哪一種放行?只信幾個網域,就在第一次提示選「don’t ask again」,或直接寫 WebFetch(domain:...)。真的想全放,才寫裸的 WebFetch 或 domain:*,而且先想好沙箱那一側要不要跟著打開。
規則是什麼時候寫進去的?workflow 跑到一半才加的話,在官方講清楚之前,我會把它當成可能沒生效,寫好規則、開新 session 再重跑。這是照回報者的經驗做的保守選擇,不是文件給的答案。
三題都問完還是卡,就要看名單以外的那幾個人了。提示還在跳,而且是沙箱裡的指令在問,去翻停車場那本簿子。沒跳提示、抓取直接失敗,或 WebFetch 整個不見,就去查那通打去 api.anthropic.com 的電話,還有組織政策。
要往下讀的話,先看 permissions 文件裡 allow、ask、deny 的優先順序,再看沙箱的 allowedDomains。
原文來源:Tools reference、Permissions、Data usage:WebFetch domain safety check、Environment variables、Claude Code CHANGELOG;GitHub issue #80621、#96260(issue 狀態以 10 月 2 日查詢為準)

















