The .claude/skills check accepts both local layouts and fails on a dangling link. Review: round 1 (@e1ed270) claude agy grok PASS, codex NOT PASS (a dangling top-level link was skipped); fixed in 150b493; round 2 4/4 PASS. Reports and the round-2 identity sidecars go to .agents/reports/<session>/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
6.8 KiB
PR #47 리뷰 — f275a259
NOT PASS. 단일 링크와 스킬별 링크 디렉터리를 모두 받는 수정은 동작한다. 다만 .claude/skills 자체가 깨진 심볼릭 링크이면 부재로 오인해 skip하는 경계 사례가 남아 있다. 작은 테스트 수정으로 해결할 수 있으며 제품 코드 변경이나 재계획은 필요 없다.
Identity와 범위
- loop:
loop-20261004T043243Z-baf133 - base:
6af4a796b1bdce3b2e929b7a45a8a22d66b36855 - head:
e1ed2708d835d712f5f3a9dfd51b8f66ca1ac447 - tree:
2f781e0db4df9a3082b4f55e69c42118868b18a1 - 검증 cwd:
/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261004T043243Z-baf133/1
HEAD/tree 일치, 시작·종료 시 git status clean. 변경 파일은 tests/test_issue42_skill_blocks.py 하나이며 제품 코드·버전 변경은 없다. git diff --check 6af4a79 HEAD 통과.
R1 — P2: 깨진 최상위 링크를 skip하지 말 것
근거: tests/test_issue42_skill_blocks.py:245–:248.
Path.exists()는 링크 자체가 아니라 대상을 따라 검사하므로 dangling symlink에서 False다. 따라서 .claude/skills가 존재하는 잘못된 링크여도 :245의 부재 분기가 실행돼 :247의 symlink 검증까지 도달하지 않는다. 정상적인 “이 워크트리에 로컬 배치가 없음”과 “로컬 배치가 있으나 깨짐”을 구별하지 못한다.
재현: 검증 트리의 원래 .claude/skills가 없음을 확인하고, 그 경로에 존재하지 않는 absent-skills-target을 가리키는 임시 링크를 만든 뒤 해당 pytest node를 실행했다. 실행 후 링크를 삭제했다.
PYTHONDONTWRITEBYTECODE=1 /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python -m pytest -p no:cacheprovider -q --basetemp=/tmp/f275a259-broken_single tests/test_issue42_skill_blocks.py::test_claude_skills_link_resolves_to_agents_skills
- 기대: 잘못된 로딩 경로이므로 fail, nonzero.
- 실제: 1 skipped, rc 0. 반면 디렉터리 안 깨진 개별 링크는 정상적으로 fail한다.
- 영향: 모든 프로젝트 스킬의 진입 경로가 끊겨도 “워크트리에 없음”으로 숨겨져 CI/검증이 성공한다.
- 수정 방향: 부재 skip을
if not link.exists() and not link.is_symlink():로 한정하거나 symlink 검증을 부재 검사보다 먼저 수행한다. 완전 부재는 계속 skip하고, dangling root link는 실패해야 한다. 부재/정상 단일 링크/정상 개별 링크/깨진 최상위 링크를 회귀 사례로 남긴다.
메모리상으로 위 조건만 바꾼 같은 함수에 대해 완전 부재는 SKIP, 깨진 링크는 AssertionError가 되는 것을 확인했다. 체크아웃 코드는 수정하지 않았다.
이 exists() 선행 분기는 이전 버전에도 있었다. 이번 변경이 새로 만든 회귀라고 주장하지 않는다. 그러나 브리프가 명시적으로 요청한 깨진 링크 검토에서 확인된 허점이고, 이 함수가 두 배치의 경로 무결성을 검증하도록 수정되는 범위 안에서 해결할 수 있으므로 반려 사유로 남긴다.
실행 결과
원래 워크트리 배치(로컬 .claude/skills 없음)에서 지정 모듈 실행:
PYTHONDONTWRITEBYTECODE=1 /Users/godopu16/PuKi/laa/canary_projects/multi-agent-mux/.venv/bin/python -m pytest -p no:cacheprovider -q --basetemp=/tmp/f275a259-selected tests/test_issue42_skill_blocks.py
31 passed, 1 skipped in 0.06s, rc 0. 이 기본 실행은 로컬 링크 분기를 검증하지 않으므로 아래 임시 배치 탐침을 별도로 실행했다.
| 임시 배치 | 실제 pytest 결과 | 판단 |
|---|---|---|
.claude/skills 완전 부재 |
1 skipped, rc 0 | 정상 |
| 8개 스킬별 올바른 링크 | 1 passed, rc 0 | 정상; base 함수는 같은 배치에서 AssertionError |
| 위 배치 + lib.sh/lib_py 링크 및 notes.txt | 1 passed, rc 0 | 정상 |
| 개별 링크 하나가 다른 스킬을 가리킴 | 1 failed, rc 1 | 정상 거부 |
| 개별 링크 하나가 깨짐 | 1 failed, rc 1 | 정상 거부 |
단일 링크 → .agents/skills |
1 passed, rc 0 | 정상 |
| 단일 링크 → 다른 기존 디렉터리 | 1 failed, rc 1 | 정상 거부 |
| 단일 링크 → 존재하지 않는 대상 | 1 skipped, rc 0 | R1 |
| 일반 파일 | 1 failed, rc 1 | 정상 거부 |
| 빈 디렉터리 | 1 failed, rc 1 | 정상 거부 |
탐침 스크립트: /tmp/f275a259-layout-probes.py; 결과: /tmp/f275a259-layout-probes.log. 위 cwd에서 PYTHONDONTWRITEBYTECODE=1 <위 Python> /tmp/f275a259-layout-probes.py로 재현한다. 스크립트는 원래 경로가 없는지 먼저 확인하고 자신이 만든 임시 배치만 finally에서 정리한다. 기대한 부정 사례의 pytest 실패는 제품 스위트 실패 수치에 합산하지 않았다.
나머지 검토 항목
- 실제 본 체크아웃: 읽기 전용 조사에서
.claude/skills는 디렉터리이며 8개 스킬 링크와 lib.sh/lib_py 링크가 있다. 8개 스킬의 resolve 결과는 각각 본 체크아웃의.agents/skills/<skill>과 정확히 일치했다. 수정의 동기가 된 정상 배치를 지원한다. - 빈 SKILL_NAMES: 대상 함수만 메모리상
SKILL_NAMES=[]로 실행하면 for-loop가 비어 통과한다. 그러나 같은 모듈의test_inventory_covers_eight_skills가 8개를 강제해 전체 모듈은 실패한다. 현재 모듈 단위 실행에는 침묵하는 성공이 없으므로 별도 반려 사유는 아니다. 대상 node를 단독 운영할 때까지 방어하려면 nonempty 단언을 추가할 수 있다. - 여분 항목: framework 8개를 확인하는 테스트에서 lib.sh/lib_py 등 여분 항목을 금지하지 않는 것은 적절하다. Claude Code 공식 문서는 프로젝트 스킬을
.claude/skills/<skill-name>/SKILL.md로 설명하며 스킬 폴더의 symlink를 지원한다. 일반 helper 파일·SKILL.md 없는 helper 디렉터리를 framework 스킬로 취급할 필요는 없다. 다만 이 테스트는 다른 사용자 스킬 전체를 감사하지 않으므로 docstring의 “each skill Claude Code loads”는 “each framework skill” 정도로 좁히면 더 정확하다. 공식 Claude Code 스킬 로딩 문서. - 과도한 엄격성: 스킬 디렉터리의 내용 복사본은 같은 resolve 대상이 아니므로 거부된다. 브리프가 두 링크 배치만 지원하려는 계약에 맞는다. 다른 추가 사용자 스킬의 존재는 허용된다.
검증 트리 안의 임시 .claude/skills는 모두 삭제했다. 본 체크아웃의 배치를 변경하지 않았으며 제품 코드·실제 .mam.env·실제 agent/herdr 상태를 건드리지 않았다. 실제 .mam 쓰기는 지시된 잡 lifecycle과 이 최종 리포트에 한정했다.
[VERDICT: NOT PASS]