On this page
표준 라이브러리만으로 헬퍼 이식성 확보하기
여러 에이전트로 배포되는 헬퍼는 CI에서 비-stdlib 의존성에 기댈 때 깨져요. 표준 라이브러리만 쓰도록 묶어 두세요. PyYAML, npm 패키지, BSD/GNU sed 플래그가 단골 함정 셋이에요.
스킬에 헬퍼 스크립트를 넣어 배포하고, 로컬에서 돌려 보고, 출력 확인하고, 다 됐다 싶었어요. PR Round 1 리뷰가 세 가지 발견 사항을 들고 돌아왔는데, 셋 다 같은 뿌리에서 나온 거였어요. 헬퍼가 소비자의 머신이 실제로 가진 것보다 풍성한 환경을 가정하고 있었어요. PyYAML이 stdlib에 없었어요. 셸 파이프라인이 BSD와 GNU 사이에서 다르게 동작하는 플래그를 썼어요. 폴백 경로에 제 사용자명이 하드코딩되어 있었고요.
악의가 있어서가 아니에요. 전부 “편의성 함정”이었어요. 작성자 머신엔 의존성이 있으니 테스트가 통과해요. CI는 헬퍼가 처음으로 가난한 환경을 만나는 자리고, 거기서 깨지는 거죠.
문제
스킬에 따라 배포되는 헬퍼(Node, Python, shell)는 종종 소비자 머신이 실제로
제공하는 것보다 풍성한 환경을 가정해요. 미니멀 CI 컨테이너에서 PyYAML import가
실패해요. npm install은 모든 에이전트가 반복해야 하는 셋업 마찰을 더해요. sed -i는 BSD(macOS 기본)와 GNU(Linux 기본)에서 다르게 동작해요 — 같은
플래그, 다른 행동.
컨텍스트
헬퍼는 신뢰 경계를 넘나들어요:
- 3B 헬퍼는 Claude / Codex / Gemini 세션에 배포돼요
- 각 에이전트는 다른 OS나 CI 이미지에서 돌아요
- 각 환경의 “기본 설치된 것”이 달라요
- 누가 레포를 처음 클론했을 때 실패하는 헬퍼는 신뢰를 잃어요
“첫 실행” 경험이 채택에 가장 중요해요. 기여자가 레포를 클론하고 헬퍼를
실행했는데 ModuleNotFoundError나 조용한 셸 분기 차이를 보면, 떠나요. 헬퍼는
셋업 없이 동작해야 해요. 아니면 추상화가 깨진 거예요.
해결책
헬퍼를 표준 라이브러리만 쓰도록 묶어요:
| 언어 | 안전한 것 | 안전하지 않은 것 |
|---|---|---|
| Python | re, json, os, pathlib, argparse | yaml (PyYAML), requests, pydantic |
| Node | fs, path, os, url, crypto | npm install 한 모든 패키지 |
| Shell | POSIX 플래그만 (-name, -type, -l, -I) | sed -i '' (BSD) vs sed -i (GNU); xargs -I {} 의미 차이 |
헬퍼가 정말 라이브러리가 필요할 땐, 다음을 우선해 봐요:
- Python →
re+ 수동 split으로 인라인 파싱하거나, 표준 subprocess 모듈로 POSIX 보장된 도구(grep,awk)를 호출해요. - Node → 일회성 셸 호출은 표준 subprocess 모듈에 의존해요. npm은 피해요.
- Shell → 헬퍼를 Node나 Python으로 옮겨서 이식성 문제를 “Node가 깔려 있나”로 바꿔요. “이 특정 플래그가 여기서 지원되나”를 신경 쓰지 않게요.
다음 세 서브섹션은 PR #39에서 발견된 실제 교환을 보여줘요. 각각 일반성을 조금 잃지만 이식성에서 훨씬 큰 이득을 얻어요.
예시: PyYAML → re
전:
import yaml
fm = yaml.safe_load(parts[1])
version = fm.get("metadata", {}).get("version") 후:
import re
fm_text = parts[1]
version_m = re.search(r'version:s*"([^"]+)"', fm_text)
if not version_m:
sys.exit(2) 일반 YAML 파싱은 잃지만 — 의존성 없는 실행을 얻어요. frontmatter에서 1~2 필드만 읽는 헬퍼라면 이게 옳은 트레이드오프예요. 전체 문서를 파싱해야 한다면 스킬 언어를 바꿔서 stdlib에 그 포맷이 있는 쪽으로 옮겨요(Node에 JSON, Go의 도구 stdlib에 YAML).
예시: BSD vs GNU sed 파이프라인 → Node
전:
find ${3B_PATH}/journals -name '*.md' -mtime -3
-exec grep -l '### Next' {} + |
xargs -I{} sh -c 'echo "## File: {}"; grep -A8 "### Next" {}' 이건 작성자의 macOS에서는 동작했지만 Linux CI에서는 다른 출력을 만들었어요
(xargs -I 의미 차이, sed in/out 의미 차이). 수정: Node로 다시 작성해서 fs.readdirSync로 디렉토리 트리를 순회하고 인라인 파싱했어요.
셸 파이프라인은 xargs -I에 손대는 순간 이식성을 잃어요. 그러면 가장 싼 행보는
보통 셸을 완전히 떠나는 거예요. Node의 stdlib에는 “파일 찾고, 읽고, 섹션 추출”
하는 데 필요한 게 다 있고, Node가 깔린 모든 플랫폼에서 같이 동작해요.
예시: 하드코딩된 사용자 경로 → $HOME
전:
REPO_ROOT="${REPO_ROOT:-$(git ... || echo /Users/brandonwie/dev/personal/3b)}" 후:
REPO_ROOT="${REPO_ROOT:-$(git ... || echo "$HOME/dev/personal/3b")}" 한 글자 차이(/Users/brandonwie → $HOME). 작성자가 아닌 모든 머신에서
깨지지만, canonical 3B 경로 컨벤션을 따르는 모든 머신에서 동작해요. 너무 작아서
민망한 종류의 버그예요. 그런데 또 그래서 쉽게 출시되는 종류이기도 해요. 폴백
경로는 마지막 보루로 의도된 거였고 — 작성자 머신이 폴백을 칠 일이 없어서
하드코딩된 값이 로컬에서 깨진 적이 없거든요.
부딪힌 어려움
- “편의성 함정”: 작성자 머신에 의존성이 있으니 헬퍼 테스트가 통과해요. CI가 헬퍼가 가난한 환경을 처음 만나는 자리고, import에서 실패해요. PR #39 Round 1 리뷰가 머지 전에 잡았어요. 그 리뷰가 없었으면 비-작성자 사용자에겐 깨진 채로 나갔을 거예요.
- JSON 파싱을
re기반 코드로 쪼개는 건 “라이브러리를 쓰자”에서 후퇴하는 것처럼 느껴져요. 스키마가 안정된 단일 필드 읽기엔 옳은 트레이드오프예요. 전체 문서 파싱이라면 stdlib 등가물을 설치하거나, stdlib에 그 포맷이 있는 언어로 소비자를 다시 쓰세요(Node엔 JSON, Go의 도구엔 YAML, Rust의 serde-json). grep/awk호출은 PATH에 POSIX 유틸이 있어야 한다는 의존을 추가해요. 보통은 괜찮지만, 미니멀 Alpine 이미지엔awk가 없는 경우도 있어요. 가정하는 POSIX 베이스라인을 문서화하세요.
핵심 포인트
- 크로스-에이전트 헬퍼는 stdlib를 기본으로 —
npm install과pip install은 모든 에이전트가 반복하는 셋업 마찰이에요. - 세 가지 단골 함정: PyYAML(파이썬), npm 패키지(Node), BSD/GNU
sed분기(셸). 각각 stdlib 우회 방법이 있어요. $HOME을 하드코딩된 사용자 경로 위에 — 본인 머신에서만 돌릴 거라 해도 CI / 기여자 머신엔 본인 사용자가 없어요.- 셸 헬퍼엔 POSIX 플래그만 —
find -name,grep -l,wc -l는 안전해요.sed -i ''(BSD)와xargs -I {}(의미 차이)는 그렇지 않아요. 출시 전에 Alpine + macOS + Ubuntu에서 테스트하세요. - stdlib 갭이 너무 크면 언어를 바꿔요 — Python에서 YAML 파싱이 필요하면
헬퍼를 Node로 작성하세요(
js-yaml은 npm이지만, 헬퍼가 사실 YAML이 필요 없을 수도 있어요. schema-versioned envelope 패턴이 Node 친화적인 대안이에요).
언제 쓰나
- 여러 에이전트가 소비하는 스킬에 따라 배포되는 모든 헬퍼.
- 미니멀 CI 컨테이너에서 도는 모든 스크립트.
- 셋업 없이 기여자 머신에서 동작해야 하는 모든 도구.
언제 쓰면 안 되나
- 작성자 머신에서만 돌고 의도적으로 머신 특정 도구를 쓰는 내부 스크립트. 가정을 문서화하세요.
- 의존성 관리가 이미 셋업된 프로덕션 애플리케이션 코드(npm, pip, poetry). 거기는 이식성 우려가 다른 종류예요.
마무리
이번 리뷰에서 배운 건 구체적인 수정이 아니었어요 — 그건 기계적이었어요. “내 머신에서는 동작한다”가 환경을 넘나드는 헬퍼의 잘못된 베이스라인이라는 거였어요. 올바른 베이스라인은 “미니멀 컨테이너 안에서 막 클론한 레포에서 동작한다”예요. 헬퍼가 그 바를 못 넘으면 추상화가 신뢰를 새고 있는 거예요.
stdlib-only 제약이 그 바를 도달 가능하게 만들어요. 더하는 모든 의존성은 소비자가 빠뜨릴 가능성이고, 그것도 첫 실행에서 — 정확히 신뢰를 가장 회복하기 어려운 순간에 — 드러나는 종류예요.
참고
knowledge/general/schema-versioned-helper-json-envelope.md— 헬퍼가 무엇을 내보내야 하는지. 이 글은 무엇으로 만들어져야 하는지를 다뤄요.knowledge/ai-ml/cross-agent-skill-alias-generalization.md— 스킬 측면: 에이전트 사이에서 동작하는 도구 별칭 선언법.