Claude Code가 재사용 가능한 프롬프트 워크플로를 발견하고, 로드하고, 파싱하고, 실행하는 방식
스킬은 Claude Code가 발견하고 실행할 수 있는, 이름이 붙은 재사용 가능한 프롬프트 워크플로입니다. 스킬은 YAML 프런트매터 헤더가 있는 SKILL.md Markdown 파일로 저장되거나, bundled skills 형태로 CLI 바이너리에 직접 컴파일됩니다.
사용자 관점에서 스킬은 슬래시 명령입니다. /commit, /simplify, /review-pr 같은 형태죠. Claude 관점에서 스킬 호출은 Skill tool(Skill)을 부르는 일이며, 이 도구는 전체 프롬프트를 로드하고, 인자를 치환하고, 필요하면 인라인 셸 블록을 실행한 뒤, 결과를 대화에 주입합니다.
모든 스킬은 Claude Code가 시작되는 순간부터 Claude가 실제로 그것을 실행하는 순간까지 여섯 단계를 거칩니다.
시작 시점에 getSkillDirCommands()는 네 위치를 병렬로 순회하고, 레거시 .claude/commands/ 디렉터리도 함께 로드합니다. 심볼릭 링크는 realpath()로 해석되므로, 중복 파일은 내용이 같고 경로만 달라도 Claude에 도달하기 전에 제거됩니다.
각 skill-name/SKILL.md 파일을 읽습니다. 토큰 수는 프런트매터만 기준으로 추정하며(name + description + whenToUse), 전체 본문은 시작 시 토큰화하지 않습니다. 덕분에 스킬이 수백 개여도 목록을 빠르게 보여줄 수 있습니다. 목록 자체도 컨텍스트 윈도우의 1%로 예산이 제한됩니다.
프런트매터는 Command 객체로 파싱됩니다. 여기서 description, allowed tools, argument hints, model override, hooks, paths, effort, shell 같은 모든 필드를 검증합니다. paths 필드가 있는 스킬은 조건부 스킬이 되며, 저장은 되지만 사용자가 일치하는 파일을 열기 전까지는 Claude에 노출되지 않습니다.
스킬이 호출되면 본문에서 다음 순서로 인자 치환이 일어납니다.
$foo, $bar (arguments 프런트매터의 위치 기반 매핑)$ARGUMENTS[0], $0, $1$ARGUMENTSARGUMENTS: ... 형태로 뒤에 붙임!`command` 또는 ```! 블록(로컬 스킬만 가능, MCP 스킬은 차단됨)${CLAUDE_SKILL_DIR}, ${CLAUDE_SESSION_ID}스킬은 프런트매터의 context: fork 여부에 따라 두 방식 중 하나로 실행됩니다.
runAgent())에서 실행됩니다. 상위 대화는 최종 텍스트 출력만 받습니다. 독립적인 작업에 적합합니다.Skill tool은 ToolResult를 반환합니다. inline 스킬에서는 결과에 allowedTools와 선택적 model 오버라이드가 담겨, 이번 턴의 이후 도구 호출에 적용됩니다. forked 스킬에서는 Done 표시가 나오고, 서브 에이전트의 출력이 다시 컨텍스트로 들어갑니다.
스킬은 네 가지 서로 다른 출처에서 올 수 있습니다. 두 출처가 같은 이름의 스킬을 정의하면 먼저 로드된 쪽이 이깁니다 (managed > user > project > bundled). 중복 제거는 이름이 아니라 해석된 파일 경로 기준이므로, 심볼릭 링크된 스킬이 실제 파일을 가릴 수도 있습니다.
IT가 배포하는 엔터프라이즈 관리 스킬입니다. CLAUDE_CODE_DISABLE_POLICY_SKILLS로 사용자 오버라이드를 막도록 잠글 수 있습니다.
프로젝트를 가로질러 쓰는 개인 스킬 라이브러리입니다. Claude Code를 실행하는 어디서나 사용할 수 있습니다. chokidar로 변경 사항을 실시간 감시합니다.
리포지토리에 커밋되는, 저장소 범위의 스킬입니다. Claude는 프로젝트 루트에서 위쪽으로 순회하므로, 중첩된 .claude/skills/ 디렉터리도 파일을 열면서 동적으로 발견됩니다.
/simplify, /loop, /remember처럼 바이너리에 함께 들어가는 스킬입니다. 시작 시 registerBundledSkill()로 등록됩니다. 일부는 feature-flagged 상태입니다.
MCP 서버가 스킬을 노출하면 (loadedFrom === 'mcp'로 감지), 연결 시점에 가져와 명령 레지스트리에 추가합니다. MCP skills는 같은 프런트매터 스키마를 따르지만 셸 주입은 차단되며, 원격의 신뢰할 수 없는 콘텐츠에 대해서는 !backtick과 ```! 블록을 절대 실행하지 않습니다.
.claude/commands/예전의 /commands/ 디렉터리도 아직 지원됩니다 (loadedFrom: 'commands_DEPRECATED'). 단일 .md 파일과 skill-name/SKILL.md 디렉터리 형식을 모두 받습니다. 새 작업은 .claude/skills/를 써야 합니다.
skillChangeDetector 모듈은 chokidar로 스킬 디렉터리를 감시합니다. 어떤 SKILL.md든 바뀌면 300 ms 디바운스를 거친 뒤 ConfigChange 훅을 실행하고, 모든 메모이제이션 캐시를 비워 다음 호출에서 새 스킬을 보게 합니다. Bun에서는 알려진 데드락 버그를 피하려고 FSWatcher 대신 stat-polling을 사용합니다.
스킬 파일은 ---로 구분된 선택적 YAML 프런트매터 블록이 붙은 일반 Markdown입니다. 파일은 반드시 <skill-name>/SKILL.md 위치에 있어야 하며, 디렉터리 이름이 슬래시 명령 이름이 됩니다.
| 필드 | 타입 | 설명 |
|---|---|---|
name | string | 디렉터리에서 유도된 표시 이름을 덮어씁니다 |
description | string | 스킬 목록에 보이는 한 줄 요약입니다. 없으면 본문의 첫 문단을 사용합니다. |
when_to_use | string | Claude를 위한 자세한 트리거 설명입니다. 목록에서는 description과 함께 표시됩니다. |
allowed-tools | list | 이 스킬 실행 중 부여되는 도구 권한 패턴입니다. Bash 대신 Bash(gh:*)처럼 가능한 한 좁게 지정합니다. |
argument-hint | string | CLI 자동완성에서 플레이스홀더 힌트로 표시됩니다. |
arguments | string or list | 이름 붙은 인자 식별자입니다. 본문의 $name 치환에 위치 기반으로 매핑됩니다. |
context | fork | 스킬을 격리된 서브 에이전트로 실행합니다. inline이 기본값이며, 그 경우 생략합니다. |
model | string | 이 스킬 실행에 사용할 모델 별칭입니다. inherit는 세션 기본값을 뜻합니다. |
effort | low/medium/high/int | 이 스킬 실행 시 적용되는 사고 예산입니다. |
version | string | 정보용 버전 태그이며, 런타임 동작에는 영향이 없습니다. |
user-invocable | bool | 기본값은 true입니다. false로 두면 /skills 메뉴에서 숨겨집니다 (에이전트 전용 스킬). |
paths | glob string(s) | 조건부 활성화입니다. 일치하는 파일이 열릴 때만 스킬이 나타납니다. |
hooks | object | 도구 사용 전후의 생명주기 훅입니다. 로드 시 HooksSchema로 검증합니다. |
agent | string | fork 시 사용하는 에이전트 유형 식별자입니다. 예: code, browser. |
shell | object | 스킬 본문 안에서 !backtick 실행에 쓰는 셸 인터프리터 설정입니다. |
disable-model-invocation | bool | true이면 이 스킬은 Skill tool로 호출할 수 없고, 슬래시 명령으로만 호출할 수 있습니다. |
paths 프런트매터 필드가 있는 스킬은 조건부 스킬입니다. 시작할 때 로드되지만, 경로가 glob 패턴 중 하나와 맞는 파일을 사용자가 열거나 수정하기 전까지는 Claude에 노출되지 않습니다.
덕분에 결제 전용, 인프라 전용, iOS 전용 워크플로를 실제로 관련이 생기기 전까지는 숨겨 둘 수 있고, 관련 없는 작업에서 스킬 목록이 시끄러워지는 일을 막을 수 있습니다.
--- paths: src/payments/** description: "Stripe refund workflow" --- # Stripe Refund Stripe API로 환불을 처리하는 단계...
패턴은 .gitignore 및 CLAUDE.md 조건부 규칙과 같은 문법을 씁니다. ** (전체 매치) 패턴은 무조건 활성로 취급되며, 이는 paths를 생략한 것과 같습니다.
조건부 스킬은 동적 스킬 발견과도 상호작용합니다. 모델이 프로젝트 트리의 더 깊은 파일을 읽어 갈수록 Claude Code는 cwd 방향으로 위쪽을 탐색하고, 시작 시에는 찾지 못했던 추가 .claude/skills/ 디렉터리를 발견할 수 있습니다. Gitignored 디렉터리는 건너뜁니다.
Bundled skills는 Claude Code 바이너리 안에 함께 들어가며, 시작 시 registerBundledSkill()를 통해 등록됩니다. 파일 기반 스킬과 같은 BundledSkillDefinition 인터페이스를 따르지만, SKILL.md 파일 대신 getPromptForCommand(args, context) 함수를 제공합니다.
잘 알려진 bundled skills는 다음과 같습니다.
/simplify, 재사용성, 품질, 효율성을 보는 세 개의 병렬 리뷰 에이전트를 띄웁니다/loop, interval과 프롬프트를 파싱해 cron job을 만듭니다(feature-flagged)/remember, /verify, /debug, /stuck/skillify, 현재 세션에 대해 질문하고 SKILL.md를 작성합니다(Anthropic 내부용)Bundled skills는 files 속성으로 참조 파일을 함께 넣을 수도 있습니다. 이런 파일은 첫 호출 시 프로세스별 nonce 디렉터리로 추출되며, 심볼릭 링크 공격을 막기 위해 O_EXCL | O_NOFOLLOW | 0o600 플래그를 사용합니다. 그리고 Claude가 이를 Read/Grep할 수 있도록 프롬프트 앞에 Base directory for this skill: ... 접두사가 붙습니다.
일부 bundled skills는 feature flag (feature('AGENT_TRIGGERS'), feature('KAIROS'))나 런타임 검사 (isKairosCronEnabled())에 따라 조건부로 활성화됩니다. 디스크의 SKILL.md를 수정하지 않고도 추가하거나 제거할 수 있습니다.
// bundled skill 등록
registerBundledSkill({
name: 'simplify',
description: 'Review changed code for reuse, quality, and efficiency.',
userInvocable: true,
async getPromptForCommand(args) {
return [{ type: 'text', text: SIMPLIFY_PROMPT }]
},
})
MCP 서버는 도구뿐 아니라 스킬도 노출할 수 있습니다. Claude Code가 MCP_SKILLS 기능이 켜진 MCP 서버에 연결되면 fetchMcpSkillsForClient()를 호출하고, 여기서 파일 기반 스킬에 쓰는 것과 같은 parseSkillFrontmatterFields() 및 createSkillCommand() 함수로 스킬 프런트매터를 파싱합니다.
MCP skills는 SkillsMenu 안의 별도 "MCP skills" 그룹에 나타납니다. 이름은 server-name:skill-name 규칙을 따릅니다.
로컬 스킬과의 핵심 차이는 다음과 같습니다.
!backtick과 ```! 블록은 조용히 건너뜁니다. 코드 주석에는 이렇게 적혀 있습니다. "보안: MCP skills는 원격이며 신뢰할 수 없으므로, markdown 본문 안의 인라인 셸 명령은 절대 실행하지 않는다."AppState.mcp.commands에 저장됩니다. Skill tool은 호출 시점에 getAllCommands()로 이들을 합칩니다.mcpSkillBuilders.ts는 createSkillCommand와 parseSkillFrontmatterFields 참조를 들고 있는 의존 그래프 말단 모듈이며, client.ts → mcpSkills.ts → loadSkillsDir.ts 사이의 순환 import 문제를 해결합니다.// getAllCommands()가 MCP와 로컬 스킬을 합치는 방식 const mcpSkills = context.getAppState().mcp.commands .filter(cmd => cmd.type === 'prompt' && cmd.loadedFrom === 'mcp') const localCommands = await getCommands(getProjectRoot()) return uniqBy([...localCommands, ...mcpSkills], 'name')
모든 Skill tool 호출은 checkPermissions()를 거칩니다. 결정 흐름은 다음과 같습니다.
deny 규칙이 스킬 이름과 맞거나 :* 접두 규칙과 맞으면 즉시 차단합니다.allow 규칙이 맞으면 묻지 않고 진행합니다.:*) allow 규칙을 로컬 설정에 추가하라는 제안을 보여주며 확인을 받습니다.즉, 단순 정보성 스킬은 권한 대화상자 없이 실행되지만, Bash 접근 권한을 얻거나 모델을 오버라이드하는 스킬은 명시적 승인이 필요합니다.
인자 파싱에는 shell-quote를 사용하므로 따옴표로 감싼 문자열이 하나의 토큰으로 처리됩니다.
/myskill "hello world" foo → ["hello world", "foo"]
지원되는 치환 패턴은 세 가지이며, 순서대로 처리됩니다.
| 패턴 | 무엇으로 해석되는가 |
|---|---|
$foo, $bar | 위치 기준 이름 붙은 인자(arguments: foo bar 프런트매터 필요) |
$ARGUMENTS[0], $0 | 인덱스 위치 인자 |
$ARGUMENTS | 원본 전체 인자 문자열 |
스킬 본문에 플레이스홀더가 하나도 없는데 사용자가 인자를 넘기면, 인자는 ARGUMENTS: <args> 형태로 자동으로 뒤에 붙습니다. 덕분에 플레이스홀더를 선언하지 않은 단순 스킬에서도 인자가 조용히 버려지지 않습니다.
<skill-name>/SKILL.md에 있습니다. 디렉터리 이름이 슬래시 명령이 됩니다. 모든 메타데이터는 YAML 프런트매터가 제어하고, Markdown 본문이 프롬프트입니다.
context: fork, 격리된 서브 에이전트)로 실행됩니다. fork는 독립적인 작업에, inline은 중간에 방향을 잡아야 하는 작업에 더 적합합니다.
!backtick 블록을 실행할 수 있는 것은 로컬(managed/user/project/bundled) 스킬뿐입니다. 이것은 소스 코드에 박혀 있는 강한 보안 경계입니다.
paths 프런트매터는 스킬을 조건부로 만듭니다. 일치하는 파일이 열리기 전까지는 보이지 않습니다. 도메인 특화 워크플로를 관련 있을 때만 노출할 때 쓰면 좋습니다.
Q1, ~/.claude/skills/deploy/SKILL.md에 있는 스킬 하나와, 현재 프로젝트의 .claude/skills/deploy/SKILL.md에 있는 스킬 하나가 있습니다. Claude는 어느 쪽을 사용할까요?
Q2, 대화 기록을 공유하지 않는 격리된 컨텍스트에서 데이터베이스 마이그레이션 스크립트를 실행하는 스킬을 만들고 싶습니다. 어떤 프런트매터 필드를 설정해야 할까요?
Q3, MCP 서버가 !`rm -rf /tmp/cache`가 들어 있는 SKILL.md를 보냈습니다. 무슨 일이 일어날까요?
Q4, 스킬의 프런트매터에 paths: src/payments/**를 추가했습니다. 이 스킬은 언제 Claude에게 보이게 될까요?
Q5, SKILL.md 본문에 $ARGUMENTS 플레이스홀더가 없습니다. 사용자가 /myskill feature-123를 실행했습니다. 이 인자는 어떻게 될까요?
Q6, 사용자 정의 스킬이 200개 있습니다. 전체 목록이 컨텍스트 윈도우 예산의 1%를 넘었습니다. 어떤 스킬 설명이 절대 잘리지 않을까요?