
그 신의 스킬, 당신의 머신에서만 작동한다 — 실행 시의 가변성을 CI에서 잡아내는 linter를 만들었다
요약
AI 에이전트용 스킬 파일(SKILL.md)이 작성자의 로컬 환경에 종속되어 CI에서 실패하는 문제를 해결하기 위해, 실행 시의 가변성을 정적 분석으로 잡아내는 linter인 'carrylint'를 소개합니다.
핵심 포인트
- AI 스킬 파일의 절대 경로 및 미선언 CLI 등 환경 종속성 문제 해결
- error, warn, opt-in 3단계 중요도 설정을 통한 정적 분석 제공
- LLM이나 API 키 없이 순수 정적 분석으로 동작하여 모델 독립성 유지
- CI/CD 파이프라인에 통합하여 PR 단계에서 환경 호환성 검증 가능
「지금의 방식을 스킬화해줘」——Claude Code나 Codex에게 이렇게 부탁하는 것만으로, 재사용 가능한 SKILL.md를 얻을 수 있는 시대가 되었다. 나도 이것으로 절차를 차례차례 스킬로 만들며 즐거움에 빠져 있었다.
문제는 배포한 후다. 내 환경에서는 작동하는데, 동료의 환경이나 CI에서는 조용히 실패한다. 살펴보니 이유는 언제나 같았다. 출력 경로가 C:\Users\atlan\...로 되어 있다. codex를 호출하면서 넣는 절차가 적혀 있지 않다. OPENAI_API_KEY가 설정되어 있다는 전제로 작성되어 있다. gpt-image-2가 직접 작성되어 있다.
모두 「만든 본인의 환경」이 각인된 흔적이며, 에러조차 발생하지 않고 다음 사람의 로컬 환경에서 조용히 실패한다. 이를 CI에서 잡아내는 linter, carrylint를 만들었다.
2025년 12월에 Agent Skills는 오픈 표준이 되어, 하나의 SKILL.md가 Claude Code, Codex, Gemini CLI, Cursor 등 20개 이상의 에이전트에서 동작하게 되었다. **형식 (format)**의 가변성은 이미 해결되었다.
하지만 표준이 보장하는 것은 그릇뿐이다. 그 안에 절대 경로(absolute path)나 미선언 CLI가 들어있다면 타인의 환경에서는 작동하지 않는다. 나의 기존 도구들과 역할을 나열해 보면 차이가 명확해진다.
reflint… 참조가 실제로 존재하는가 -
skills-lint… 스킬이 충돌하지 않는가 · frontmatter가 올바른가 -
carrylint… 참조가 다른 환경 · 다른 모델에서 해결되는가
다른 것들은 「사양으로서 올바른가」를 보지만, carrylint는 「다음 사람이 설치해서 실제로 작동하는가」를 본다.
일부러 가변성을 없앤 샘플을 통과시키면 다음과 같이 나온다.
✗ examples/bad/leaky-image-gen/SKILL.md — error 4 / warn 3
✗ :16 [abs-path] 머신 고유의 절대 경로 `C:\Users\atlan\Downloads\out.png`
✗ :16 [undeclared-cli] `codex`를 호출하고 있지만, 설치 절차나 requires 선언이 없습니다
...
오탐(False Positive)은 linter가 미움받는 유일한 이유다. 그래서 중요도를 3단계로 나누고, 모호함이 전혀 없는 것들만 error (PR을 중단시킴)로 설정했다.
error: 절대 경로 (C:\ / /Users/ / $HOME) / 미해결 플레이스홀더 ( <FILL_ME> / YOUR_API_KEY ) / 미선언된 외부 CLI -
warn: ~/ 홈 상대 경로 / 프로바이더 고유 env의 직접 참조 / TODO: 남음 -
opt-in: 모델 ID 직접 작성 (의도적인 고정은 정당하므로 기본 OFF)
실행 시에 LLM도 API 키도 사용하지 않는 순수 정적 분석이다. codex도 gemini도 claude도 똑같이 「프로바이더 고정」으로 취급하므로, 어떤 모델로 작성해도 동일한 규칙으로 동작한다.
name: carrylint
on: [push, pull_request]
jobs:
...
error가 있으면 PR에 인라인 주석이 나타나고 작업(job)이 중단된다. 사람이 의식하지 않아도 매 PR마다 실행된다. 로컬 환경이라면 npx @hyuga/carrylint로 지금 바로 실행할 수 있다.
솔직히 말하겠다. 공개하기 전에 내 리포지토리의 AGENTS.md에 적용했더니, 갑자기 4건의 오탐이 발생했다. 「특정 프로바이더를 편애하지 않는다」고 설명하기 위해 본문에 나열한 codex` / `gemini라는 언급을, carrylint가 「CLI 호출」로 오인한 것이다. 오탐 제로가 실패 원인이라고 써두고서, 가장 먼저 내가 직접 겪었다.
수정 사항은 「백틱(backtick) 내부의 인수를 동반하는 명령만 호출로 간주한다」로 변경했다. 이 과정을 거치며 테스트는 23건 모두 통과(green)되었고, reflint / skills-lint를 상호 적용해도 정합성이 유지되는 상태가 되었다. 실제 데이터에 적용해 보지 않으면 보이지 않는 버그는 반드시 존재한다.
설계 중에 조사하며 깨달은 사실이지만, SKILL.md linter는 이미 7개 이상 존재하며, 나의 skills-lint도 그중 하나다. 빈 땅은 아니었다. 다만 전부 읽고 확인한 결과, 「내용이 실제로 다른 환경에서 작동하는가」를 보고 있는 것은 없었다. carrylint는 오직 그 한 점에 집중하고 있다.
Claude와 Codex를 섞어서 스킬을 배포·공유하는 팀은 아직 많지 않기에, 수요는 조금 앞서가는 것일지도 모른다. 그럼에도 「배포했더니 나만 작동한다」는 상황에 한 번이라도 찔려본 사람에게는 효과가 있을 것이다.
표준은 SKILL.md
를 형식으로 하여 이식 가능하게(portable) 만들었다. 실제로 작동할지는 별개의 문제다. carrylint는 그 부분을 CI에서 걸러낸다.
npx @hyuga/carrylint
-
GitHub Action은
uses: hyuga611/carrylint@v0 -
리포지토리(Repository): https://github.com/hyuga611/carrylint
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기