플러그인 evals로 테스트
기준일: 2026-10-23
공식 기준: Test plugins with evals
개요
claude plugin eval 셸 명령은 플러그인을 테스트 케이스 묶음에 돌려 채점합니다. 케이스 하나는 현실적인 프롬프트 + 하나 이상의 grader입니다. grader는 Claude가 만든 결과에 대한 통과/실패 검사입니다 (응답에 대한 regex, 특정 도구 호출 여부, 두 번째 모델이 판정하는 rubric 등).
suite를 손으로 쓸 필요는 없습니다. claude plugin eval init이 플러그인에 대해 묻고, 케이스·grader를 제안·시험한 뒤 파일을 씁니다. 열려 있는 세션에서 Claude에게 같은 일을 시켜도 됩니다. 이 명령은 v2.1.269에서 추가됐습니다 (JSON + HTML 리포트).
용도:
- 플러그인이 올바른 결과를 얼마나 안정적으로 이끄는지 측정
- 플러그인 변경이나 새 모델 출시 때 회귀 검출
- 플러그인 없을 때 대비 기여도 확인
대상: 동작하는 플러그인을 가진 플러그인/skill 작성자, CI에서 플러그인 변경을 게이트하는 팀. 대화 중 skill 하나를 반복 개선하려면 skill-creator 플러그인(자체 evals/evals.json 형식, 서로의 케이스 파일은 읽지 않음). 파일 구문·스키마 검사는 claude plugin validate.
주의: 모든 eval 실행과 judge grader는 계정에서 실제 모델 호출이며 플랜 사용량이나 API 요금에 잡힙니다.
핵심 개념
| 개념 | 설명 |
|---|---|
| 케이스 | evals/<case>/ 디렉터리. prompt.md 또는 case.yaml + grader 1개 이상(없으면 로드 실패) |
| grader | 결과 pass/fail 검사. 종류 6개 (regex, tool_used, tool_order, file_exists, llm, baseline) |
| 점수 | run 점수 = 통과한 grader 비율(weight 반영), 케이스 점수 = run 평균. --threshold(기본 1.0) 이상이면 통과 |
| with-arm / without-arm | 플러그인을 로드한 run / 로드하지 않은 baseline run |
Δ |
with 점수 - without 점수. 플러그인의 기여분 |
| 격리 | run마다 임시 홈·작업 디렉터리·설정을 가진 claude -p 자식 프로세스 |
상세
요구 사항
- Claude Code v2.1.269 이상 (
claude --version, 업그레이드claude update) - git이 설치돼 있다면 Git 2.31 이상 (
git --version). 더 낮으면 케이스 실행 전에 중단. git이 없으면 정상 실행. (v2.1.283부터 검사, 이전엔 낮은 git에서 hook 등이 켜진 채 실행) plugin.json또는.claude-plugin/plugin.json매니페스트가 있는 플러그인 디렉터리, 또는 skills-directory 플러그인- 평소 세션과 같은 인증·모델 제공자. 보고되는 비용은 list-price 추정치. Amazon Bedrock, Google Cloud Agent Platform, Microsoft Foundry라면 평소와 같은 provider 변수를 export한 셸에서 실행 (
env필드가 설명하듯 run이 셸에서 상속)
실행 방식
각 케이스 run마다 Claude Code가 플러그인만 로드한 새 격리된 비대화 세션을 시작해 프롬프트를 보내고, 끝나거나 턴/시간 한도에 닿을 때까지 둡니다. 이후 grader가 최종 응답·transcript·생성 파일을 검사합니다.
비결정적 에이전트 한 번은 정보가 적으므로 기본 3회 실행합니다. 모델 호출 규모는 대략 cases × runs(플러그인 포함) + 같은 수(baseline) + llm/baseline grader당 run마다 짧은 judge 호출 3회.
첫 suite 만들기
- 케이스 생성: 플러그인 루트에서
claude plugin eval init
디렉터리를 아직 신뢰하지 않으면 Trust this plugin directory?를 묻습니다 (y). 대화형 세션이 열려 Claude가 좋은 결과가 무엇인지 묻고, 플러그인이 발동해야/하지 말아야 할 프롬프트를 제안하고, grader를 설계해 시험 실행한 뒤 프롬프트 이름으로 된 케이스 디렉터리를 evals/ 아래 씁니다. 끝나면 /exit 또는 Ctrl+D. 이미 열린 세션에서 Claude에게 claude plugin eval init 실행을 시킬 수도 있습니다. 직접 쓰려면 수동 작성.
- suite 실행:
claude plugin eval .
케이스 1개당 플러그인 포함 3회 + 미포함 3회 = 6 run. run이 끝날 때마다 점수와 grader 판정이 한 줄 출력됩니다. 수동 작성 시 첫 실행에서 Trust this plugin directory? [y/N]를 묻습니다.
- 요약 읽기:
CASE WITH W/OUT Δ RUNS COST NOTES
first-case 1.00 0.33 +0.67 6 $0.41
1 case(s) · mean Δ +0.67 · 74s · $0.41
Report: /Users/you/my-plugin/evals/results/2026-09-10T17-02-11-482Z/report.html
Published: https://claude.ai/... · keep local next time with --no-publish
COST는 list-price 추정, NOTES는 with-arm에서 weight가 가장 큰 실패 grader의 설명 또는 run 오류.
- 리포트 열고 반복:
Published:URL(없으면Report:경로)에서 run별 grader 판정·설명,llmgrader는 judge 투표와 판정한 발췌를 봅니다.Published:줄은 계정이 리포트를 게시할 수 있을 때만 나옵니다. 흔한 첫 발견은Δ가 0 근처이고tool_used: Skillgrader가 실패하는 경우: 자연스러운 표현에서 Claude가 skill을 고르지 못한다는 뜻이므로 skilldescription을 고쳐 다시 실행합니다.
한 케이스만 적은 run으로 반복(단일 run은 노이즈가 크므로 기본 3회로 재확인). 한 arm만 돌리면 WITH/W/OUT/Δ 대신 SCORE, PASS% 열이 나옵니다.
claude plugin eval . --case <case-name> --runs 1 --ablation none
케이스 작성과 다듬기
케이스는 prompt.md, case.yaml 또는 둘 다 가진 디렉터리입니다. grader는 graders/<name>.md 파일이나 case.yaml의 graders: 항목으로, 최소 1개 필요합니다. 묶으려면 케이스가 아닌 디렉터리 아래에 중첩합니다. 케이스 디렉터리 안의 graders/·fixture는 그 케이스 소속.
my-plugin/
├── .claude-plugin/plugin.json
├── skills/...
└── evals/
├── first-case/
│ ├── prompt.md # frontmatter: case fields; body: the prompt
│ ├── graders/
│ │ ├── criteria.md # frontmatter: type + options; body: rubric or pattern
│ │ └── skill-fired.md
│ └── case.yaml # optional: only for context.* fields
├── ignores-unrelated-request/
│ └── ...
└── results/ # written by each run; add to .gitignore
케이스 수동 작성
빈 템플릿(아무것도 실행하지 않음):
claude plugin eval init --bare first-case
evals/first-case/
├── prompt.md # the prompt sent to Claude, plus run limits
└── graders/
└── criteria.md # one grader: how to score the result
prompt.md는 매 run에 Claude가 받는 메시지와 frontmatter의 한도·도구입니다. skill 이름을 쓰지 말고 사용자가 칠 법한 말로 씁니다 (예: 커밋 메시지 skill).
---
max_turns: 10
allowed_tools: [Read, Glob, Grep, Skill]
---
Write me a commit message for this change: I renamed getUser to fetchUser and updated the three call sites.
각 run은 빈 작업 디렉터리에서 시작하므로 필요한 것은 프롬프트에 넣거나 workspace를 준비합니다. graders/criteria.md는 judge 모델용 rubric (구체적 PASS/FAIL 조건):
---
type: llm
---
PASS if <what a correct response contains>.
FAIL if <what a wrong or missing response looks like>.
두 번째 grader evals/first-case/graders/skill-fired.md (your-skill-name은 skills/ 아래 디렉터리 이름):
---
type: tool_used
tool: Skill
input_match: '"skill"\s*:\s*"(?:[\w-]+:)?your-skill-name"'
---
Claude가 그 skill을 한 번이라도 호출(plugin-name:skill-name 네임스페이스 형태 포함)하면 통과합니다. 저장 후 claude plugin eval .로 실행.
prompt.md에서 한도와 도구 설정
max_turns, timeout_seconds, model, tags, 쓸 수 있는 allowed_tools를 frontmatter에 씁니다 (필드 표). 본문은 쓴 그대로 전달되며 @path 멘션은 파일 첨부로 확장되지 않습니다. 파일을 읽어야 하면 allowed_tools에서 도구를 허용하세요.
grader 선택과 가중치
frontmatter에 type, 선택적으로 weight(점수 비중), arm(baseline 대비 채점 방식). 6개 종류 중 regex, tool_used, tool_order, file_exists는 transcript/파일로 계산되어 비용이 0이고, llm/baseline은 judge 모델을 호출해 비용이 듭니다. 사용자 코드 grader는 없습니다. llm/baseline의 기본 judge는 Claude Code가 백그라운드 작업에 쓰는 모델이며 --judge-model sonnet 또는 전체 모델 ID로 바꿉니다.
안정적 신호를 위한 습관:
- 생성 파일 같은 긴 출력은 파일 내용에 대한
regexgrader로 채점 (매번 동일하게 전체 검사).llmgrader는 짧은 출력에, rubric은 구체적 PASS/FAIL 조건으로 - 케이스마다 결과(최종 메시지·산출 파일) grader 하나 + 과정(
tool_used/tool_order) grader 하나. 답이 맞았는지와 플러그인이 만들었는지를 모두 알 수 있음 tool_used: Skill은 통과하는데Δ가 음수면 플러그인보다 judge를 의심. 작은 judge가 형식이 다르다는 이유로 정답을 오답 처리할 수 있음.--judge-model sonnet으로 재실행하고 형식이 판정을 좌우하지 않게 rubric 보강- 빌드/테스트 통과 확인은 프롬프트에서 실행 후 결과를 파일에 쓰게 하고 그 파일을 채점,
tool_usedgrader의input_match로 명령이 실행됐는지 확인
플러그인 없는 baseline과 비교
플러그인을 테스트할 때 케이스는 보통 두 arm으로 돕니다. with-arm(플러그인 로드)과 같은 횟수의 without-arm(플러그인 없음). 요약·리포트에 두 점수와 Δ가 나옵니다. 다음 경우 with-arm만 돌아 W/OUT/Δ가 없습니다.
--ablation none: 모든 케이스 한 arm. 비교가 필요 없을 때(예: grader 개선 중) 비용이 절반- 대상이 경로(
.등)이고 케이스가 transcript를 이어받는 경우(context.history_file): 기록된 대화가 이미 플러그인을 반영한다고 보고 기본 한 arm. stderr에single-arm (no Δ)알림. 비교하려면--ablation with-without - 케이스에서 플러그인을 못 찾음: 대상이 경로일 때 기본 한 arm. 해결
두 arm 실행에서 일부 grader는 scored: false로 보고됩니다. "skill이 호출됐다" 같은 검사는 플러그인 없이는 통과 불가라 점수에 넣으면 without-arm이 0으로 쏠려 Δ가 부풀기 때문에, 양쪽 점수에서 제외하고 with-arm에서 pass/fail 지표로만 보여 줍니다.
tool이Skill인 모든tool_usedgrader- 케이스의 mock된 서버가 모두 플러그인이 선언한 서버일 때
target: mock_calls인 모든regexgrader와focus: mock_calls인 모든llmgrader arm: with-only로 표시한 grader
제외를 바꾸는 3가지:
- 모든 grader가 제외 대상이면 정상 채점 (남는 게 없으므로)
arm: both: 양쪽 arm에서 채점. "skill을 호출하지 않아야 한다" 검사(min: 0,max: 0)에 사용--ablation none: 아무것도 제외하지 않으므로 같은 suite라도 절대 점수가 달라질 수 있음
다른 eval 디렉터리
evals/를 다른 도구가 쓰고 있으면:
plugin.json에"experimental": { "evals": "quality/evals" }- 또는
claude plugin eval과claude plugin eval init둘 다에--eval-dir quality/evals
둘 다 주면 플래그가 우선. qa, quality/evals처럼 일반 디렉터리 이름의 상대 경로만 허용되고 절대 경로나 ..는 플래그면 오류, 매니페스트 값이면 Warning: 후 evals/ 사용. 케이스·결과·init 출력이 모두 그 디렉터리로 이동합니다.
fixture와 mock
workspace와 대화 시드
prompt.md 옆에 case.yaml을 두고 context 블록을 씁니다.
- fixture 파일/git 저장소: 케이스 디렉터리의 Bash 스크립트를
context.scaffold_script에 지정. 에이전트 샌드박스 밖에서 사용자로 실행되며--scaffold를 줄 때만 실행되므로, 본인/조직이 쓴 suite에만 쓰세요 - 이어갈 이전 대화: transcript를
.jsonl로 저장해context.history_file에 지정하면 프롬프트가 다음 사용자 턴. 경로 대상이면 기본적으로 baseline arm 없음 - run 중 Claude가 읽을 fixture 디렉터리:
context.add_dirs
case.yaml에는 schema_version: "1.1"과 name이 필요합니다.
schema_version: "1.1"
name: changelog-from-diff
tags: [smoke]
context:
scaffold_script: fixture.sh
add_dirs: [resources]
scaffold 스크립트는 빈 workspace에서 작은 고정 환경(셸의 PATH, run의 임시 홈을 가리키는 HOME, TMPDIR, TERM=dumb 등 상수)으로 시작합니다. 그 외 셸 환경과 케이스의 EVAL_* 변수는 전달되지 않습니다. 0이 아닌 종료나 120초 초과 시 그 run은 scaffold failed 오류와 함께 0점. 파일과 git 상태만 만드세요. 스크립트가 쓴 프로젝트 설정은 로드되지 않습니다.
MCP 서버 mock
실제 서비스 없이 MCP 도구를 호출하는 skill을 평가합니다. 도구당 Markdown 파일 하나를 suite 전체는 evals/mocks/<server>/<tool>.md, 케이스 하나는 그 케이스의 mocks/ 아래에 둡니다. <server>는 플러그인 MCP 설정의 서버 이름.
요청하지 않으면 실제 MCP 서버는 시작되지 않습니다. Claude Code가 각 서버 이름으로 대체 서버를 등록하고, mock 파일이 있는 도구는 거기서 응답하며 --allow-tools 허용 없이 쓸 수 있고, mock 파일이 없는 도구는 Claude가 쓸 수 없습니다. mock이 전혀 없는 서버는 mocked: 진행 줄에 plugin_<plugin>_<server>[not started: no mock]로 나옵니다.
evals/mocks/tracker/create_issue.md (서버 tracker의 create_issue 대체, 입력을 검사하고 제목을 에코):
---
expect:
title: string
priority: [low, medium, high]
---
Created issue #4821: {{input.title}}
- 치환:
{{input.<field>}}로 입력 필드,{{file:fixtures/{input.<field>}.json}}로 mock 옆 fixture 파일 내용 expect:: 입력 가드. 위반하면 run이 중단되어 0점이고 이유가 기록됨 (플러그인이 서버에 무엇을 요청했는지 단언)error: true: 본문을 도구 오류로 반환type: agent: 본문 지시에 따라 judge 모델이 서버처럼 응답. agent mock 호출은 run당 케이스max_turns의 4배 예산을 공유하며 초과하면 run이 0점으로 중단
전체 키와 _server.md, _tools.json은 mock 파일. 호출 자체를 채점하려면 grader의 target: mock_calls.
실제 서버로 돌리려면 다음 플래그 중 하나. 이 프로세스는 run 샌드박스 밖에서 사용자로 실행되고 도구는 --allow-tools 허용이 필요합니다.
--allow-real-servers: mock하지 않은 서버마다 실제 프로세스를 시작하고, mock된 도구는 파일로 계속 응답--mocks off:mocks/를 무시하고 플러그인이 선언한 서버를 모두 시작
agent mock 응답 재생
type: agent mock은 --judge-model 호출로 답하므로 run마다 출력이 달라지고 judge가 바뀌면 달라집니다. run이 오류/중단 없이 끝나면 각 agent mock 응답이 결과 디렉터리 mock-recordings/에 저장됩니다. 거기의 ADOPT.txt에 각 녹화와 그것을 복사할 .replay/<server>/ 디렉터리(해당 mock 옆)가 나옵니다. 복사한 뒤 같은 호출은 모델 호출 없이 녹화로 응답합니다. CI 반복성을 위해 mocks/.replay/를 mocks/와 함께 커밋하세요.
eval 실행
대상 선택
| 대상 | 실행되는 것 |
|---|---|
플러그인 루트 디렉터리 (. 등) |
eval 디렉터리의 모든 케이스, 그 플러그인 로드 |
prompt.md 또는 case.yaml 파일 하나 |
그 케이스, 둘러싼 플러그인 로드 |
설치된 플러그인 이름 name 또는 name@marketplace |
그 플러그인과 eval 디렉터리의 케이스(제자리 또는 설치 사본에서 읽음). 결과는 현재 디렉터리의 ./evals/results/, --eval-dir면 ./<dir>/results/ |
name@skills-dir |
skills-directory 플러그인에 대해 동일 |
| 생략 | 현재 디렉터리를 경로로 |
--case <glob>은 케이스 이름 필터, --tag <tag>는 태그 중 하나라도 맞는 케이스. 대상을 --tag, --allow-tools, --json 앞에 두세요 (앞의 둘은 목록을, --json은 선택적 경로를 받으므로 뒤따르는 대상을 자기 값으로 읽음).
도구 허용
run은 권한을 묻지 않고 멈추지 않습니다. 허용하지 않은 Bash, Write, Edit, WebFetch, WebSearch 같은 내장 도구는 세션에서 제거됩니다. run은 케이스 allowed_tools에 적힌 읽기 전용 도구(Read, Glob, Grep, NotebookRead, Skill, AskUserQuestion, Agent, TodoWrite, 태스크 도구 TaskCreate, TaskGet, TaskList, TaskUpdate, TaskStop)와 --allow-tools로 준 것만 허용합니다. 이 허용은 run의 모든 케이스에 적용됩니다.
claude plugin eval . --allow-tools Write Edit "Bash(npm test *)"
케이스가 요청했지만 허용하지 않은 도구는 진행 출력에 not granted로 나옵니다. mock된 MCP 서버의 도구는 허용 불필요. 실제 플러그인 MCP 서버의 도구는 서버 시작(--allow-real-servers 또는 --mocks off)과 이름 허용(예: --allow-tools "mcp__plugin_my-plugin_github__*")이 모두 필요하며 도구 이름은 mcp__plugin_<plugin>_<server>__<tool>.
Bash를 어떤 형태로든 허용하면 모든 명령이 Claude Code의 OS 수준 샌드박스에서 실행됩니다. 쓰기는 run workspace로 제한, 홈 디렉터리와 Claude Code 설정은 읽을 수 없고, 네트워크는 --allow-tools "WebFetch(domain:example.com)"로 허용한 도메인만. 샌드박스 백엔드가 없는 머신에서 Bash/PowerShell을 허용하면 제한 없이 돌리지 않고 각 run을 거부하며 케이스는 run 오류와 보통 0점. 네이티브 Windows는 백엔드가 없으니 WSL2에서, Linux는 bubblewrap와 socat을 먼저 설치하세요. 2.1.299에서 --disallow-tools와 disallowed_tools 케이스 필드가 추가되어 Agent 같은 내장 도구를 eval run에서 뺄 수 있습니다 (이 항목은 changelog 기준이며 위 옵션 표에는 없음).
명령 옵션
전체 목록은 claude plugin eval --help (--case, --tag, --eval-dir, --no-scaffold, --report, --verbose 포함).
| 옵션 | 기본값 | 효과 |
|---|---|---|
--runs <n> |
케이스의 runs, 없으면 3 |
arm당 케이스별 run 수 |
-j, --concurrency <n> |
1 |
동시 에이전트 run 수 1~8. 계정 rate limit 공유. 결과는 케이스 순서 유지 |
--model <model> |
케이스의 model, 없으면 설정된 ANTHROPIC_MODEL, 없으면 Claude Code 기본 |
테스트 대상 에이전트 모델. 모델 롤아웃이 플러그인 회귀로 오인되지 않게 CI에서 고정 |
--judge-model <model> |
백그라운드 작업 모델 | llm/baseline grader 모델 |
--ablation <mode> |
케이스별 결정 | none은 한 arm, with-without는 baseline 추가 |
--threshold <0..1> |
1.0 |
with-arm 점수가 이 이상이면 통과. 미달 케이스가 있으면 exit 1 |
--max-cost-usd <usd> |
상한 없음 | list-price 비용 추정치 상한(플랜 사용량이 아님). 각 run 시작 전 확인. 초과 후엔 더 시작하지 않지만 이미 시작한 run은 끝나므로 넘을 수 있음. 미시작 run이 있으면 부분 결과와 exit 2 |
--allow-tools <tools...> |
없음 | 읽기 전용 외 도구 허용 |
--scaffold |
꺼짐 | 각 케이스의 scaffold_script 실행 |
--trust-plugin |
꺼짐 | 직접 실행할 코드와 suite의 플러그인에 대해 첫 실행 신뢰 프롬프트 생략. CI에서 사용 |
--mocks <mode> |
record |
record는 MCP 호출을 mock으로 응답, 실제 서버 미시작, agent-mock 응답 저장. off는 mock 무시, 실제 MCP 서버 시작 |
--allow-real-servers |
꺼짐 | --mocks record에서 mock 없는 서버의 실제 MCP 서버도 시작 |
--json [path] |
꺼짐 | 결과 문서를 stdout에 출력하거나 .json 경로에 기록. 진행 줄·요약 표 없음 |
--output-dir <dir> |
<eval dir>/results/<timestamp>/ |
aggregate-result.json, report.html 위치 |
--no-publish |
HTML 리포트를 로컬에 유지 | |
--publish-report |
기본적으로 로컬에 남는 경우(예: Claude Code 세션이 시작한 run)에도 게시 | |
--keep-temp |
꺼짐 | 각 run 샌드박스 디렉터리를 유지하고 경로 출력 (디버깅) |
CI에서 evals 실행
--json으로 결과를 기록해 보관하고 종료 코드로 빌드를 실패시킵니다. --trust-plugin으로 신뢰 프롬프트를 피하고, 두 모델을 고정하고, 리포트는 로컬에 두고, 비용 상한을 둡니다.
claude plugin eval . \
--trust-plugin \
--json results.json \
--threshold 0.8 \
--model claude-sonnet-5 \
--judge-model claude-haiku-4-5 \
--no-publish \
--max-cost-usd 20
| 종료 코드 | 의미 |
|---|---|
| 0 | suite 통과: 모든 케이스가 --threshold 이상이고 다른 실패 없음 |
| 1 | 케이스가 threshold 미달, 케이스 파일 로드 실패, 케이스 없음, run 시작 불가, 또는 API 오류로 종료된 run. 플러그인 디렉터리가 신뢰되지 않았는데 --trust-plugin이 없을 때, 잘못된 옵션, --json 결과 기록 실패에도 1 |
| 2 | 부분 실행: --max-cost-usd 상한 도달 또는 첫 run 전/중 자격 증명 거부. results.json은 partial: true와 이유와 함께 기록됨 |
| 130 | 중단됨. 부분 결과 기록 |
| 143 | 종료됨 (CI 타임아웃 등) |
with-minus-without 델타는 보고만 하고 종료 코드를 바꾸지 않으며, HTML 리포트 기록/게시 문제도 마찬가지입니다. 점수가 낮은 이유는 --json 없이 로컬에서 실행해 run별 진행과 grader 줄을 봅니다.
CI 러너에는 다음이 필요합니다.
- 설치와 자격 증명: Claude Code 설치와 환경의 자격 증명 (
ANTHROPIC_API_KEY또는 클라우드 제공자 변수) - 신뢰:
--trust-plugin이 없고 체크아웃 디렉터리가 신뢰되지 않았다면 첫 실행 신뢰 프롬프트가 필요한데 물을 수 없으면 exit 1로 거부 - CI에서
init: 질문할 터미널이 필요하므로 CI에서는claude plugin eval init --bare <name>으로 빈 템플릿 생성
비용 예측: 변경마다 도는 빠른 suite에는 judge를 부르지 않는 grader만, Δ가 불필요하면 --ablation none을 쓰고, partial: true 문서와 skippedPaidGraders가 있는 run은 추세 차트에서 제외하세요.
결과 읽기
run마다 eval 디렉터리 안에 results/<timestamp>/가 만들어지고 aggregate-result.json과 report.html이 들어갑니다. 경로 대상이면 플러그인 아래, 이름으로 지정한 플러그인이면 현재 디렉터리 아래. 요약 표·JSON·리포트는 같은 결과 데이터입니다.
HTML 리포트
report.html은 외부 요청이 없는 단일 자체 완결 파일이라 CI 작업에 첨부하거나 디스크에서 열 수 있습니다. 위에서 아래로 읽습니다.
- verdict 줄과 타일: suite 전체에서 플러그인이 도움이 됐는지. Suite score = 케이스별 with 점수 평균, Ablation Δ = baseline 대비 차이, Cases = threshold 충족 수, Perfect runs = 모든 grader가 통과한 with run 비율
- 케이스 카드: 케이스의
Δ와 with 점수, threshold 위치 눈금.Δ가 음수면 빨간 왼쪽 테두리 - 케이스 안: with run이 먼저, baseline이 뒤. 실패 grader는 설명이 펼쳐져 있고
llmgrader는 judge 투표와 본 증거도 표시. 점수에 안 들어가는 grader(tool_used: Skill등)에는plugin-fired indicator배지 - Prompt and Graders: 케이스 프롬프트와 grader의 rubric/패턴
claude.ai 구독으로 로그인했고 artifacts를 쓸 수 있으면 리포트를 비공개 artifact로 게시하고 Published: <url>을 출력합니다. --no-publish로 로컬 유지. API 키 인증이면 Published: 줄이 없고 로컬 파일이 리포트입니다. Claude Code 세션이 시작한 run도 로컬에 남고 Report: 줄이 kept local이라 나오며, --publish-report로 게시합니다.
JSON 결과
aggregate-result.json과 --json 출력은 schemaVersion: 1의 버전 문서입니다. 필드명은 camelCase이며 기존 이름을 바꾸지 않고 새 필드가 추가되므로 모르는 필드는 무시하세요.
| 필드 | 의미 |
|---|---|
partial, partialReason |
suite가 끝나지 않으면 true와 cost_ceiling, interrupted, auth_failed. 추세 차트에서 제외 |
aggregates.overallScore |
suite 케이스 점수 평균 |
aggregates.casesPassed, aggregates.casesTotal |
--threshold 이상 케이스 수와 전체 |
aggregates.meanDelta |
두 arm 모드에서 케이스 평균 Δ |
cases[].name |
케이스 이름 |
cases[].aggregates.score |
케이스의 with-arm run 평균 점수 |
cases[].aggregates.delta |
with - without. 한 arm이거나 비교 불가면 생략 |
cases[].arms.with[].error |
null 또는 비정상 종료 이유(예: timed out after 300s). 일부는 만든 것으로 채점되므로 null이 아니어도 0점이 아닐 수 있음. API 오류로 종료된 run은 채점 없이 0점 |
cases[].arms.with[].aborted |
mock이 expect:, abort_when, agent-mock 호출 예산으로 run을 멈췄을 때 server, tool, reason과 함께. 0점이고 error는 null |
cases[].arms.with[].skippedPaidGraders |
비용 상한 때문에 judge grader를 건너뛰어 점수가 비교 불가일 때 true |
costUsd, durationSeconds, claudeVersion |
judge 포함 list-price 추정 비용, 실행 시간(초), suite를 돌린 Claude Code 버전 |
run이 접근할 수 있는 것
claude plugin eval은 대상 플러그인의 skill·hook·agent를 로드하고 suite를 내 기기에서 나로서 실행합니다. 플러그인을 대상으로 지정하는 것은 claude --plugin-dir과 같은 신뢰 결정이므로 신뢰하는 플러그인만 평가하세요. 여기의 격리는 테스트 대상 에이전트가 닿는 범위를 제한할 뿐 플러그인 자신의 코드에 대한 경계가 아니며, suite 통과가 플러그인의 안전을 말해 주지 않습니다.
플러그인 디렉터리 신뢰
디렉터리에 처음 claude plugin eval을 실행하면 아무것도 로드하기 전에 Trust this plugin directory?를 묻습니다 (이미 그곳에서 대화형 claude 신뢰 프롬프트를 수락했다면 제외). git 저장소 안에서 yes 하면 대화형 세션도 포함해 저장소 전체를 신뢰합니다. stdin/stdout이 터미널이 아니거나 --json이면 물을 수 없어 exit 1로 거부되므로, 직접 돌릴 플러그인에 한해 --trust-plugin으로 신뢰를 선언합니다. 경로가 아닌 이름으로 지정한 대상(설치된 플러그인, skills-directory 플러그인)은 프롬프트를 건너뜁니다.
해당 run에 플래그를 줄 때만 실행되는 것:
- 케이스의
scaffold_script:--scaffold - 읽기 전용 외 도구:
--allow-tools - 플러그인의 실제 MCP 서버:
--allow-real-servers또는--mocks off
케이스의 allowed_tools나 skill 자체의 allowed-tools frontmatter가 이들을 넓힐 수 없습니다. 직접 쓰지 않은 hook이 있거나 실제 MCP 서버를 시작하면, 컨테이너나 CI 러너 같은 격리 환경에서 돌리지 않는 한 점수를 참고용으로 보세요. hook과 서버는 에이전트 샌드박스 밖에서 돌며 grader가 읽는 파일을 바꿀 수 있습니다.
격리
run마다 임시 홈, 작업 디렉터리, Claude Code 설정을 가지며 테스트 대상 에이전트가 거기서 플러그인만 로드한 claude -p 자식 프로세스로 돕니다.
- 개인/프로젝트 수준은 로드되지 않음: 사용자 설정·hook·
CLAUDE.md·MCP 서버·다른 설치 플러그인·메모리·skill 없음. 프로젝트 범위 설정도 어디서도 읽지 않음 (.claude/,CLAUDE.md,.mcp.json모두 workspace 위/안에서 로드 안 됨,scaffold_script가 쓴 것도).add_dirs는 읽기 전용. 셸 환경 대부분도 차단되고 allowlist와EVAL_*변수만 run에 전달. 케이스가 의존하는 skill·agent·hook·MCP 서버는 테스트 대상 플러그인에 넣어야 함 (scaffold는 파일과 git 상태만 줄 수 있음) - 관리 정책은 여전히 제한 가능: 관리자가 기기에 배포한 managed settings가 run 안에도 적용되어 관리 머신 결과가 다를 수 있음
- Artifact 도구 꺼짐: artifact를 게시하는 skill은 그 단계 전까지 만든 것으로만 채점 가능
- 케이스 정의는 에이전트에게 숨김: run이 eval 디렉터리를 읽을 수 없어 프롬프트·grader·다른 케이스를 볼 수 없음
- 셸 명령 밖에는 네트워크 샌드박스 없음: 허용한 셸 명령은 샌드박스 네트워크 규칙 아래.
WebFetch(domain:…)허용은 그 도메인에 직접 접근하고, 플러그인 자체 hook과 시작한 실제 MCP 서버는 어느 호스트에도 접근 가능
eval suite 레퍼런스
evals/
├── <case>/ # one directory per case; nest under a non-case directory to group
│ ├── prompt.md # frontmatter: case and run fields; body: the prompt
│ ├── case.yaml # optional: context.* fields, or the whole case in one file
│ ├── graders/
│ │ └── <name>.md # one grader per file; frontmatter: type and options; body: rubric
│ ├── mocks/ # optional: mocks for this case only, same layout as below
│ └── <fixtures, scripts, transcripts referenced by case.yaml>
├── mocks/ # optional: suite-wide MCP mocks
│ ├── <server>/
│ │ ├── <tool>.md # one mocked tool; body: the tool result
│ │ ├── _server.md # optional: one agent that answers several tools
│ │ ├── _tools.json # optional: saved tools/list response for real descriptions and schemas
│ │ └── fixtures/ # files inserted with {{file:fixtures/...}}
│ └── .replay/<server>/ # adopted agent-mock recordings, answered without a model call
└── results/<timestamp>/ # written by each run; add results/ to .gitignore
├── aggregate-result.json
├── report.html
└── mock-recordings/ # agent-mock answers from clean runs, with ADOPT.txt
prompt.md 또는 case.yaml이 있는 디렉터리가 케이스이고, grader가 하나도 없으면 graders를 가리키는 invalid case.yaml 오류로 로드 실패합니다.
prompt.md frontmatter
알 수 없는 키는 오류입니다.
| 필드 | 기본값 | 용도 |
|---|---|---|
schema_version |
"1.1", 자동 설정 |
케이스 형식 버전. prompt.md 케이스는 자동이라 거의 안 씀 |
name |
디렉터리 이름 | 케이스 이름. --case glob과 리포트 키 |
description |
사람용. 실행 시 미사용 | |
tags |
[] |
--tag 필터 라벨. 하나라도 맞으면 실행 |
plugins |
가장 가까운 상위 플러그인 | 테스트 대상 플러그인 디렉터리(케이스 디렉터리 기준 상대). 자동 감지 실패 시 plugins: ["../.."] |
runs |
3 |
arm당 run 수 1~50. --runs가 덮어씀 |
expected_outcome |
사람용. 실행 시 미사용 | |
model |
자식 세션 기본 | 테스트 대상 에이전트 모델. --model이 덮어씀 |
max_turns |
10 |
턴 상한 최대 200. 닿으면 run 오류로 기록되어 보통 점수가 낮아지므로 넉넉히 |
timeout_seconds |
300 |
run당 wall-clock 상한 최대 3600 |
allowed_tools |
[] |
케이스가 원하는 도구(예: [Read, Glob, Grep, Skill]). 읽기 전용은 여기 적으면 허용, 나머지는 도구 허용 |
append_system_prompt |
자식 세션 시스템 프롬프트에 덧붙일 텍스트 | |
env |
{} |
자식 세션 추가 환경 변수. 키는 EVAL_[A-Z0-9_]*여야 하고 다른 키는 run 실패. run은 셸에서 allowlist만 상속(PATH·locale 등 기본, 프록시·인증서 설정, 모델 제공자를 선택·인증하는 변수, 대부분의 ANTHROPIC_*·CLAUDE_CODE_*, EVAL_*). 그 외(툴체인 설정 등)는 EVAL_* 변수로 export |
case.yaml 필드
case.yaml은 prompt.md의 대안/보완으로 YAML로 케이스를 기술하고 다른 파일을 가리키는 필드를 더합니다. schema_version: "1.1"과 name 필수. description, tags, plugins, runs, expected_outcome은 최상위, model, max_turns, timeout_seconds, allowed_tools, append_system_prompt, env는 execution: 아래. 둘 다 있으면 prompt.md frontmatter가 같은 case.yaml 필드를 덮어쓰고, prompt.md 본문이 프롬프트, graders/*.md는 case.yaml에 나열된 grader 뒤에 추가됩니다.
| 필드 | 용도 |
|---|---|
context.scaffold_script |
Claude 시작 전 빈 workspace에서 실행하는 케이스 디렉터리의 Bash 스크립트 (fixture 파일/git 저장소 생성). --scaffold일 때만, 최소 환경과 120초 제한, 0이 아닌 종료는 run 실패 |
context.history_file |
이어받을 케이스 디렉터리의 .jsonl transcript. 프롬프트가 다음 사용자 턴 |
context.add_dirs |
run 중 Claude가 읽을 수 있는 케이스 디렉터리 안 디렉터리 (읽기 전용) |
execution.prompt |
prompt.md 없이 전체 케이스를 case.yaml에 둘 때의 프롬프트 |
graders |
grader 목록. 각각 name + graders/*.md frontmatter와 같은 키. llm grader는 rubric을 criteria에 |
grader frontmatter
| 키 | 기본값 | 용도 |
|---|---|---|
type |
필수 | grader 종류 중 하나 |
weight |
1 |
run 점수 내 상대 가중치. 양수 |
arm |
미설정 | with-only는 두 arm run에서 채점 제외, both는 원래 제외될 grader를 양쪽에서 채점 |
grader 이름은 .md를 뺀 파일명입니다.
grader가 볼 수 있는 것
regex는 target, llm은 focus를 받고 값은 같습니다.
| 값 | grader가 보는 것 |
|---|---|
last_message |
Claude의 최종 응답 텍스트. 기본값 |
trace |
세션을 한 줄에 메시지 하나인 JSON으로. regex는 모든 메시지, llm judge는 처음 12개와 마지막 12개. 따옴표·개행이 JSON 이스케이프되어 regex는 "가 아니라 \"에 맞음 |
files |
run 중 Claude가 만든 경로 목록(줄당 하나). 내용이 아님. scaffold가 만들었거나 Claude가 수정만 한 파일 제외 |
{ source: file, path: <path> } |
run 후 workspace 파일 하나의 내용. 플러그인이 만든 것을 채점할 때. PNG/JPEG/GIF/WebP는 llm judge에 이미지로 표시. 그 외 바이너리(.pptx, PDF 등)는 llm judge가 거부하므로 이미지로 렌더하거나 텍스트로 써서 채점 |
mock_calls |
Claude가 mock된 MCP 도구에 한 각 호출의 입력과 mock의 응답 |
grader 종류
| 종류 | 옵션 | 통과 조건 |
|---|---|---|
regex |
pattern, flags, match, target |
JavaScript regex pattern이 target에서 발견됨. match: not_contains는 부재, match: "count:N"은 정확히 N회. 대소문자 무시는 flags: i (인라인 (?i) 미지원) |
tool_used |
tool, input_match, min, max |
선택적 input_match regex에 맞는 JSON 인코딩 입력을 가진 tool 호출 수가 min(기본 1)과 max(기본 무제한) 사이. 호출 금지는 min: 0, max: 0 |
tool_order |
before, after |
두 도구가 모두 호출되고 before에 맞는 첫 호출이 after에 맞는 첫 호출보다 앞섬. 각각 도구 이름 또는 { tool, input_match } |
file_exists |
path, exists |
Claude가 만든 파일이 path glob에 맞음, exists: false면 없음. run 중 생성된 파일만 계산 |
llm |
criteria, focus |
judge 모델이 rubric에 3표 중 2표 이상 PASS. .md 형식에서는 본문이 criteria |
baseline |
baseline_file, criteria |
judge가 run이 케이스 디렉터리의 .jsonl baseline_file 참조 transcript만큼 이상으로 criteria를 만족한다고 판단 |
mock 파일
mocks/<server>/ 아래 <tool>.md 하나가 도구 하나에 답합니다. 본문이 도구 결과이고 {{input.<field>}}, {{file:fixtures/<name>}} 치환을 지원합니다.
| 키 | 기본값 | 용도 |
|---|---|---|
type |
fixed |
fixed는 본문 그대로 반환. agent는 본문을 judge 모델용 지시로 보고 run 동안 서버처럼 행동하며 이전 호출을 기록으로 봄 |
expect |
미설정 | 점 경로 입력 → 타입 이름(string, number, boolean, array, object), /regex/, 리터럴, 허용 리터럴 목록의 맵. 위반하면 run이 0점으로 중단되고 서버·도구·이유와 함께 aborted로 보고 |
error |
false |
fixed 전용. 본문을 도구 오류로 반환 |
abort_when |
미설정 | agent 전용. 에이전트가 run을 중단해도 되는 유일한 조건을 적은 산문 |
서버 디렉터리에는 도구 파일 옆에 선택 파일 두 개:
_server.md: 여러 도구에 답하는 단일type: agentmock, 도구는tools:frontmatter에 나열. 같은 도구의<tool>.md가 우선.tools:가 단일 도구가 아니면 여기의expect:가드는 로드 오류이므로 개별<tool>.md에 둘 것_tools.json: 실제 서버의tools/list응답 저장본. mock 도구가 느슨한 placeholder 대신 실제 설명·입력 스키마를 가짐
케이스 자체 mocks/는 같은 구조이며 suite mock을 파일 단위로 덮어씁니다.
expect 안의 regex 패턴
expect:의 /regex/ 값은 suite 로드 시 검사되는 작은 방언입니다.
- 리터럴 문자,
.,\d같은 이스케이프,[a-z]같은 문자 클래스 - 한 문자/이스케이프/클래스에 붙는
*,+,?,{m,n}한정자 - 시작의 선택적
^, 끝의$ - 플래그는
i,s만
그룹, 교대, 역참조, lookaround, 다른 플래그가 있으면 케이스가 로드되지 않아 0점이며 오류가 패턴을 지목합니다. 여러 정확한 값은 교대 대신 리터럴 목록으로. 패턴은 최대 길이까지만 값을 검사하고 더 긴 값은 위반입니다. 한정자는 그 길이를 낮추고 선두 ^는 높이므로 ^로 고정하고 한정자는 적게 쓰세요.
문제 해결
- "plugin eval is currently in early access": 빌드가 정식 제공 이전.
claude update후 새 세션에서 재실행 - "plugin eval is currently unavailable": Anthropic이 서버 측에서 껐음. 기기에서 켤 방법 없음.
claude update후 나중에 새 세션에서 재시도 - "is not a trusted plugin directory, and this run cannot stop to ask you about it": 아직 신뢰하지 않은 디렉터리의 첫 실행인데 stdin/stdout이 터미널이 아니거나
--json. 터미널에서claude plugin eval <dir>을 한 번 실행해 답하거나, 플러그인 코드와 suite를 신뢰하면--trust-plugin - "is too old for claude plugin eval":
PATH의git이 2.31 미만이라 케이스 실행 전 exit 1. git hook, credential helper 등 저장소 git 설정이 시작할 수 있는 프로그램을 run마다 끄는데, 이는 git 2.31부터 읽는 환경 설정(GIT_CONFIG_COUNT)으로 하기 때문. 2.31 이상 설치 후 재실행 - "cannot be evaluated where sessions load it": 로컬 경로에서 추가한 마켓플레이스에서 설치한 플러그인 이름으로 실행했는데 지금은 세션이 로드할 수 없음 (카탈로그를 읽을 수 없거나 더는 나열하지 않음).
claude plugin marketplace list로 확인해 마켓 디렉터리 복원 등으로 고치거나, 디렉터리를 대상으로claude plugin eval ./plugins/formatter - "No eval cases found": 적용 중인 eval 디렉터리 아래
<case>/prompt.md/<case>/case.yaml이 없거나--case/--tag가 아무것도 못 맞춤. 플러그인 루트에서 실행하거나claude plugin eval init - baseline arm에 플러그인이 없거나 delta가 0:
W/OUT열이 없거나 "ablation requested but no plugin resolved"로 실패하면 보통 케이스의 플러그인을 못 찾은 것. 모든 케이스가context.history_file로 transcript를 이어받으면 열이 없는 것이 정상 (기본 한 arm). 그 외에는 케이스에plugins: ["../.."](케이스 디렉터리에서 플러그인 디렉터리까지 경로)를 추가. 플러그인은 로드됐는데Δ가 0 근처이고tool_used: Skill이 실패하면 보통 진짜 발견: skilldescription이 프롬프트 표현에 발동하지 않음 - "Agent type '...' not found": baseline run에서 Claude가 플러그인 agent를 부르면
Agent type '<plugin>:<agent-name>' not found. Available agents: ....Δ가 플러그인 run과 baseline을 비교하므로 정상 (JSON에서는cases[].arms.without). 플러그인 run에서는allowed_tools에Agent가 있으면my-plugin:code-reviewer같은 네임스페이스 이름으로 호출 가능. baseline을 건너뛰려면--ablation none - 올바른 파일이 만들어졌는데 모두 0점: grader가 내용이 아니라 경로 목록인
files를 target으로 함.{ source: file, path: <path> }사용.file_exists는 run 중 생성된 파일만 보므로 scaffold가 만들거나 Claude가 수정만 한 파일은 보이지 않음. 내용을 채점하거나Edit에tool_used - trace에 대한 regex가 보이는 텍스트와 안 맞음: 기본
target은last_message.trace는 줄마다 JSON이라 따옴표가\". regex는 JavaScript 문법이라(?i)대신flags에i - 도구 거부, MCP 도구 없음, Bash 실행 안 됨: 읽기 전용 외에는
--allow-tools Bash Write같은 허용 필요. 개인 MCP 서버는 run에 로드되지 않음. 플러그인 서버는 옵트인하지 않으면 시작 안 되고 도구는--allow-tools "mcp__plugin_<plugin>_<server>__*"필요. mock 도구는 둘 다 불필요 - 결과가 괜찮은데 exit 1: 모든 케이스가
--threshold이상인데도 1이면--json이어도 stderr 한 줄이 원인을 명시 (케이스 파일 로드 실패, API 오류 종료 run 등). 기본--threshold1.0이므로 하나라도 만점 미만이면 1. 요구 점수에 맞게 설정 --json output path must end in .json: 대상을--json뒤에 둬서 출력 경로로 읽힘.claude plugin eval . --json처럼 대상을 먼저, 또는.json경로 명시- run은 1.0인데 grader가
passed: false: 두 arm 실행에서 설계상 점수 제외된 grader (scored: false) - "ended on an API error and scored 0 without grading": 최소 한 run의 마지막 요청이 HTTP 오류(rate limit, 플랜 사용 한도 등)로 끝남. 그 run은 grader 결과 없이 0점, 점수·
--threshold와 무관하게 비정상 종료. 영향받은 run의 오류는 진행 줄error:뒤(--json이면cases[].arms.with[].error또는cases[].arms.without[].error)에서 확인해 원인 해결 후 재실행. baseline run도 세므로 모든 케이스가 통과해도 명령은 실패할 수 있고, 두 arm 실행에서는 해당 케이스의W/OUT/Δ가—. v2.1.299 이전에는 그 run이 만든 것으로 채점됨 (2.1.299에서 변경) - 중간에 usage/rate-limit 오류로 run 실패: 채점 없이 0점, 명령은 비정상 종료. suite는 끝까지 돌고
partial로 표시되지 않음. 한도 초기화 후 재실행, 한도 아래에 머물려면--runs 1이나--case필터 - mock call budget exceeded: run의 모든
type: agentmock은 케이스max_turns의 4배(기본 10이면 40회) 호출 예산 하나를 공유..replay/녹화로 응답한 호출도 포함되고mock budget진행 줄이 예산을 표시. 초과하면 이 이유로 0점 중단. agent mock 호출이 많은 skill은 케이스의max_turns를 올리세요 - 시간 초과/턴 상한: 기본 10턴, 300초. 더 필요한 작업은
max_turns,timeout_seconds를 올리고 빡빡한 run별 한도 대신--max-cost-usd로 비용 상한 관리
체크리스트
- Claude Code v2.1.269 이상, git 2.31 이상 확인
-
claude plugin eval init또는--bare로 케이스 작성, 케이스마다 grader 1개 이상(결과 + 과정) -
claude plugin eval .로Δ와 실패 grader 확인 - CI는
--trust-plugin,--json,--threshold,--model/--judge-model고정,--max-cost-usd설정 -
evals/results/를.gitignore에 추가,mocks/.replay/는 커밋
함께 보기
- Create a plugin: 테스트할 플러그인 작성, 개발 중
--plugin-dir - Plugin commands reference:
plugin eval,plugin eval init항목. 매니페스트의experimental.evals키 - Skills, Sandboxing
- Publish a plugin: suite 통과 후 배포
- Measure plugin cost and usage: 세션 컨텍스트에 더해지는 양과 사용 여부
기준일
- 문서 작성·동기화 기준일: 2026-10-23 (v2.1.309 반영)
- 원문: https://code.claude.com/docs/en/plugin-evals