Claude Code 슬래시 커맨드 description must be a string 오류
프로젝트에서 반복하는 코드 리뷰 지시문을 매번 붙여 넣는 대신 Claude Code 커스텀 슬래시 커맨드(custom slash commands)로 남기려면, .claude/commands/ 아래에 마크다운 파일 하나를 두면 됩니다. 걸리는 곳은 frontmatter의 description입니다. 항목 목록처럼 줄 앞에 하이픈을 붙여 적는 순간 그 값은 YAML 배열이 되고, claude plugin validate는 그 커맨드 파일을 오류로 거절합니다.
순서는 단순합니다. 배열로 적힌 description이 검증을 막는 장면을 먼저 재현하고, 같은 필드를 문자열로 고쳐 통과시킨 뒤, 여러 줄 설명과 스킬 파일, shell 값, --strict를 같은 명령으로 하나씩 대 봅니다. 명령은 재현 단계, 해결 단계, 점검 단계에 나뉘어 있고, 숫자와 오류 문장은 각 출력 블록에 찍힌 것만 옮겼습니다.
description을 배열로 쓰면 커맨드 파일 검증이 막히는 이유
실행 환경은 재현 단계가 찍은 값입니다. 스크립트는 이 폴더의 home을 HOME으로 둔 뒤 아래 명령을 실행합니다. 작업 디렉터리는 출력의 pwd 줄입니다.
| 항목 | 재현 출력 값 |
|---|---|
| 작업 디렉터리 | ~/claude |
| OS | Debian GNU/Linux 13 (trixie) |
| 커널 | Linux 6.12.94+ x86_64 |
| Claude Code | 2.1.299 (Claude Code) |
| 실행일 | 2026-10-14 |
재현용 파일은 proj-a/.claude/commands/review.md입니다. description 아래에 하이픈 한 줄을 두었고, argument-hint와 본문의 $ARGUMENTS는 함께 적었습니다. 그 다음 claude plugin validate에 프로젝트 디렉터리를 넘깁니다.
pwd
. /etc/os-release && printf '%s\n' "$PRETTY_NAME"
uname -srm
claude --version
date -I
rm -rf proj-a
mkdir -p proj-a/.claude/commands
cat > proj-a/.claude/commands/review.md << 'EOF'
---
description:
- 변경 파일 리뷰
argument-hint: "[파일 경로]"
---
$ARGUMENTS 파일을 읽고 버그 후보를 목록으로 적어라.
EOF
cat proj-a/.claude/commands/review.md
claude plugin validate proj-a
아래는 위 명령을 그대로 실행한 기록입니다. 줄 앞에 +가 붙은 것이 실행된 명령이고, 그 아래가 출력입니다.
+ pwd
/home/user/claude
+ . /etc/os-release && printf "%s\n" "$PRETTY_NAME"
Debian GNU/Linux 13 (trixie)
+ uname -srm
Linux 6.12.94+ x86_64
+ claude --version
2.1.299 (Claude Code)
+ date -I
2026-10-14
+ rm -rf proj-a && mkdir -p proj-a/.claude/commands
+ cat > proj-a/.claude/commands/review.md << 'EOF'
+ cat proj-a/.claude/commands/review.md
---
description:
- 변경 파일 리뷰
argument-hint: "[파일 경로]"
---
$ARGUMENTS 파일을 읽고 버그 후보를 목록으로 적어라.
+ claude plugin validate proj-a
Validatingcomponentsin:/home/user/claude/run/proj-a
Validatingcommand:/home/user/claude/run/proj-a/.claude/commands/review.md
✘Found1error:
❯description:descriptionmustbeastring,gotarray.Atruntimethisvalueisdropped.
✘Validationfailed
exit_code=1

세 캡처의 Validating 줄에는 작업 폴더 전체 경로가 찍혀 있습니다. 본문에서는 이 앞부분을 /home/user/claude로 줄였고, 검증 결과 문구는 그대로 옮겼습니다.
검증기는 이 파일을 command로 읽고 오류 1건을 찾아냅니다. 문장은 description must be a string, got array입니다. 런타임에는 이 값이 버려진다는 안내가 뒤따르고, 마지막 줄은 Validation failed, 종료 상태는 exit_code=1입니다. 어느 파일이 문제인지는 실패 출력의 Validating command 줄이 review.md 경로로 알려 줍니다.
원인은 YAML이 문자열과 배열을 다르게 읽는 데 있습니다. description: 다음 줄이 하이픈으로 시작하면 그 내용은 시퀀스의 원소가 되고, 그 줄 하나뿐이어도 값은 문장이 아니라 원소가 하나인 배열입니다. 검증기는 배열을 description으로 받지 않습니다. 출력이 got array라고 적은 것은, 파일은 제대로 읽혔고 타입 단계에서 거절되었다는 뜻입니다.
스킬 문서와 슬래시 커맨드 문서 URL은 점검 출력에서 HTTP 200입니다. 그 문서는 커스텀 커맨드가 스킬로 합쳐졌다고 적고, .claude/commands/deploy.md와 .claude/skills/deploy/SKILL.md가 둘 다 /deploy를 만들며 같은 방식으로 동작한다고 설명합니다.
그렇다고 두 형식이 완전히 같지는 않습니다. 커맨드 파일은 예전 형식이고, 같은 frontmatter 가운데 name과 paths는 커맨드 파일 설명에서 빠집니다. 호출 이름은 확장자를 뺀 파일 이름이라고 적혀 있습니다. description은 권장 필드이고, 비우면 본문의 첫 비어 있지 않은 줄을 쓴다고 적혀 있습니다.
같은 문서는 YAML이 파싱되지 않는 SKILL.md를 찾을 때도 claude plugin validate를 안내합니다. 그 안내는 파싱이 안 되는 파일을 찾는 용도입니다. 이번 재현은 그 경우와 다릅니다. 배열은 키와 원소로 읽힌 뒤, description 타입이 맞지 않아 거절되었습니다. 플러그인 레퍼런스도 HTTP 200이었고, 그 페이지는 claude plugin validate를 매니페스트 기준으로 안내합니다. 이번 실행은 매니페스트가 없는 프로젝트 디렉터리에 같은 명령을 적용해 커맨드 파일의 frontmatter를 검사했습니다.
문자열 description으로 바꾸고 plugin validate 다시 돌리기
하이픈 목록을 지우고 description을 한 줄 문자열로 두면 배열이 사라집니다. argument-hint와 본문은 재현 때와 같이 두고 proj-b에 다시 씁니다. 검사 명령도 같습니다.
rm -rf proj-b
mkdir -p proj-b/.claude/commands
cat > proj-b/.claude/commands/review.md << 'EOF'
---
description: 변경 파일 리뷰
argument-hint: "[파일 경로]"
---
$ARGUMENTS 파일을 읽고 버그 후보를 목록으로 적어라.
EOF
cat proj-b/.claude/commands/review.md
claude plugin validate proj-b
실행 기록도 재현 때와 같은 순서로 찍힙니다. 파일 내용을 먼저 보여 주고, 검증 결과가 그 뒤에 나옵니다.
+ rm -rf proj-b && mkdir -p proj-b/.claude/commands
+ cat > proj-b/.claude/commands/review.md << 'EOF'
+ cat proj-b/.claude/commands/review.md
---
description: 변경 파일 리뷰
argument-hint: "[파일 경로]"
---
$ARGUMENTS 파일을 읽고 버그 후보를 목록으로 적어라.
+ claude plugin validate proj-b
Validatingcomponentsin:/home/user/claude/run/proj-b
✔Validationpassed
exit_code=0

통과 쪽 출력은 짧습니다. Validating components 줄에 proj-b 디렉터리가 찍히고 Validation passed로 끝나며, 종료 상태는 exit_code=0입니다. 실패 때처럼 Validating command 줄에 파일 경로가 반복되지도 않습니다. 파일 내용은 같은 출력의 cat 결과에 그대로 있습니다. 짚어 둘 점이 하나 있는데, 이 통과는 본문 지시가 리뷰를 수행했다는 뜻이 아니라 frontmatter 검사를 넘었다는 뜻입니다.
파일에 적어 둔 argument-hint와 $ARGUMENTS는 이번 통과를 막지 않았습니다. 스킬 문서는 argument-hint를 자동완성에서 기대 인자를 보여 주는 힌트라고 설명하고, $ARGUMENTS를 호출할 때 넘긴 인자 전체라고 설명합니다. 문서 기준으로 커맨드 파일 review.md의 호출 이름은 확장자를 뺀 review입니다.
이 방법의 좋은 점은 프로젝트의 커맨드 파일 한 곳만 고치면 끝난다는 것입니다. 추가 패키지 없이 claude plugin validate가 통과하고, 재현에 쓴 argument-hint와 본문을 그대로 둬도 종료 상태는 exit_code=0입니다. 한계도 있습니다. 여러 줄 설명을 다시 하이픈 목록으로 적으면 같은 배열 오류가 돌아오고, 공식 문서는 새 작업에 스킬 디렉터리를 안내하면서 커맨드 파일 설명에서는 name과 paths를 빼 둡니다.
스킬 파일, 블록 스칼라, shell 값, –strict를 차례로 검증한 결과
문자열 한 줄로 끝나지 않는 상황이라면 파일 형식과 옵션을 나눠서 보는 편이 빠릅니다. 점검 단계는 스킬 파일, 블록 스칼라, shell 값, description 생략, --strict, 없는 CLI 옵션, 문서 HEAD를 차례로 실행합니다. 버전은 재현 때와 같은 Claude Code 2.1.299입니다.
rm -rf proj-c-skill
mkdir -p proj-c-skill/.claude/skills/review
cat > proj-c-skill/.claude/skills/review/SKILL.md << 'EOF'
---
name: review
description: 변경 파일 리뷰
argument-hint: "[파일 경로]"
disable-model-invocation: true
---
$ARGUMENTS 파일을 읽고 버그 후보를 목록으로 적어라.
EOF
cat proj-c-skill/.claude/skills/review/SKILL.md
claude plugin validate proj-c-skill
rm -rf proj-c-block
mkdir -p proj-c-block/.claude/commands
cat > proj-c-block/.claude/commands/review.md << 'EOF'
---
description: |
변경 파일 리뷰.
버그 후보를 목록으로 적는다.
argument-hint: "[파일 경로]"
---
$ARGUMENTS 파일을 읽고 버그 후보를 목록으로 적어라.
EOF
cat proj-c-block/.claude/commands/review.md
claude plugin validate proj-c-block
rm -rf proj-c-zsh
mkdir -p proj-c-zsh/.claude/commands
cat > proj-c-zsh/.claude/commands/review.md << 'EOF'
---
description: 변경 파일 리뷰
shell: zsh
argument-hint: "[파일 경로]"
---
$ARGUMENTS 파일을 읽고 버그 후보를 목록으로 적어라.
EOF
cat proj-c-zsh/.claude/commands/review.md
claude plugin validate proj-c-zsh
rm -rf proj-c-bash
mkdir -p proj-c-bash/.claude/commands
cat > proj-c-bash/.claude/commands/review.md << 'EOF'
---
description: 변경 파일 리뷰
shell: bash
argument-hint: "[파일 경로]"
---
$ARGUMENTS 파일을 읽고 버그 후보를 목록으로 적어라.
EOF
cat proj-c-bash/.claude/commands/review.md
claude plugin validate proj-c-bash
rm -rf proj-c-nodesc
mkdir -p proj-c-nodesc/.claude/commands
cat > proj-c-nodesc/.claude/commands/review.md << 'EOF'
---
argument-hint: "[파일 경로]"
---
$ARGUMENTS 파일을 읽고 버그 후보를 목록으로 적어라.
EOF
cat proj-c-nodesc/.claude/commands/review.md
claude plugin validate proj-c-nodesc
claude plugin validate --strict proj-c-nodesc
claude --slash-commands
claude --help | grep -F -- '--disable-slash-commands' || echo 'disable_slash_commands_line=absent'
claude --help | grep -F -- '--slash-commands' || echo 'option_--slash-commands=absent'
curl -sI --max-time 20 -o /dev/null -w '%{http_code} %{url_effective}\n' https://code.claude.com/docs/en/skills
curl -sI --max-time 20 -o /dev/null -w '%{http_code} %{url_effective}\n' https://code.claude.com/docs/en/slash-commands
curl -sI --max-time 20 -o /dev/null -w '%{http_code} %{url_effective}\n' https://code.claude.com/docs/en/plugins-reference
점검 명령이 길어서 실행 기록도 깁니다. 검증마다 뒤에 붙은 exit 줄을 따라가면 결과를 하나씩 맞춰 보기 쉽습니다.
+ rm -rf proj-c-skill && mkdir -p proj-c-skill/.claude/skills/review
+ cat > proj-c-skill/.claude/skills/review/SKILL.md << 'EOF'
+ cat proj-c-skill/.claude/skills/review/SKILL.md
---
name: review
description: 변경 파일 리뷰
argument-hint: "[파일 경로]"
disable-model-invocation: true
---
$ARGUMENTS 파일을 읽고 버그 후보를 목록으로 적어라.
+ claude plugin validate proj-c-skill
Validatingcomponentsin:/home/user/claude/run/proj-c-skill
✔Validationpassed
skill_exit=0
+ rm -rf proj-c-block && mkdir -p proj-c-block/.claude/commands
+ cat > proj-c-block/.claude/commands/review.md << 'EOF'
+ cat proj-c-block/.claude/commands/review.md
---
description: |
변경 파일 리뷰.
버그 후보를 목록으로 적는다.
argument-hint: "[파일 경로]"
---
$ARGUMENTS 파일을 읽고 버그 후보를 목록으로 적어라.
+ claude plugin validate proj-c-block
Validatingcomponentsin:/home/user/claude/run/proj-c-block
✔Validationpassed
block_exit=0
+ rm -rf proj-c-zsh && mkdir -p proj-c-zsh/.claude/commands
+ cat > proj-c-zsh/.claude/commands/review.md << 'EOF'
+ cat proj-c-zsh/.claude/commands/review.md
---
description: 변경 파일 리뷰
shell: zsh
argument-hint: "[파일 경로]"
---
$ARGUMENTS 파일을 읽고 버그 후보를 목록으로 적어라.
+ claude plugin validate proj-c-zsh
Validatingcomponentsin:/home/user/claude/run/proj-c-zsh
Validatingcommand:/home/user/claude/run/proj-c-zsh/.claude/commands/review.md
✘Found1error:
❯shell:shellmustbe'bash'or'powershell',got'zsh'.
✘Validationfailed
zsh_exit=1
+ rm -rf proj-c-bash && mkdir -p proj-c-bash/.claude/commands
+ cat > proj-c-bash/.claude/commands/review.md << 'EOF'
+ cat proj-c-bash/.claude/commands/review.md
---
description: 변경 파일 리뷰
shell: bash
argument-hint: "[파일 경로]"
---
$ARGUMENTS 파일을 읽고 버그 후보를 목록으로 적어라.
+ claude plugin validate proj-c-bash
Validatingcomponentsin:/home/user/claude/run/proj-c-bash
✔Validationpassed
bash_exit=0
+ rm -rf proj-c-nodesc && mkdir -p proj-c-nodesc/.claude/commands
+ cat > proj-c-nodesc/.claude/commands/review.md << 'EOF'
+ cat proj-c-nodesc/.claude/commands/review.md
---
argument-hint: "[파일 경로]"
---
$ARGUMENTS 파일을 읽고 버그 후보를 목록으로 적어라.
+ claude plugin validate proj-c-nodesc
Validatingcomponentsin:/home/user/claude/run/proj-c-nodesc
Validatingcommand:/home/user/claude/run/proj-c-nodesc/.claude/commands/review.md
⚠Found1warning:
❯description:Nodescriptioninfrontmatter.AdescriptionhelpsusersandClaudeunderstandwhentousethis
command.
✔Validationpassedwithwarnings
nodesc_exit=0
+ claude plugin validate --strict proj-c-nodesc
Validatingcomponentsin:/home/user/claude/run/proj-c-nodesc
Validatingcommand:/home/user/claude/run/proj-c-nodesc/.claude/commands/review.md
⚠Found1warning:
❯description:Nodescriptioninfrontmatter.AdescriptionhelpsusersandClaudeunderstandwhentousethis
command.
✘Validationfailed(--stricttreatswarningsaserrors)
strict_exit=1
+ claude --slash-commands
error: unknown option '--slash-commands'
slash_commands_exit=1
+ claude --help | grep -F -- --disable-slash-commands
--disable-slash-commands Disable all skills
+ claude --help | grep -F -- --slash-commands || echo option_--slash-commands=absent
option_--slash-commands=absent
+ curl -sI -o /dev/null -w "%{http_code} %{url_effective}\n" https://code.claude.com/docs/en/skills
200 https://code.claude.com/docs/en/skills
+ curl -sI -o /dev/null -w "%{http_code} %{url_effective}\n" https://code.claude.com/docs/en/slash-commands
200 https://code.claude.com/docs/en/slash-commands
+ curl -sI -o /dev/null -w "%{http_code} %{url_effective}\n" https://code.claude.com/docs/en/plugins-reference
200 https://code.claude.com/docs/en/plugins-reference

스킬 대안은 proj-c-skill/.claude/skills/review/SKILL.md입니다. name은 review이고, description은 문자열이며, disable-model-invocation은 true입니다. 출력은 Validation passed이고 종료 상태는 skill_exit=0입니다. 스킬 문서는 disable-model-invocation을 true로 두면 Claude가 그 스킬을 자동으로 불러오지 않는다고 설명합니다. 직접 호출용으로 두고 싶을 때의 문서 옵션입니다.
스킬 형식은 공식 문서가 새 작업에 안내하는 쪽이고, name과 disable-model-invocation을 넣은 파일도 이번 검증을 통과했습니다. 대신 디렉터리와 SKILL.md라는 한 단계가 더 붙습니다. 같은 이름의 커맨드 파일과 나란히 둘 때 어느 쪽이 실행되는지는 이번 검증 출력으로 정해지지 않으니, 문서의 이름 충돌 표를 따르면 됩니다.
문서의 이름 충돌 표는 스킬과 .claude/commands/ 파일이 같은 이름이면 스킬이 실행된다고 적습니다. 이 문장은 스킬 문서의 설명이고, 이번 검증은 각 형식을 따로 둔 디렉터리의 frontmatter 결과입니다.
여러 줄 설명이 필요하면 하이픈 목록 대신 블록 스칼라를 씁니다. description: | 다음에 두 줄을 둔 proj-c-block은 Validation passed, block_exit=0입니다. 블록 스칼라의 값은 문자열이라 배열 오류가 날 자리가 없습니다.
shell: zsh는 환경 제약에 걸립니다. description이 문자열이어도 출력은 shell must be 'bash' or 'powershell', got 'zsh'이고, Validation failed이며 zsh_exit=1입니다. 같은 자리에 shell: bash를 두면 Validation passed이고 bash_exit=0입니다. 스킬 문서의 shell 필드 설명도 bash와 powershell을 받습니다. 이번 실행에서 통과한 값은 bash입니다.
description을 아예 빼면 경고 1건이 납니다. 문장은 No description in frontmatter이고, 사용자와 Claude가 이 커맨드를 언제 쓰는지 이해하는 데 설명이 도움이 된다고 적습니다. 이 상태의 종료는 nodesc_exit=0이고, 출력은 Validation passed with warnings입니다. 같은 디렉터리에 --strict를 붙이면 그 경고를 오류로 보고, Validation failed (--strict treats warnings as errors)와 strict_exit=1이 납니다. 경고까지 막고 싶은 검사에서는 --strict를 쓰면 됩니다.
파일을 만든 뒤 claude --slash-commands로 목록을 보려는 시도는 다른 오류입니다. 출력은 error: unknown option '--slash-commands'이고 slash_commands_exit=1입니다. 도움말에서 --slash-commands를 찾으면 option_--slash-commands=absent입니다. 비슷해 보이는 --disable-slash-commands는 도움말에 있고, 그 줄은 Disable all skills라고 적습니다. 그 플래그는 스킬을 끄는 옵션으로 안내될 뿐, 커맨드 파일을 검사하지 않습니다. 파일 검사에 이번 글이 사용한 명령은 claude plugin validate입니다.
기존 커맨드는 한 줄 description, 새 커맨드는 SKILL.md로
표에는 재현 출력, 해결 출력, 점검 출력에서 통과나 실패가 찍힌 선택만 넣었습니다. 종료 상태 이름은 각 스크립트가 찍은 줄 그대로입니다.
| 선택 | 파일 | 검증 결과 | 종료 상태 |
|---|---|---|---|
| description을 YAML 배열로 둔 커맨드 파일 | proj-a/.claude/commands/review.md | Validation failed, description must be a string, got array | exit_code=1 |
| description을 문자열로 둔 커맨드 파일 | proj-b/.claude/commands/review.md | Validation passed | exit_code=0 |
| description을 블록 스칼라로 둔 커맨드 파일 | proj-c-block/.claude/commands/review.md | Validation passed | block_exit=0 |
| 스킬 SKILL.md | proj-c-skill/.claude/skills/review/SKILL.md | Validation passed | skill_exit=0 |
| shell을 bash로 둔 커맨드 파일 | proj-c-bash/.claude/commands/review.md | Validation passed | bash_exit=0 |
| shell을 zsh로 둔 커맨드 파일 | proj-c-zsh/.claude/commands/review.md | Validation failed, shell must be ‘bash’ or ‘powershell’, got ‘zsh’. | zsh_exit=1 |
| description을 생략한 커맨드 파일 | proj-c-nodesc/.claude/commands/review.md | Validation passed with warnings | nodesc_exit=0 |
| 같은 파일에 –strict | proj-c-nodesc/.claude/commands/review.md | Validation failed (–strict treats warnings as errors) | strict_exit=1 |
이미 .claude/commands/review.md를 쓰고 있다면 할 일은 하나입니다. description을 한 줄 문자열로 고치고 claude plugin validate로 통과만 확인하면 충분합니다. 이번 해결의 종료 상태가 exit_code=0이었습니다. 설명이 여러 문장이어야 한다면 하이픈 목록은 피하고 블록 스칼라로 적습니다. 그 경우의 종료 상태는 block_exit=0입니다.
새로 만드는 커맨드라면 처음부터 .claude/skills/review/SKILL.md로 시작하기를 권합니다. 이번 실행에서 skill_exit=0이었고, 공식 문서도 새 작업에는 이 형식을 안내하면서 기존 커맨드 파일이 계속 동작한다고 적습니다. shell을 적는다면 bash로 둡니다. zsh는 zsh_exit=1로 막혔습니다. 경고까지 실패로 처리하고 싶다면 --strict를 붙이고, 이때 description이 비어 있으면 strict_exit=1이 납니다.
마지막으로 --slash-commands는 이 빌드의 도움말에 없는 옵션이니, 파일 검사는 claude plugin validate에 맡깁니다. 세 문서 URL의 HTTP 상태 코드는 출력의 200이었고, 통과 여부는 출력 블록의 Validation passed와 각 exit 줄로 판단합니다.






