切到 Architect 就只能寫 markdown:Roo Code 把權限綁在角色上
補寫於 2026 年 8 月 24 日,9 月才上線(8 月 21 日的排程沒跑起來,稿子隔三天補上;部落格的發佈額度也在 8 月中用完了)。文中對 Roo Code 的描述以 8 月 24 日的官方文件與 repo 狀態為準,你讀到時可能已經改版。
一個模式憑什麼知道這個檔案不能改?
Roo Code 把答案寫成一個欄位,叫 groups。它掛在角色的定義上。你切換角色,可動的檔案範圍跟著換掉,中間不會有人問你要不要授權。
這個 VS Code 擴充套件本身已經收了。官方文件 repo 裡那份 sunset.md 標題就叫 “Sunsetting Roo Code (Extension, Cloud, and Router)”,內文寫 “All Roo Code products will be shut down on May 15, 2026”,GitHub 上的主 repo 目前的狀態是 archived: true,最後一次 push 停在 2026-05-15,三個多月前的事。我沒裝過也沒跑過它,下面每一段都是讀官方文件站跟原始碼得到的,講的是它封存當下的行為。
產品收掉了,機制還留在原始碼裡可以讀。
groups 裡那一行,不是字串就是一組 tuple
從最裡面拆起。一個模式的定義有個必填欄位 groups,型別是陣列,每一項只有兩種長相。
字串是一種,例如 "edit",代表這個群組整組放行、不受限。
另一種是兩個元素的 tuple,第一個元素還是群組名,第二個元素是一個 options 物件。官方的 YAML 範例長這樣,那個 - - edit 看起來像排版壞掉,其實是 list 裡面再包一個 list:
1 | groups: |
options 物件在 zod schema 裡只有兩個欄位,fileRegex 跟 description,兩個都是選填。沒給 fileRegex 就等於沒限制,description 純粹是給人看的說明。換成 JSON 寫的時候反斜線要雙跳脫,變成 "\\.(js|ts)$"。
真正決定擋不擋的是下一層。
擋下來的那一刻,是 throw
工具跑起來之前會先過一個函式 isToolAllowedForMode(),在 src/core/tools/validateToolUse.ts。它對 edit 的處理逐字是這樣:
1 | if (groupName === "edit" && options.fileRegex) { |
throw。它沒有在對話裡提醒一句然後照做,那次工具呼叫當場就被打斷了。
丟出來的錯誤訊息也組得很完整:模式名、允許的 pattern、你自己寫的 description、實際想動的檔案路徑、被擋下的工具名,全部塞在同一句裡。文件 Error Handling 段落列的欄位,跟原始碼組字串的順序對得起來。
它擋的不是模型的意圖,是那次工具呼叫。模型想幹嘛、有沒有被說服、規則檔寫得夠不夠兇,在這一行前面全都不重要。
那個 regex 比你以為的鬆
真正比對的實作只有幾行:
1 | const regex = new RegExp(pattern) |
兩個細節藏在這兩行裡。
regex.test() 是部分比對。pattern 沒有用 ^ 或 $ 錨定的話,只要路徑「任何位置」命中就算通過。文件另外寫明比對對象是從 workspace root 起算的完整相對路徑,例如 src/components/button.js。所以你要是圖方便寫個 docs 想把某個模式關在文件目錄裡,src/docsomething.ts 照樣過關。
new RegExp(pattern) 沒帶 i flag,所以區分大小寫。文件那句 “Regex patterns are case-sensitive by default” 的實作依據就在這裡。
寫壞的 pattern 分兩層處理。存檔的時候 schema 的 refine 會先擋,回一句 “Invalid regular expression pattern”。執行期萬一還是拿到壞的,doesFileMatchRegex 會 console.error 之後 return false,判定不 match,等於擋下。壞掉的時候倒向保守的那一邊。
只有 edit 會被拿去比對路徑
原始碼那個條件寫死了 groupName === "edit"。其他群組就算你在 tuple 裡塞了 fileRegex,也不會被拿去比對檔案路徑。
還有一層更細的:要 toolParams 裡真的出現編輯參數才算數,清單是 diff、content、operations、search、replace、args、line、patch、old_string、new_string。只帶一個 path 的串流呼叫不會觸發阻擋。
apply_patch 被特別處理過,會從 patch 內容裡把 *** Add File: 、*** Delete File: 、*** Update File: 三種標記後面的路徑全部抓出來,每一個都要通過同一條 regex,任一個不過就 throw。
想靠換個工具名繞過去也行不通。權限比對之前會先把別名解析成正式名稱,write_file 會變回 write_to_file。
拆完機制,才看得懂它為什麼要這樣設計
現在回頭看內建的 Architect 模式,它的 groups 在原始碼裡是這樣一行:
1 | ["read", ["edit", { fileRegex: "\\.md$", description: "Markdown files only" }], "mcp"] |
切到這個角色,它就只能寫 markdown。文件站那張表的 Tool Access 欄位寫的是 “restricted edit (markdown files only)”,跟原始碼對得上。
官方在「Why Use Different Modes?」列的理由之一逐字是 “Safety controls: Prevent unintended file modifications when focusing on planning or learning”。你在做規劃、在讀程式碼學東西的時候,順手把某個檔案改掉這件事,從一開始就不可能發生。
內建模式一共五個。
Code 跟 Debug 拿到 read、edit、command、mcp 全套;Ask 只有 read 跟 mcp,改不了檔案也跑不了指令;Architect 就是上面那個只能寫 markdown 的版本;剩下一個 Orchestrator 等一下講。
順帶提醒,repo 的 README 只列了四個模式、漏掉 Orchestrator。以文件站跟原始碼的 DEFAULT_MODES 為準,那個陣列剛好五個元素。
一個什麼工具都沒有的角色,還是能叫別人做事
Orchestrator 的 groups 是空陣列。文件寫得更直白,”No direct tool access”。
那它怎麼工作?
isToolAllowedForMode() 裡有一份 ALWAYS_AVAILABLE_TOOLS:ask_followup_question、attempt_completion、switch_mode、new_task、update_todo_list、run_slash_command、skill。這份清單的判斷排在群組迴圈之前,命中就直接 return true。new_task 跟 switch_mode 都在裡面。
所以它拿不到 read、拿不到 edit、拿不到 command,卻可以開一個子任務,指定由哪個模式去跑。官方管這叫 Boomerang Tasks:父任務暫停,子任務在另一個專門模式裡從自己的獨立 context 開始,做完之後父任務只收到一份摘要。子任務讀了哪些檔案、diff 長什麼樣,不會回流到父任務的對話歷史。
邊界不在那份宣告裡,在它被檢查之前的那幾行。
綁在角色上要付的帳
第一個是靜態宣告不會叫。browser 這個工具群組已經被移除了,但 schema 用 z.preprocess 在驗證之前把它靜默過濾掉,不報錯。照著舊教學抄設定檔的人,會有一行完全沒有作用的宣告安安靜靜躺在那裡。
第二個是文件跟實作會漂。合法的工具群組在原始碼裡是五個,read、edit、command、mcp、modes,文件站兩個地方都只列四個,漏掉 modes。預設模式更妙:文件標題寫 “Code Mode (Default)”,原始碼寫的是 defaultModeSlug = modes[0].slug,而 DEFAULT_MODES[0] 是 architect。兩邊說法不一致,我沒找到說明這個變更的 commit,所以哪個是最終行為我不知道。
覆寫規則很硬。自訂模式放專案根目錄的 .roomodes(沒有副檔名,YAML 跟 JSON 都收,會先試著當 YAML 解析),或者放全域的 custom_modes.yaml。同一個 slug 兩邊都有的時候,專案版本完整覆蓋全域版本,文件特地用粗體標了 Important,說的是所有屬性都不合併。你在全域寫好的 customInstructions,不會因為專案檔沒寫就自動沿用下來。
真正的取捨在更前面。權限綁在動作上,決定點落在事情要發生的那一秒。你手上的資訊最完整,代價是得一直在場。權限綁在角色上,決定點提前到你挑角色的那一刻,之後它做一百件事都不再問你。代價是你得在開工之前就想清楚這個身分會碰到什麼。角色挑錯了,它會很有效率地做錯的事。
還有一個粒度問題。fileRegex 管得到檔案在哪,管不到檔案裡面寫什麼。一個模式被允許改 .md,它就有資格把你整份文件砍掉重寫。
我回頭看了自己的設定檔
我天天用的是 Claude Code,設定檔裡那些准與不准,綁的是工具跟指令的樣式,落在動作那一軸。同一條規則不管我當下在規劃、在修 bug 還是在寫文件,都是同一套。
讀完這套東西之後,我多了一個問題:這條規則到底該綁在什麼上面。如果我想擋的是「規劃的時候不要順手改到 code」,那我要擋的其實是一個身分,而身分這件事我現在是靠自己記得的,沒有寫在任何地方。忘了就沒了。
Roo Code 在 sunset 那份文件裡推薦的開源替代品,原文寫的是 Cline;另外提到 roomote.dev,他們接下來要做的東西會放在那裡。那是同一批人換了題目,跟接手 Roo Code 是兩回事。
來源
- RooCodeInc/Roo-Code(主 repo,
archived: true、最後一次 push 停在 2026-05-15) - Sunsetting Roo Code (Extension, Cloud, and Router)(停止服務公告原文)
packages/types/src/mode.ts(內建模式定義與fileRegex)- Custom Modes(自訂模式與權限群組文件)










