Claude Code에 기능을 직접 다는 모드, 만들기부터 설치 전 점검까지
Tools28분 읽기

Claude Code에 기능을 직접 다는 모드, 만들기부터 설치 전 점검까지

모드의 훅은 도구 호출이 지나는 여덟 관문에서 next를 부르느냐로 하는 일이 갈립니다. 호출 사슬과 가드, 샘플·사용자 모드, 만들기, 설치 전 점검을 뜯어 봅니다.

설정 훅은 도구 호출 앞에서 셸 명령 하나를 부르고 그 결과를 읽을 뿐인데, 모드는 같은 자리에서 Claude Code 안의 함수를 부르고, 그 함수가 호출을 그대로 보내거나 고치거나 대신 답하며 화면까지 그립니다. 이 차이는 10월 1일(UTC) 공개된 2.1.287 버전에서 모드가 들어오며 생겼습니다. 변경 기록의 첫 줄은 “플러그인이 더 깊은 동작을 바꿀 수 있게 됐다”가 전부였고, 그 뒤 3주 동안 모드 항목이 거의 매 버전에 붙어 10월 23일(UTC)에 나온 2.1.310까지 이벤트와 화면 요소가 계속 늘었습니다.

이 글은 Claude가 git push --force 같은 도구 호출 하나를 시도하는 순간을 붙들고, 그 호출이 거치는 관문을 바깥에서 안으로 뜯어 봅니다. 조직이 서는 바깥 관문, 내 훅의 몸체, next를 부르지 않을 때 일어나는 일, 사람을 기다리는 동안의 시계, 그 위에 얹힌 권한 잠금을 차례로 지난 뒤, 같은 구조 위에서 Anthropic 샘플과 사용자 모드가 어디에 서는지, 어떻게 만들고 시험하는지, 설치 전에 무엇을 읽어야 하는지로 이어집니다.

호출 한 건이 거치는 관문을 바깥에서 안으로 세어 보면

모드는 플러그인의 한 종류입니다. 플러그인 안의 JavaScript나 TypeScript 파일(설명서 용어로 훅 모듈)이 register 함수를 내보내면 Claude Code가 모드를 불러올 때 한 번 호출하고, register 안에서 on('이벤트 이름', 함수)로 등록한 함수가 훅입니다. 설정 파일에 적는 셸 명령도 같은 훅이라는 이름을 쓰지만 설명서는 “설정 훅”으로 따로 부르므로 이 글도 그렇게 가릅니다. 도구를 쓰기 직전에 나는 이벤트가 tool.call이고, 서브에이전트가 부르는 도구와 MCP 도구도 같은 이벤트를 지납니다.

아래 그림은 이 이벤트 하나가 지나는 길을 설명서의 “모드가 실행되는 순서” 절과 조직 관리 문서를 이어 붙여 그린 것입니다. 위에서 아래로 읽고, 오른쪽 열은 각 출구에서 Claude가 돌려받는 것입니다.

도구 호출이 관리 설정 훅, 가드와 조직 모드, 내 모드, 뒤에 붙는 모드, 다른 PreToolUse 훅, tool.check, 권한 확인, 도구 실행의 여덟 관문을 차례로 지나고 세 곳에서 중간에 끝나는 길 그림 1. 도구 호출 한 건의 여덟 관문과 출구(설명서의 실행 순서 기준)

처음 두 관문은 내가 손댈 수 없는 쪽입니다. 조직이 관리 설정에 둔 PreToolUse 훅은 모든 모드보다 먼저 돌고, 거기서 막히면 모드의 tool.call 훅은 아예 실행되지 않습니다. 이어서 내장 가드와 조직이 앞에 붙인 모드가 서고, 호출이 이 둘을 지나 어느 모드에서 고쳐지면 관리 훅이 고쳐진 호출을 보고 한 번 더 돌아 여전히 막을 수 있습니다.

셋째가 내 모드입니다. 같은 이벤트를 여러 모드가 다루면 먼저 설치한 모드가 바깥에 서서 이벤트를 먼저 보고 결과를 나중에 봅니다. 한 모드는 매니페스트의 dependencies에 적은 모드보다 앞서고, 한 파일 안에서는 register가 on을 부른 순서대로 돕니다. 넷째는 조직이 appendPlugins에 올린 모드와 그 밖의 내장 모드로, 사용자 모드들이 넘겨 준 형태만 봅니다. 모드 사슬이 끝나야 Claude Code 자신의 동작이 시작됩니다. 설정 파일(관리 설정은 제외)과 플러그인의 hooks/hooks.json에 있는 PreToolUse 훅이 여기서 돌고, 그 뒤 tool.check를 다루는 모드가 허락 결정을 보며, 마지막으로 권한 규칙과 대화상자, 자동 모드의 분류기를 거쳐 도구가 실행됩니다. 결과는 온 길을 거꾸로 올라가 내 훅의 await next(e) 뒤에 도착합니다.

그림에서 눈여겨볼 것은 출구가 셋이라는 점입니다. 관리 훅이 막으면 모드는 호출을 보지도 못하고, 내 모드가 next 없이 답하면 그 뒤의 다른 PreToolUse 훅과 권한 확인, 도구 실행이 모두 건너뛰어지며, 권한 판정에서 거부되면 도구가 실행되지 않습니다. 훅이 사슬의 어디에 서느냐가 그 훅이 건너뛸 수 있는 것과 건너뛸 수 없는 것을 정합니다.

바깥 두 관문은 조직의 몫이다

관리 설정이 있는 기기이거나 세션이 팀·엔터프라이즈 요금제의 /login으로 돌면 Claude Code는 사용자가 설치한 모드보다 먼저 sec-default@builtin이라는 내장 모드를 불러옵니다. /plugin과 디버그 로그에는 cc-plugin-sec-default로 보이고 사용자는 끌 수 없습니다. API 키나 클라우드 제공자로 로그인한 사용자는 관리 설정이 있는 기기에서만 이 가드를 얻고, 수명이 긴 CLAUDE_CODE_OAUTH_TOKEN으로 도는 세션은 설령 기기에 팀·엔터프라이즈 로그인이 저장돼 있어도 이 로그인으로 치지 않습니다.

가드가 지키는 것은 조직이 관리하는 것들입니다. 사용자의 모드는 관리 훅이 받는 입력과 내리는 판단, 시스템 프롬프트, 관리 CLAUDE.md, 어느 모드가 읽든 설정값, 관리 MCP 서버의 도구와 설명을 바꾸지 못합니다. 그 밖에서는 사용자의 권한을 그대로 가진 모드가 파일을 읽고 쓰고 프로그램을 실행하고 네트워크에 나가고, 도구 호출과 프롬프트를 고치고 거부하고 화면을 그립니다. 가드가 있으면 사용자 모드는 기본적으로 거부 규칙이 막는 호출과 확인(ask) 규칙이 묻게 한 호출, PreToolUse 훅이 막은 호출을 승인하지 못합니다.

관리자가 순서를 직접 정할 수도 있습니다. 관리 설정의 prependPlugins에 올린 모드는 모든 사용자 모드 앞에서 이벤트를 먼저 보고 결과를 나중에 보며, 호출을 바꾸거나 막거나 사용자 모드를 통째로 건너뛰게 할 수 있습니다. appendPlugins는 사용자 모드 뒤에 붙습니다. 이 목록을 설정하면 기본 목록을 대체하므로 가드를 남기려면 sec-default@builtin을 직접 적어야 합니다. 조직의 모드로 인정받는 조건도 까다롭습니다. 관리 설정의 enabledPlugins가 그 플러그인을 켜고, 관리 설정이 마켓플레이스를 기기의 절대 경로 디렉터리로 가리키며, 마켓플레이스가 플러그인을 상대 경로로 올려 제자리에서 불러와야 합니다. GitHub·git·URL·npm에서 복사해 온 플러그인은 관리 설정이 켜도 사용자 모드로 칩니다.

사용자 모드를 통째로 막는 길은 한 줄입니다. 관리 설정의 pluginConfigs에서 가드의 allowManagedModsOnly 옵션을 켭니다.

{
  "pluginConfigs": {
    "cc-plugin-sec-default@builtin": { "options": { "allowManagedModsOnly": true } }
  }
}

이러면 설치한 플러그인의 모드, --plugin-dir로 부른 모드, 세션 중 Claude가 쓴 모드 모두 훅이 돌지 않습니다. 사용자 설정이나 프로젝트 설정에 같은 항목을 적어도 효과가 없고, 사용자의 설정 훅과 상태줄은 그대로 일합니다. 반대로 allowModsToOverrideDenyRules를 켜면 사용자의 모드가 거부 규칙이 막은 호출을 승인할 수 있게 됩니다.

가드가 있는 기기에서는 사용자 모드의 훅 가운데 일부가 아예 돌지 않습니다. 설정 훅 이벤트(classic.Stop 같은 classic.<Event>), 시스템 프롬프트의 구성과 각 절(prompt.compose, prompt.section), 첫 메시지와 함께 가는 맥락(prompt.context), Claude에게 덧붙이는 알림(prompt.attachment), 스킬 본문(skill.prompt), 커밋·PR 서명(attribution.text), 압축(session.compact), 설정 읽기(settings.read)가 그렇습니다. tool.call은 이 목록에 없어서 도구 호출을 다루는 모드는 그대로 돕니다. 2.1.299부터 /plugin의 상세와 오류 탭이 건너뛰는 훅을 알려 주고, 2.1.304부터는 먼저 도는 플러그인이 내 모드의 훅을 건너뛰면 대화에도 한 줄이 찍힙니다. 이 이벤트를 다루려면 모드를 조직의 모드로 설치하거나, prependPlugins를 빈 목록으로 두어 가드를 통째로 없애야 하는데, 후자는 가드의 보호도 함께 잃습니다.

Claude Code의 일부 기능도 이미 모드이며, /plugin의 Installed 탭에서 Built-in으로 보입니다. 업데이트나 삭제는 못 하고 끄는 방법만 각각 있습니다.

/plugin의 이름 하는 일 끄는 법
cc-plugin-agents-md AGENTS.md를 프로젝트 지침으로 읽음 /plugin에서 끄거나 읽을 지침 파일을 고름
cc-plugin-diff /diff 창을 그림 /plugin에서 끄면 내장 /diff가 대신 답함
cc-plugin-plugin-authoring 모드 작성용 plugin-authoring 스킬(코드 없이 스킬만) /plugin에서 끔
cc-plugin-sec-default 조직이 관리하는 것을 사용자 모드로부터 지키는 가드 사용자는 못 끔
cc-plugin-telemetry Claude Code와 내장 모드가 남기는 분석 기록 전송 /plugin에서 끄거나 DISABLE_TELEMETRY 등으로 분석을 끔
cc-plugin-you-should-know 보조 에이전트가 대화를 다시 읽다 놓쳤을 한 가지를 띄움 기본 꺼짐. /plugin enable cc-plugin-you-should-know@builtin으로 켬

“You should know”의 보조 에이전트는 같은 모델에 보내는 별도 요청이라 사용량이 더 듭니다. diff, agents-md, sec-default, telemetry는 소스가 Claude Code 저장소의 mods 폴더에 공개되어 있고, 각각 훅 모듈과 테스트가 있는 완전한 플러그인이라 읽으며 배우기 좋습니다.

내 훅 한 개의 몸체: 인자 셋과 처리 방식 셋

바깥 관문을 지나 내 차례가 오면 훅은 인자 셋을 받습니다. 모드가 바깥세상에 닿는 방법을 모은 $, 이벤트를 담은 읽기 전용 데이터 e, 다음 단계로 넘기는 next입니다. e는 깊이 얼려 있어서 값을 직접 고치려 하면 오류가 나므로, 바꾸고 싶으면 고친 복사본을 next({ ...e, text: '새 값' })처럼 넘깁니다. 뒤따르는 모드와 Claude Code는 원본을 보지 못합니다. next(e)는 다음 모드의 훅을 부르고, 사슬 끝에서는 Claude Code의 원래 동작을 실행한 뒤 결과를 돌려줍니다.

처리 방식은 next를 어떻게 다루느냐로 갈립니다. 이벤트를 지켜보기만 하려면 일을 하고 return next(e)합니다. 일을 이벤트 뒤에 하려면 await next(e)로 결과를 받아 일한 뒤 그 결과를 돌려줍니다. 이벤트를 고치려면 복사본을 넘기고, 이벤트를 직접 처리하려면 next를 부르지 않고 결과를 돌려줍니다. 설명서의 첫 예제 하나에 앞의 둘이 들어 있습니다. 파일 맨 위 변수 calls를 두고, 도구 호출 훅은 1을 더한 뒤 화면을 다시 그리게 하고 next(e)로 통과시키며, 스피너를 그리는 훅은 글자 뒤에 · tool calls: 3…을 붙인 복사본을 넘깁니다.

let calls = 0
export function register(on) {
  on('tool.call', async ($, e, next) => {
    calls += 1
    $.ui.invalidate('ui.render')   // 화면을 다시 그리게 함
    return next(e)                 // 지켜보기만 하고 도구는 그대로 실행
  })
  on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
    return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })  // 고쳐 쓰기
  })
}

한 모드 안의 훅들은 파일의 변수를 같이 쓰므로 한 훅이 센 값을 다른 훅이 보여 줄 수 있습니다. 이 점과 화면 그리기가 설정 훅·스킬·MCP 서버에는 없는 모드만의 능력입니다. 설정 훅은 이미 쓰는 스크립트로 호출을 막거나 기록할 때, 스킬은 같은 지시를 계속 붙여 넣을 때, MCP 서버는 외부 서비스에 닿을 때가 맞고, 한 플러그인에 넷을 모두 담을 수도 있습니다. 모드가 필요한 때는 패널이나 프롬프트 위 띠를 그리고, 도구 호출 줄이나 스피너, 질문 대화상자를 바꾸고, Claude의 턴 없이 즉시 도는 슬래시 명령을 달고, 요청 하나를 다른 모델로 돌리는 것처럼 안쪽에서만 되는 일입니다.

모든 이벤트에 반응하면 시끄러우니 on의 둘째 인자로 거름 조건(매처)을 줍니다. 값 하나({ tool: 'Bash' }), 값의 배열({ tool: ['Edit', 'Write'] }), 정규식({ tool: /^mcp__github__/ })이 되고, 이벤트 이름도 'classic.*'처럼 와일드카드를 받습니다. '*'는 분석 이벤트를 뺀 모든 이벤트에 맞습니다. 같은 이벤트를 매처 없이 두 번 등록하면 on("session.start") is registered twice without a matcher라는 오류로 모듈이 로드되지 않으니 한 이벤트의 일은 한 훅에 모읍니다.

$에는 모드가 파일·프로세스·네트워크·모델·화면에 닿는 메서드가 모두 들어 있고, 이 메서드 호출도 이벤트라서 fs.read 같은 이름으로 훅을 걸면 뒤에서 도는 다른 모드의 호출을 가로챌 수 있습니다. 훅 함수에는 next.signal(이벤트가 버려지면 중단 신호), next.origin(이벤트를 낸 쪽의 플러그인과 우선순위 prepend·user·append·builtin), next.budget(남은 시간)도 붙습니다. next.to(e, 'append')로 사용자 모드를 건너뛰는 일은 prependPlugins나 appendPlugins의 모드만 할 수 있습니다.

next를 부르지 않으면 어디까지 건너뛰나

이벤트를 직접 처리하는 방식이 가장 강합니다. 훅이 next를 부르지 않고 { deny: '이유' }를 돌려주면 도구는 실행되지 않고 Claude는 이유 글을 도구의 결과로 읽습니다. 그래서 이유는 Claude가 따를 수 있는 지시문으로 씁니다. 권한 확인도 건너뛰어 대화상자가 뜨지 않으며, 그림 1의 가운데 출구처럼 그 뒤의 PreToolUse 훅과 tool.check, 도구 실행이 모두 없던 일이 됩니다.

async function guard($, e, next) {
  if (/git push .*--force/.test(e.command)) {
    return { deny: '강제 푸시는 이 저장소에서 금지입니다. 새 브랜치로 푸시하세요.' }
  }
  return next(e)   // 나머지 명령은 권한 확인을 거쳐 실행
}

export function register(on) {
  on('tool.call', { tool: 'Bash' }, guard).catch(async ($, e, next) => {
    if (next.called) return next(e)   // guard가 next를 부른 뒤 실패했으면 그 결과를 그대로
    return { deny: '검사기가 실패해 이 명령은 실행하지 않았습니다: ' + next.error.kind }
  })
}

답하는 방식에는 변형이 여럿 있습니다. 호출을 고치려면 바뀐 인자를 next에 넘기고, 다시 시도하려면 첫 결과에서 isError를 본 뒤 next(e)를 한 번 더 부릅니다. 도구를 돌리지 않고 { result }로 대신 답할 수도 있는데, 그러면 권한 대화상자도 뜨지 않고 Claude는 내가 돌려준 결과만 압니다. await next(e)로 도구가 실행된 뒤에 { deny: 이유 }를 돌려주면 결과가 Claude에게서 가려집니다. Claude는 “Bash가 실행됐고 플러그인이 결과를 가렸다”는 안내 뒤에 이유를 읽습니다(2.1.295에서 이렇게 보고하도록 바뀌었습니다). 가리는 것이 이미 한 일을 되돌리지는 않으니, 막으려면 next를 부르기 전에 해야 합니다.

막는 훅은 실패해도 막히게 만든다. 훅이 오류를 던지거나 시간을 넘기면 Claude Code는 그 훅을 건너뛰고 호출을 그대로 진행합니다. next를 부르기 전에 실패했다면 뒤의 훅이 대신 돌아 명령이 나가고, next 뒤에 실패했다면 그 결과가 그대로 남습니다. 실패한 이유는 my-mod: tool.call hook skipped: threw Error: boom 같은 한 줄로 남습니다. 안전장치는 위 코드처럼 on(...)이 돌려주는 등록에 .catch 처리기를 붙여 실패 때 거부하게 합니다. 처리기는 2.1.301부터 훅이 던지거나 시간을 넘길 때만 돌고 시간 제한은 1초이며, 처리기까지 실패하면 훅은 처리기 없는 것처럼 건너뛰어집니다.

처리기에서 next.error.kind는 throw와 timeout이고, 훅이 자기 $ 호출이나 모드 코드가 일으킨 이벤트 때문에 건너뛰어졌을 때는 re-entry입니다(2.1.292부터). 이벤트마다 처리기의 거절 모양이 다릅니다. prompt.submit은 { drop: '이유' }, config.set은 { deny: '이유' }이고 tool.check는 { decision: 'deny', reason }입니다. tool.check와 모드 로드를 지켜보는 plugin.register에서는 next 뒤에 낸 거절도 그대로 효력이 있어 next.called를 따질 필요가 없습니다. 반면 tool.call에서 next 뒤의 deny는 앞서 본 대로 결과를 가릴 뿐입니다.

사람에게 물으며 붙들 때 10초 시계는 멈춘다

도구 호출 훅은 await로 next 앞에서 기다릴 수 있고, 그동안 호출은 대기합니다. 사람에게 물으려면 $.ui.ask가 맞습니다. Claude가 사용자에게 질문할 때 쓰는 대화상자에 내 질문과 번호 붙은 선택지를 띄우고, 사용자가 고른 선택지의 이름으로 풀립니다. 선택지 뒤에는 다른 답을 직접 입력하는 줄과 “Chat about this” 줄이 자동으로 붙습니다.

설명서의 예제는 rm -rf, git reset --hard, --force가 붙은 git push에 맞는 정규식을 두고, 맞는 Bash 명령에서 “Run it”과 “Refuse”를 묻습니다. 정규식이 git push -f처럼 다른 표기는 놓친다고 설명서 스스로 적어 두었습니다. 기본 답을 Refuse로 두는 것이 핵심입니다. 사용자가 질문을 닫거나 “Chat about this”를 고르거나 claude -p처럼 물을 사람이 없으면 $.ui.ask가 거부(reject)되는데, 그때 catch에서 답이 Refuse로 남아 명령이 거부됩니다.

그림 2는 이 흐름에서 누가 기다리고 어느 시간이 세어지는지를 보입니다. 훅이 $.ui.ask로 사용자에게 간 사이 시계는 멈춰 있고, 사용자의 답이 오면 훅은 deny로 Claude에게 곧장 답하거나 next(e)로 권한 확인과 실행으로 넘깁니다.

Claude가 호출을 내면 훅이 사용자에게 질문하고, 사용자의 답에 따라 훅이 deny를 Claude에게 돌려주거나 next로 Claude Code의 권한 확인과 실행으로 넘기는 순서 그림 2. 호출을 붙들고 묻는 순서(설명서의 $.ui.ask 예제 기준)와 10초 시계

훅 하나가 한 이벤트에서 자기 코드에 쓸 수 있는 시간은 10초이고(prompt.edit 훅은 50밀리초) next를 기다리는 시간과 $의 메서드를 기다리는 시간은 여기에 들어가지 않습니다. 다만 $.clock.sleep은 예외로 세어지고, 내가 직접 만든 프라미스를 기다리는 시간도 셉니다. 시간을 넘기면 Claude Code는 훅을 건너뛰니 붙들려던 명령이 그대로 나갑니다. 사람을 기다리는 훅에 .catch 거부 처리기가 꼭 필요한 이유입니다.

Blast Radius 샘플은 이 제한 때문에 다른 길을 택했습니다. 버튼 눌림을 $.process.run으로 0.25초씩 쉬면서 확인하는 반복문으로 붙들었고, $ 호출에 쓴 시간은 제한에 들지 않으니 사람의 답을 오래 기다릴 수 있습니다. 설명서가 권하는 더 단순한 길이 $.ui.ask입니다. 시간과 크기의 한도는 이 밖에도 정해져 있습니다.

한도 값
훅 하나의 자체 실행 시간 10초(prompt.edit은 50밀리초)
.catch 처리기 1초
$.process.run 기본 30초, 최대 10분
$.http.fetch 한 호출 30초, 본문 4 MiB, 리다이렉트 5번
$.fs.read·$.fs.write 파일 하나, $.store 총량 4 MiB
drop·deny 이유 글자 수 4,096자(넘으면 끝이 잘리고 효력은 그대로)
$.ui.invalidate로 다시 그리기 초당 10번(터미널의 일부 영역은 30번)
사용자가 열지 않았는데 모드가 연 패널 터미널 144열부터 놓임, 한 번 열어 본 뒤에는 110열부터
claude plugin test 테스트 하나 5초

tool.check는 허락만 따로 다룬다

모든 호출을 건드릴 필요 없이 “이 호출이 실행돼도 되나”의 결정만 다루고 싶으면 tool.check 이벤트가 있습니다. 이 이벤트는 tool.call 훅과 PreToolUse 훅이 끝난 뒤, 권한 대화상자와 자동 모드의 분류기, PermissionRequest 훅 앞에서 납니다. await next(e)는 규칙과 권한 모드와 훅이 내린 Claude Code의 결정을 돌려주고 그동안 권한 대화상자는 뜨지 않습니다. 결정은 decision이 allow, ask, deny 중 하나인 객체이고, 누가 그렇게 정했는지 알려 주는 reason, rule(Bash(git push:*)처럼 쓴 규칙), hook, setting이 붙어 옵니다. ask는 대화상자나 자동 모드 분류기, 헤드리스 호스트 같은 그 모드의 결정자에게 넘기라는 뜻입니다. 훅은 그 결정을 그대로 돌려주거나 자기 결정으로 바꿉니다.

on('tool.check', { tool: 'Bash' }, async ($, e, next) => {
  const decided = await next(e)                    // Claude Code의 결정
  if (!e.input.command.includes('git push')) return decided
  const branch = await $.process.run(['git', 'branch', '--show-current'])
  if (branch.stdout.trim() !== 'main') return decided
  return { decision: 'deny', reason: 'main 브랜치가 아닌 곳에서 푸시하세요' }
})

현재 브랜치가 main일 때만 거부하는 규칙은 규칙 파일에 쓸 수 없으니 코드가 필요합니다. 반면 고정된 명령이나 경로라면 Bash(npm test) 같은 권한 규칙이 코드 없이 더 단순합니다. 이 훅은 명령 글자를 읽을 뿐이라 설명서도 Claude에게 건네는 알림 정도로 보라고 하고, 모두에게 main 푸시를 막으려면 Git 호스트에서 브랜치를 보호하라고 권합니다. 훅이 실패하면 호출은 모드 없이 받았을 결정을 받으며, 막으려면 .catch에서 deny를 돌려줍니다.

이벤트에는 판단 재료가 더 있습니다. 2.1.304부터 e.isReadOnly는 도구가 그 호출을 읽기 전용이라고 보고할 때 true이고, 아닐 때는 false가 아니라 필드가 없습니다. Read·Grep·Glob·WebSearch는 항상 읽기 전용이고, Bash는 내장 읽기 전용 명령 검사를 통과할 때, MCP 도구는 서버가 readOnlyHint를 true로 둘 때입니다. 도구가 스스로 하는 보고라서 이 필드만 보고 승인하면 안 됩니다. WebFetch와 Agent 호출도 읽기 전용으로 보고되지만 URL을 요청하고 도구를 쓰는 서브에이전트를 띄웁니다. 읽기 전용만 받는 매처는 { tool: 'Bash', isReadOnly: true }이고 isReadOnly: false를 매처에 쓰면 훅이 영영 돌지 않으므로, 읽기 전용이 아닌 호출은 훅 본문에서 e.isReadOnly가 없는지로 가릅니다. 2.1.290부터 e.agentId(서브에이전트나 팀원의 호출일 때)와 e.ceiling(조직이 ask로 묶은 커넥터 도구일 때 ask)도 읽을 수 있습니다.

내 모드가 allow를 내면 모든 잠금이 풀릴까요. 그렇지 않고, 어느 잠금이 남느냐는 그 세션에 가드가 있느냐로 갈립니다. 그림 3의 두 열은 같은 allow가 가드가 있을 때와 없을 때 어떻게 다르게 먹히는지를 보입니다.

모드가 allow를 내렸을 때 가드가 있는 세션에서는 거부 규칙, 확인 규칙, PreToolUse 차단과 확인, 권한 프롬프트 도구가 유지되고, 가드가 없는 세션에서는 앞의 다섯 곳이 모두 뒤집히며, 플랜 모드와 사용자 상호작용 도구는 두 환경 모두 유지된다 그림 3. 모드의 allow가 넘지 못하는 잠금(권한 설명서와 모드 관리 문서 기준)

위쪽 다섯 줄은 가드 유무로 통째로 뒤집히고, 아래 두 줄은 환경과 무관하게 유지됩니다. 가드가 없는 세션은 API 키나 클라우드 제공자로 로그인하고 관리 설정도 없는 개인 기기가 전형이라서, 개인 사용자일수록 모드의 승인이 규칙과 PreToolUse 훅을 넘는다는 점을 알고 써야 합니다. 두 환경 모두 유지되는 줄은 모드의 종류가 아니라 호출의 성격이 정합니다. 플랜 모드에서 모드의 tool.check는 파일 편집처럼 읽기 전용이 아닌 호출의 결정을 조이기만 하고 풀지는 못하며, AskUserQuestion이나 requiresUserInteraction을 단 MCP 도구는 모드가 allow해도 사용자에게 묻습니다(2.1.292부터). 자동 모드에서는 모드가 승인한 호출이 분류기 검사 없이 돌지만, 가드가 있고 --permission-prompt-tool이나 Agent SDK의 canUseTool이 걸린 세션은 그 분류기가 먼저 봅니다. dontAsk 모드에서는 그 모드 말고는 막을 이유가 없던 호출을 모드가 승인할 수 있습니다.

샘플 셋의 자리: 보기만, 붙들기, 기록만

Anthropic은 anthropics/claude-code-playground 저장소의 claude-code/mods에 샘플 모드를 둡니다. 저장소는 Apache-2.0이고 2026-10-27에 별 300개이며, 지원 없이 있는 그대로 공유하고 외부 기여는 받지 않는다고 밝힙니다. 각 샘플은 완전한 플러그인이고 _template/README.md를 바탕으로 만든 과정과 한계가 README에 적혀 있습니다. 앞 절의 사슬에 얹어 보면 세 샘플이 서로 다른 자리에 섭니다.

모드 서는 자리 하는 일
token-weather 화면만 컨텍스트 창이 찬 비율을 프롬프트 위 띠에 날씨로 그림. 모델을 부르지 않음
blast-radius tool.call에서 붙듦 위험한 셸 명령을 붙들고 영향 범위를 보인 뒤 Proceed·Cancel로 결정
replay-theater tool.call에서 기록만 직전 턴에서 Claude가 고친 파일을 diff 한 장씩 넘겨 봄

token-weather는 25% 미만을 맑음, 2549%를 구름, 5074%를 소나기, 75~89%를 폭풍, 90% 이상을 “곧 압축”으로 표시합니다. 수치는 $.session.usage()가 돌려주는 실제 값이고 상태줄과 같습니다. README의 시험은 42KB짜리 소스 파일 다섯 개가 든 저장소에서 200K 창을 가진 모델로 턴마다 더 읽게 한 기록으로, 첫 턴 뒤 18%(맑음), 파일 넷을 읽은 뒤 67%(소나기), 하나 더 읽은 뒤 81%(폭풍)입니다. 읽을 때 주의할 점은 이 퍼센트가 창 전체 대비라는 것입니다. Claude Code 자체의 “컨텍스트 사용” 알림은 그보다 낮은 자동 압축 시점 기준이라 시험에서는 띠가 81%일 때 알림이 90%였습니다. 띠는 턴이 끝날 때 한 번 갱신되고, 100만 토큰 창에서는 작업이 오래 맑음에 머뭅니다.

blast-radius는 rm -r, git reset --hard, git push --force, git clean, 마이그레이션 명령을 붙듭니다. 영향은 명령마다 다르게 보입니다. rm이면 지워질 파일 목록과 개수·크기, git reset --hard면 커밋되지 않은 변경 파일, 강제 푸시면 원격에만 있고 내 HEAD에는 없어서 사라질 커밋입니다. 패널이 열리면 Cancel에 초점이 가 있어서 Enter만 눌러도 거부됩니다. 한계는 README가 직접 적었습니다. 명령 글자를 읽을 뿐 셸을 완전히 해석하지 않아서 $(...), 별칭, eval, bash -c "...", xargs rm, find -delete, rm을 부르는 스크립트, timeout 5 rm 같은 감싼 형태는 잡지 못합니다. Bash 도구만 지켜보며 파일 편집은 붙들지 않고, 응답이 10분 없거나 Esc로 턴을 끊으면 명령은 거부되며, 한 번에 한 명령만 붙듭니다. 누른 뒤에는 명령이 쓰인 그대로 실행되고, 저자들도 “안전망이지 권한 체계가 아니다”라고 적습니다. 확실히 막아야 하는 경로는 권한 규칙으로 막습니다.

화면 쪽 한계도 있습니다. 사용자가 열지 않은 패널은 터미널이 144열 이상일 때 옆에 놓이고, 그보다 좁으면 프롬프트 위 띠로 대신합니다. 2.1.305부터는 옆에 붙는 패널이 터미널 폭의 45%를 넘지 않습니다. 띠는 모든 모드가 함께 쓰는 한 줄이라서, 같은 자리를 쓰는 다른 모드가 먼저 차지하면 버튼이 보이지 않을 수 있습니다. 모드를 만드는 쪽은 await next(e)의 결과를 자기 트리의 자식으로 넣으면 다른 모드의 그림을 지우지 않고 같이 보여 줄 수 있습니다.

replay-theater는 Edit·Write·MultiEdit 호출을 호출 시점에 기록하므로 사용자가 거부했거나 실패한 편집도 재생에 나옵니다. 한 단계는 변경 부분 주변 한 줄씩의 맥락만 두고 12줄까지 보이고, 기록은 메모리에만 있어서 재시작하거나 모드가 다시 로드되면 사라집니다. 열 때는 /replay를 치거나 ctrl+x 다음 Tab으로 띠에 초점을 옮긴 뒤 r을 누르고, 창 안에서는 n이 다음, p가 이전, c나 Esc가 닫기입니다.

써 보는 순서는 읽고, 한 세션만 불러 보고, 마음에 들면 설치하는 것입니다.

  1. 복제한다. git clone https://github.com/anthropics/claude-code-playground.git 다음 cd claude-code-playground/claude-code/mods로 들어갑니다.
  2. 무엇을 하는지 본다. claude plugin validate ./blast-radius를 실행해 hooks:와 calls: 줄을 읽습니다.
  3. 한 세션만 불러 본다. claude --plugin-dir ./blast-radius로 시작하고, 작업 폴더에서 rm -rf build를 시켜 Cancel을 눌렀을 때 파일이 남는지 확인합니다. /plugin 탭 아래에 1 mod active 줄이 나옵니다.
  4. 계속 쓸 것만 설치한다. claude plugin marketplace add ./로 그 폴더를 로컬 마켓플레이스로 등록하고 claude plugin install blast-radius@claude-code-playground-mods --scope user로 설치합니다. 마켓플레이스가 복제한 폴더를 가리키므로 폴더를 옮기거나 지우면 모드도 로드되지 않습니다.

사용자 모드 넷과 요구 버전

출시 뒤 한 달이 안 되어 공개 저장소가 여럿 나왔습니다. 모두 MIT 라이선스이고 별 수는 2026-10-27 기준이며, 이 가운데 어느 것도 Anthropic이 보증하지 않습니다. 모드 기능이 빠르게 자라는 중이라 요구 버전을 먼저 봅니다. 데스크톱 앱 Code 탭은 2.1.286부터, 터미널은 2.1.287부터 모드를 로드하고, claude-code-filetree는 2.1.306 이상을 요구합니다.

모드 별 요구 버전 하는 일
claude-skins 214 2.1.287+ 도구 호출 줄에 아이콘과 소요 시간, 표·코드·Mermaid 차트는 카드로, 편집은 diff 카드로 그림. /skin으로 바꾸고 /skin off로 복귀
claude-code-filetree 298 2.1.306+ IDE처럼 파일 트리를 두고 Claude가 읽고 고치는 파일을 표시. git 상태, diff, 트리맵, 분할 미리보기
claude-kit의 savvy-progress 274 저장소 claude-kit 프롬프트 위 진행 막대와 서브에이전트 패널(/agents-info)
cache-tax 277 2.1.287+, 2.1.304에서 검증 프롬프트 캐시가 식은 뒤 첫 메시지가 대화 전체를 다시 쓰는 비용을 보내기 전에 알림

앞의 둘은 화면을 바꾸는 모드입니다. claude-skins는 에이전트, 계획 모드, 할 일, 권한 프롬프트가 Claude Code의 원래 화면으로 남는다고 README가 밝히는데, 모드가 권한 프롬프트를 못 바꾸는 규칙과 맞습니다. 네트워크 호출은 하루 한 번 버전 확인뿐이라고 적혀 있고, 설치는 /plugin marketplace add hellosverre/claude-skins 다음 /plugin install skins@hellosverre-mods입니다. claude-code-filetree는 터미널에서 전체 화면 레이아웃(/tui fullscreen)과 110열 이상이 조건이고 git이 필요하며, 리눅스에서 실시간 갱신에는 inotifywait가 있어야 하고, 데스크톱 앱에는 분할 미리보기와 트리맵이 없습니다. 설치는 claude plugin marketplace add data-goblin/claude-code-filetree 다음 claude plugin install filetree@claude-code-filetree이고 켜고 끄는 명령은 /filetree입니다.

savvy-progress의 패널은 모델·노력 수준·진행·컨텍스트·예상 비용·시간을 보여 줍니다. 비용은 내장 가격표(2026-10-10 기준 목록 가격)로 계산한 대략치이며 청구액이 아니라고 README가 밝힙니다. 이 모드는 모델이 부르는 도구(mcp__savvy-progress__progress, step)도 같이 등록해서 작업을 나눠 맡기는 에이전트가 진행 상황을 보고하는 길로 씁니다. 명령과 도구를 모드 하나가 함께 가질 수 있다는 예입니다. 같은 저장소의 session-band는 턴 수, 속도, 비용, 프롬프트 캐시 카운트다운, 컨텍스트를 띠 하나에 모으고 캐시가 만료되기 전에 알려 줍니다.

cache-tax는 숫자로 이유를 보이는 모드입니다. 컨텍스트가 5만 토큰 이상이고 캐시가 식은 상태에서 처음 보내는 메시지를 한 번 막고 예상 쓰기 비용을 보인 뒤, 같은 메시지를 다시 보내면 통과시킵니다. README의 예로 33만 토큰 세션은 예상이 6.61달러였고 실제 캐시 쓰기는 6.28달러였습니다. API 단가로 환산한 값이며 구독에서 추가로 청구되는 돈이 아닙니다. 자리를 비울 때는 /keepwarm(기본 6시간, /keepwarm 90m처럼 기간을 주고 always와 off도 있음)으로 유휴 50분 뒤 대화 기록을 도구 없이 한 번 더 보내 캐시를 데웁니다. 핑 한 번의 예는 7.5만 토큰을 읽는 데 0.02달러였습니다. 읽기 값이 0이 되거나 쓰기가 읽기의 10%에 이르면 데우기를 멈추고, 식은 쓰기를 감지하면 최소 3시간(always는 6시간) 데웁니다. 서버가 바쁘다는 응답(429, 529, 5xx)은 1, 2, 4분 뒤에 다시 시도합니다.

구독 계정은 메인 대화의 캐시가 기본 1시간이지만, API 키나 클라우드 제공자로 로그인하면 기본 5분이라 promptCacheTtl을 1h로 바꿔야 한다고 README와 Claude Code 설명서가 안내합니다. Claude Code에도 비슷한 시점에 도는 기능이 있습니다. 2.1.299에서 들어온 유휴 압축은 20만 토큰 이상의 대화가 약 54분 방치되면 1시간 캐시가 끝나기 전에 압축해서, 다음 메시지가 전체를 다시 처리하지 않게 합니다. 최소 토큰은 CLAUDE_CODE_IDLE_COMPACT_MIN_TOKENS로 바꾸고, "idleCompaction": false로 끄며, 2.1.302부터는 /config에 Idle compaction 행도 있습니다. 50분의 핑과 54분의 압축이 가까우니 같이 켜면 겹치는지 따져 봐야 합니다. 데우는 요청은 토큰을 쓰고, 작성자는 같은 일을 하는 설정 훅과 이 모드를 둘 다 켜면 보내기 전마다 가드가 둘이 되니 하나만 쓰라고 권합니다.

말로 설명해 만든 모드의 수명

직접 코드를 쓰지 않아도 됩니다. 설명서는 대화형 세션에서 원하는 모드를 평소 말투로 설명하라고 안내하며, 예로 든 문장이 “현재 git 브랜치를 프롬프트 위에 보여 주는 모드를 만들어 줘”입니다. Claude Code에 내장된 plugin-authoring 스킬이 이벤트와 메서드 목록, 로드되는 방식을 알려 줘서 Claude가 코드를 씁니다. 2.1.300부터 이 스킬은 공유 단계에서 모드의 파일에 든 키를 먼저 훑고, 2.1.304에서 모드를 켜겠느냐는 질문이 평문으로 바뀌고 공유가 비밀 검사에서 멈추던 문제가 고쳐졌습니다.

  1. 설명한다. 직접 불러 쓰려면 /plugin-authoring을 입력합니다. 모드는 ~/.claude/dev-mods/<세션 ID>/ 아래에 만들어지고, 기본·acceptEdits 권한 모드에서는 이 폴더가 보호 경로라서 파일마다 승인 요청이 뜹니다.
  2. 승인한다. 첫 파일을 저장할 때 이 세션에서 핫 리로드를 켤지 묻고, "이 세션에서 켜기"를 고르면 답은 세션을 다시 열어도 유지됩니다. "지금은 아니요"를 고르면 파일만 남고 다음에 그 세션을 시작할 때 로드됩니다.
  3. 확인한다. /plugin의 Installed 탭에 모드가 나타납니다.
  4. 고친다. 마음에 안 드는 부분은 말로 다시 시킵니다. 로드에 실패하면 모드 이름으로 시작하는 줄이 대화에 찍히는데, Claude는 그 줄을 보지 못하니 다음 메시지에 붙여 넣어야 원인을 압니다.

이렇게 만든 모드는 임시입니다. 그 세션에서만 로드되고 폴더도 cleanupPeriodDays가 지나면 삭제됩니다. 계속 쓰려면 폴더를 다른 곳(예: ~/mods/git-branch)으로 복사한 뒤 claude --plugin-dir ~/mods/git-branch로 불러 쓰거나 마켓플레이스에 올려 설치합니다. 몇 사람에게는 플러그인 폴더나 .zip을, 팀에는 팀 마켓플레이스와 저장소 설정을, 조직 전체에는 관리자가 관리 설정의 조직 모드를, 누구에게나는 공개 마켓플레이스 저장소나 Anthropic 디렉터리 제출을 씁니다.

Claude가 쓴 모드가 로드되려면 승인을 받을 사람이 있는 대화형 세션이고, 신뢰 대화상자에 동의한 작업 폴더이며, 모드가 허용된 설정이어야 합니다. claude -p 실행과 dontAsk 권한 모드, 신뢰하지 않은 작업 폴더, --safe-mode, --bare, disableAllHooks, 관리 설정의 차단 중 하나라도 있으면 로드되지 않습니다. 공유할 때 걸리는 규칙도 있습니다. claude plugin validate는 claude-로 시작하는 것처럼 Anthropic이 만든 것으로 보이는 이름을 실패로 처리합니다. 설치된 플러그인은 버전별로 캐시되니, 개발 중에는 설치본이 아니라 --plugin-dir로 폴더를 직접 가리켜야 고친 것이 반영되고, 설치본에 반영하려면 버전을 올려 다시 설치합니다.

first-mod 한 벌: 파일, 재로드, 시험

원리를 손으로 익히고 싶다면 설명서의 튜토리얼 모드 first-mod가 본보기입니다. Node.js도 번들러도 필요 없습니다. Claude Code가 .js와 .ts 파일을 바로 읽습니다. 그림 4의 왼쪽이 그 폴더이고, 오른쪽은 모드와 관련해 Claude Code가 보관하는 위치입니다.

first-mod 폴더의 plugin.json, hooks.json, register.js와 테스트·타입 파일, 그리고 Claude Code가 보관하는 dev-mods 폴더, plugins/store, settings.json 위치 그림 4. 직접 쓰는 파일과 Claude Code가 보관하는 위치

hooks.json의 modules 키({"modules": ["./register.js"]})가 있어야 플러그인이 모드가 됩니다. register.js는 앞 절의 calls 예제에 훅 두 개를 더합니다. 세션이 시작될 때 /tally 명령을 등록하는 session.start, 그리고 /tally를 칠 때만 호출 수를 글로 돌려주는 command.run(거름 조건 { command: 'tally' })입니다. 이 훅은 next를 부르지 않고 직접 답하는 방식입니다. 이벤트 session.start는 로드된 모드마다 한 번, 모드가 다시 로드된 뒤에도 한 번 나며 /clear나 /resume 뒤에는 나지 않습니다.

claude --plugin-dir ./first-mod
# "이 폴더의 파일을 나열하고 README를 읽어 줘" 같은 요청을 보내면
# 스피너 옆에  Thinking · tool calls: 2…  가 올라간다
# 끝난 뒤 /tally 를 치면
#   first-mod: Claude has made 2 tool calls since this mod loaded
claude -p "/tally" --plugin-dir ./first-mod    # 세션 없이 명령만 확인

--plugin-dir로 연 폴더는 Claude Code가 지켜보다가 파일이 바뀌면 훅 모듈을 다시 불러옵니다. 스피너 글자를 tool calls에서 tools used로 고쳐 저장하면 세션을 끄지 않고 바로 반영됩니다. 재로드 때는 register가 다시 실행되고 모듈 변수는 처음 값으로 돌아가 calls가 0이 되며, 대화에는 모드 이름과 훅 목록이 한 줄로 찍힙니다. 저장이 깨졌다면 이전 버전이 계속 돌고 reload failed, the previous version stays loaded 줄이 나옵니다(2.1.304부터는 실패한 재로드가 남은 버전의 도구와 명령을 지우지 않습니다). 값을 남기려면 저장 위치를 고릅니다.

저장 위치 언제까지 쓰는 곳
모듈 변수 모듈이 재로드될 때까지(개발 중엔 저장할 때마다) 잃어도 되는 값
$.state 세션이 끝나거나 /clear, /resume, /branch를 칠 때까지 재로드 뒤에도 유지돼야 하는 그림 상태
$.store 모드가 지우거나, 어떤 세션도 cleanupPeriodDays 동안 쓰지 않을 때까지 설정·기록처럼 다음에도 찾을 값. ~/.claude/plugins/store/의 JSON 파일

작성 도중 확인하는 도구도 있습니다. 대화형 세션에서 --plugin-dir로 모드를 불러오면 Claude Code가 .claude-plugin/types/에 지금 버전의 이벤트·메서드 타입 선언을 써 줘서 편집기가 자동완성과 타입 검사를 합니다. $.state를 쓰거나 $에 이름공간을 더하는 모드는 types/index.d.ts도 있어야 합니다. 정적 분석(claude plugin validate)이 모든 훅과 호출을 찾도록 지켜야 할 규칙은 네 가지입니다. 이벤트 이름은 'tool.call'처럼 문자열 리터럴로 쓰고(변수나 반복문은 실패), $를 변수에 담거나 구조 분해하지 않으며(const ui = $.ui는 실패), 같은 이벤트를 매처 없이 두 번 등록하지 않고, 파일은 import를 쓰는 ES 모듈로 씁니다.

claude plugin test는 세션·로그인·네트워크 없이 .test.ts·.test.tsx 파일을 돌립니다. 테스트가 도구 호출 이벤트를 쏘고 /tally의 답을 검사하는 식이며, 하나에 5초 제한이 있고 실패하면 종료 코드 1을 돌려줍니다. 동작하지 않을 때는 claude --debug-file ./mod-debug.log --plugin-dir ./first-mod로 로그를 남기고 모드 이름으로 걸러 봅니다. 로드된 모드는 hooks module first-mod@inline loaded …; events: … 줄로 나옵니다. 플래그를 줄 수 없는 앱에서는 환경 변수 CLAUDE_CODE_PLUGIN_DIRS(절대 경로를 :로 구분)가 --plugin-dir와 같은 일을 하고, 오래 도는 비대화형 세션이 저장에 반응해 다시 불러오게 하려면 CLAUDE_CODE_PLUGIN_DIR_WATCH=1을 둡니다.

설치 전 validate 두 줄과 끄는 단계

설명서는 모드를 “내 권한으로 실행되는 코드”라고 부릅니다. 로드된 모드는 내 계정이 닿는 어느 곳이든 파일을 읽고 쓰고, 프로그램을 실행하고 네트워크에 연결하며, 환경 변수와 설정 파일에 든 API 키도 읽을 수 있습니다. 내가 보내는 모든 프롬프트와 Claude의 모든 도구 호출을 보며, 프롬프트나 도구 호출을 고쳐 쓰거나 내가 묻기 전에 도구 호출을 승인하고, 내 요금제나 API 키로 모델을 부를 수도 있습니다. 샌드박싱을 켜도 격리되는 것은 Claude가 실행하는 Bash 명령뿐이고 모드가 띄운 프로세스는 밖에서 돕니다.

권한 규칙은 모드 안의 호출을 막지 못한다. 권한 규칙과 PreToolUse 훅은 Claude의 도구 호출에만 적용되고 모드가 $.fs나 $.process로 직접 하는 일에는 적용되지 않습니다. Read(.env)를 거부해 두어도 모드는 $.fs.read로 그 파일을 읽거나 그 파일을 읽는 프로그램을 띄울 수 있습니다. 그런 호출까지 제한하려면 사용자 모드를 못 불러오게 하거나 정책 모드로 직접 걸러야 합니다. 정책 모드는 plugin.register 이벤트로 다른 모드의 e.uses.calls를 읽어 로드를 거부하거나, fs.write 같은 호출 이름으로 훅을 걸어 기록하고 막을 수 있습니다.

그러니 설치 전에 모드 폴더를 받아 두고 셸에서 claude plugin validate ./some-mod를 실행합니다. 코드를 실행하지 않고 정적으로 분석해서, 출력의 hooks:와 calls: 줄에 그 모드가 처리하는 이벤트와 Claude Code에 요청하는 일이 나옵니다. 분석이 읽을 수 없는 방식으로 $를 쓰는 모드는 Claude Code가 로드를 거부합니다. 아래 그림은 튜토리얼 모드의 출력 형태와, 설명서가 따져 보라고 든 calls: 줄의 예입니다.

claude plugin validate의 출력으로, first-mod의 hooks와 calls 줄과 통과 표시, 그리고 파일 읽기와 네트워크 호출이 든 some-mod의 calls 줄 그림 5. validate의 hooks:와 calls: 줄(설명서의 출력 예시)

hooks: 줄에서는 tool.call과 prompt.submit이 보이면 그 모드가 모든 도구 호출과 프롬프트를 보고 고칠 수 있다는 뜻입니다. session.append는 대화의 각 줄을 저장 전에 고칠 수 있고, ui.render{component=AskUserQuestion}는 Claude가 묻는 대화상자를 그릴 수 있으며, tool.check는 권한 프롬프트가 뜨기 전에 호출을 승인하거나 거부할 수 있다는 뜻입니다. calls: 줄에서 볼 것은 다음과 같습니다.

호출 뜻
$.fs.read, $.fs.write 사용자가 닿는 어디서든 파일을 읽고 씀
$.process.run, $.process.spawn 사용자 권한으로 프로그램을 실행
$.http.fetch 네트워크 요청
$.env.get, $.settings.read 환경 변수와 설정을 읽음(키가 들어 있을 수 있음). env reads: 줄이 변수 이름을 알려 줌
$.env.set Claude Code와 이후 실행되는 모든 명령·MCP 서버의 환경을 바꿈. env writes: 줄이 이름을 알려 줌
$.mcp.call 연결된 MCP 서버의 도구를 호출
$.model.complete 내 요금제나 API 키로 모델을 부름
$.prompt.submit 프롬프트를 제출하고 내가 직접 쓴 것처럼 보낼 수도 있음
$.session.send 다른 세션·서브에이전트·팀원의 Claude가 읽는 메시지를 보냄

파일을 읽거나 네트워크를 쓰는 호출이 있는데 모드 소개와 어울리지 않으면 그 모드는 건너뜁니다. 그다음 --plugin-dir로 한 세션만 불러 보고 /plugin 화면 탭 아래의 연한 줄이 1 mod active · 이름으로 나오는지 확인합니다. validate는 코드가 하는 일의 범위를 알려 주지만, 그 호출로 무엇을 하는지(어느 주소로 보내는지, 어느 파일을 읽는지)는 알려 주지 않으니 호출이 수상하면 소스를 읽어야 합니다. 2.1.304에서 훅이 어디서 돌지를 예측하던 안내 줄은 validate 출력에서 빠졌습니다.

끄는 방법은 범위별로 있습니다. 모드 하나는 /plugin의 Installed 탭에서 비활성화하거나 삭제하고, 한 세션 전체는 claude --safe-mode로 시작해 설치한 모드를 모두 끕니다. 모든 세션에서 끄려면 ~/.claude/settings.json에 "disableAllHooks": true를 두는데, 설정 훅과 사용자 정의 상태줄도 함께 꺼지고 조직이 관리하는 것은 계속 돕니다. 조직 전체는 관리 설정의 allowManagedModsOnly입니다. 이 방법들은 앞의 표에 나온 내장 모드를 끄지 못하고, 내장 모드는 표의 방법으로 하나씩 끕니다.

어디서 그려지는지도 알아 둡니다. 훅은 거의 모든 곳에서 실행되지만 화면은 다릅니다. 터미널의 claude와 데스크톱 앱 Code 탭(WSL 세션 제외)에서는 화면까지 그려집니다. WSL 세션에서는 플러그인이 없어 훅도 돌지 않습니다. VS Code 확장 채팅 패널은 확장 2.1.299 이상이어야 그려지고(Bedrock처럼 기능 플래그를 받지 않는 세션은 2.1.304 이상), claude -p와 Agent SDK에서는 훅이 실행되지만 화면은 그려지지 않으며, 클라우드 세션은 훅이 돌더라도 화면이 없습니다. 원격 제어로 쓰는 세션은 내 기기에서 훅이 돌고 화면은 내 기기의 터미널에 나옵니다. 앱마다 그리는 요소도 달라서, VS Code에서는 Button의 hotkey와 Text의 size·mono 같은 속성이 무시되고 터미널도 글꼴 크기가 하나뿐이라 size를 무시하므로, 터미널에서 쓰던 모드가 다른 앱에서는 일부 요소만 보일 수 있습니다.

#Claude Code#모드#플러그인#AI 에이전트#개발 도구