📚 엔지니어 · 세션 · 에이전틱 개발 (수정중) · L05

L28 — 자연어로 스킬을 부를 때 오발동을 막는 법: description floor(hook)와 실행-직전 가드 (수정중)

작성일자 2026-07-01  ·  수정일자 2026-07-01  ·  강의차수 L05  ·  예상소요 30분

목표 이 강의가 끝나면 학습자가 "왜 스킬 오발동의 원인은 비슷한 이름이 아니라 겹치는 description 인지", 그리고 그 위험을 hook(입구 검증) 과 실행-직전 확인(행동 직전 가드) 으로 나눠 막는 이유를 설명할 수 있다.

이번 강의는 L11 Skills·L13 Hooks 위에 얹힌다. 스킬이 뭔지, hook 이 exit 2 로 어떻게 막는지는 그 두 강의에서 다뤘으니, 여기서는 "스킬이 늘어나면 생기는 문제" 와 그걸 standarda-template 이 어떻게 정리했는지(PR #29·#30)에 집중한다.


1. 문제: 자연어 호출은 "이름"이 아니라 "description"으로 결정된다

L11 에서 스킬은 /release 처럼 슬래시로도, "배포해줘" 처럼 자연어로도 부를 수 있다고 했다. 그런데 스킬이 2개, 5개, 11개로 늘어나면 새 문제가 생긴다 — Claude 가 자연어 요청에서 "어떤 스킬을 쓸지" 고를 때 1차 신호로 삼는 건 스킬 이름이 아니라 description 이다.

  • 이름이 비슷해도 description 의 "무엇을 / 언제 쓰는지"가 또렷이 갈리면 오발동은 거의 없다.
  • 이름이 달라도 description 이 두루뭉술하게 겹치면 엉뚱한 스킬이 실행된다.

➡️ 위험의 원인은 "비슷한 이름"이 아니라 "겹치는 용도 설명"이다. 그래서 대책도 전부 description 을 또렷하게 만드는 데 모인다.

예: client-test-guide(고객용 테스트 가이드)와 client-usage-guide(고객용 사용 가이드)는 이름이 거의 같다. 둘이 안 헷갈리는 건 이름 덕이 아니라, 각 description 이 "테스트 절차" vs "사용법"으로 용도를 갈라 놓았기 때문이다.


2. PR #29 — description 의 최소 품질을 hook 으로 강제한다 (floor)

"description 을 잘 쓰자"는 권고만으로는 언젠가 누군가 description: 리포트를 보냅니다. 같은 모호한 한 줄로 스킬을 저장한다. 그 순간부터 자연어 호출은 오발동하기 시작한다. standarda-template PR #29 는 이걸 hook 으로 강제했다.

.claude/hooks/check-skill-description.sh — PreToolUse 의 Write|Edit 가 .claude/skills/*/SKILL.md 를 저장할 때만 발동해서, frontmatter 의 name/description 을 검사한다:

# .claude/hooks/check-skill-description.sh (발췌)
# .claude/skills/*/SKILL.md 가 아니면 통과
case "$FILE" in
  *.claude/skills/*/SKILL.md) ;;
  *) exit 0 ;;
esac
...
DESC_VAL=$(printf '%s' "$DESC_LINE" | sed -E 's/^[Dd]escription:[[:space:]]*//')
if [ "${#DESC_VAL}" -lt 20 ]; then
  fail "description 이 너무 짧습니다. 무엇을·언제 쓰는지 구체적으로 적으세요"
fi

fail() 은 stderr 로 안내를 찍고 exit 2 로 저장 자체를 거부한다 — L13 에서 본 그 exit 2 차단 패턴이다. 차단 조건은 세 가지다:

  • frontmatter 에 name 이 없다 (Write=파일 전체일 때만 검사)
  • frontmatter 에 description 이 없다
  • description 값이 20자 미만이다 (빈/한두 단어 수준)

.claude/settings.json 에는 이렇게 등록돼 있다:

// .claude/settings.json — Write|Edit matcher
{
  "matcher": "Write|Edit",
  "hooks": [
    { "type": "command", "command": ".claude/hooks/check-skill-description.sh" }
  ]
}

한 가지 섬세한 점: Edit 인데 description 줄을 안 건드리면 검사를 건너뛴다. 스킬 본문만 고치는 수정까지 매번 막으면 마찰만 커지기 때문이다 — hook 은 new_string 에 ^description: 이 있을 때만 검사한다.

이건 L13 에서 말한 hook 의 보수성(오탐을 줄이려 검사 범위를 좁게)과 같은 태도다. "모호한 description 으로 스킬이 저장되는 것" 이라는 가장 흔한 사고 원인 하나만 결정론적으로 막는 floor 이지, description 이 완벽한지까지 보장하진 않는다.


3. description 작성 표준 — hook 을 통과해도 이걸로 쓴다

hook 은 20자 floor 만 본다. "20자 넘고 형제 스킬과 안 겹치는" 좋은 description 은 사람이 쓴다. 팀 표준(wiki development/skill-policy.md, 자습용)의 체크리스트는 다섯 가지다:

  1. 무엇을 하는지 — 한 문장으로 명확히.
  2. 언제 쓰는지(트리거) — "~할 때 사용", "Use when ~" 형태로 발동 상황을 구체적으로.
  3. 언제 쓰지 않는지(경계) — 헷갈릴 형제 스킬이 있으면 "이런 경우엔 /other-skill 사용"처럼 상호 참조로 서로 밀어낸다.
  4. 고유 키워드 — 그 스킬만의 명사/동사를 넣어 의미 공간을 분리한다.
  5. 도메인 접두어 — 이름에 email-…·sheet-… 같은 접두어를 둬 의미가 겹치지 않게.
# 나쁨 — 무엇을·언제 쓰는지 불명확, 형제 스킬과 헷갈림
description: 리포트를 보냅니다.

# 좋음 — 트리거 + 경계 + 고유 키워드
description: 주간 매출 리포트를 Excel 로 만들어 메일로 발송합니다.
  매출 집계 결과를 정기 발송할 때 사용. 단발성 요약 텍스트만 보낼 때는
  /send-summary 를 사용하세요.

이 프로젝트의 실제 스킬 description 들도 이 꼴이다 — 예컨대 create-lesson 은 "…형제 스킬과의 경계 — Word(.docx)는 create-docx, 기능 문서는 feature-doc…" 처럼 경계를 description 안에 박아 이웃 스킬을 밀어낸다.


4. PR #30 — "슬래시 전용"으로 막지 말고, "실행 직전 확인"으로 보호한다

여기서 자연스러운 반문이 나온다. "자연어 호출이 그렇게 위험하면, /release 처럼 비가역·위험한 스킬은 자연어로 못 부르게(슬래시 전용) 막으면 되지 않나?"

PR #29 초안엔 실제로 "위험 스킬은 슬래시 전용으로 둔다"는 문구가 있었다. PR #30 은 그걸 제거했다. 새 정책은 이렇다:

모든 스킬은 슬래시·자연어 둘 다로 호출 가능하게 둔다. 자연어 호출을 막는 "슬래시 전용" 스킬은 만들지 않는다. 배포·삭제·외부 발송·릴리즈처럼 위험한 스킬도 호출 방식을 제한하지 않는다. 대신 실행 자체를 안전하게 만든다 — 스킬 본문에 실행 전 확인 단계("X 를 Y 에 배포합니다. 진행할까요?")를 둬서, 자연어로 잘못 불려도 사람이 한 번 거른다.

핵심 원칙 한 줄: 가드는 입구가 아니라 행동 직전에 둔다.

  • 입구를 막는 방식(슬래시 전용)의 문제: 접근성을 통째로 희생한다. 게다가 "호출을 못 하게" 하는 건 위험의 진짜 지점(비가역 행동)이 아니라 그 한참 앞(호출 경로)을 막는 것이라 어긋나 있다.
  • 행동 직전 확인의 이점: 자연어든 슬래시든, 어떤 경로로 불려왔든 위험한 행동 바로 앞에서 한 번 사람이 거른다. 접근성과 안전을 동시에 얻는다.

실제로 이 프로젝트의 release 스킬에는 L20 에서 본 migration 휴먼 게이트("목록 보여주고 y/N")가 본문에 박혀 있다. 자연어로 "배포해줘" 라고 불러도, 되돌리기 어려운 migrate 직전에 확인이 뜬다 — 이게 PR #30 이 말하는 "실행 직전 가드"의 실물이다.


5. 큰 그림: 강제력은 3층으로 나뉜다

PR #29·#30 을 한 장의 표로 겹쳐 보면, 스킬 거버넌스가 강제력이 다른 3개 층으로 되어 있다는 게 보인다:

층 메커니즘 성격 강제력 이번 변화
문서 wiki skill-policy.md 사람용 근거·거버넌스 없음 (런타임 영향 0) 정책 원문
유도 스킬 description + CLAUDE.md 규칙 컨텍스트에 주입돼 모델이 참고 소프트 (거의 따르나 보장 X) 작성 표준(§3), 실행-직전 확인(§4)
강제 hook (check-skill-description.sh) 하네스가 실행, Claude 우회 불가 하드 (결정론적) description floor(§2)

이건 L13·L24 에서 반복해 본 "말(CLAUDE.md) → 유도(description) → 강제(hook)" 제도화 사다리와 같은 구조다. 규칙 하나를 어느 층에 둘지가 설계 결정이다:

  • description 이 비었는지(객관·이분법) → 결정론적으로 판정 가능 → hook(강제).
  • description 이 충분히 또렷한지, 위험 스킬을 언제 확인할지(맥락·판단) → 기계로 못 가름 → 유도(사람 review + 본문 확인 단계).

hook 을 만능으로 밀어붙이면 오탐으로 마찰만 커지고, 전부 권고로 두면 언젠가 뚫린다. 판정이 결정론적인 최소한만 강제하고, 나머지는 유도로. 이게 이번 두 PR 이 보여주는 판단이다.


오늘 정리 + 다음

  • 정리: 스킬이 늘면 자연어 호출이 오발동하는데, 그 원인은 비슷한 이름이 아니라 겹치는 description 이다. 그래서 (PR #29) description 의 최소 품질을 hook 으로 강제(floor) 하고, 좋은 description 은 작성 표준(무엇을/언제/경계/고유키워드/도메인접두어)으로 사람이 쓴다. 위험한 스킬은 (PR #30) 호출을 막는 대신 실행 직전 확인으로 보호한다 — 가드는 입구가 아니라 행동 직전에.
  • 흔한 함정: "위험하니까 자연어로 못 부르게 막자"(입구 차단)로 반응하기 쉽다. 하지만 그건 접근성을 희생하면서 정작 위험의 진짜 지점(비가역 행동)은 안 지킨다. 막을 곳은 호출 경로가 아니라 되돌릴 수 없는 행동 바로 앞이다.
  • 다음 시간: 이 3층 사다리(문서→유도→강제)를 실제 변경 이력에 적용해 추적하는 법 — L27 standarda 변경 이력 추적.
  • 자습 권장: 팀 wiki 의 development/skill-policy.md 6번("충돌 회귀 테스트") — "자연어 문장 → 기대 스킬" 매핑 표를 만들어 두면 스킬 추가/이름 충돌을 조기에 잡는다. 이 프로젝트 .claude/skills/ 를 열어 각 스킬의 description 이 §3 체크리스트를 지키는지 직접 대조해 보자.
이 강의를 학습하셨나요?