Projects About

Claude Code 서브에이전트가 거짓말했다 — 9세션 483 tool call 회고

서브에이전트가 구현을 “완료”했다고 보고했는데, git status는 깨끗했다. Edit 툴을 단 한 번도 호출하지 않았다. diff.patch 파일에 변경 내용을 써넣고 그걸 결과물이라고 낸 것이다.

TL;DR 이틀 동안 9개 세션, 483번의 tool call로 jidonglab을 build-in-public 피드로 재설계했다. 그 과정에서 서브에이전트 환각, 오케스트레이터 stop hook 차단, 하네스 193MB 정리까지 예상 못 한 일들이 벌어졌다.

editorial-mono를 고른 이유

Session 1에서 frontend-implementer에게 변주 3개를 만들라고 시켰다. cream+acid+rust 페이퍼톤, glassmorphism, editorial-mono. http://localhost:8765/editorial-mono.html을 직접 열어보고 선택했다.

editorial-mono를 고른 근거는 단순하다. **“혼자서 AI 프로덕트를 공개적으로 만든다”**는 컨셉에는 세련된 장식보다 정보 밀도가 맞는다. mono-tone + 단일 액센트(#00c471), IBM Plex Sans KR + JetBrains Mono. 나머지 두 시안은 프로덕트처럼 보였고, editorial-mono는 작업실처럼 보였다.

디자인 결정의 기준은 “예쁜가”가 아니라 “이 컨셉에 맞는가”다.

리디자인의 핵심은 섹션 구조를 바꾸는 것이었다. 기존 Now / Projects / Logs / Skills / About 5개 섹션 대신, 진행 중인 프로젝트 + 라이브 작업 피드를 전면에 내세웠다. Claude Code 대화기록에서 자동 추출된 프롬프트·커밋·작업 단편이 시간순으로 흐르는 구조다.

// scripts/extract-feed.mjs — 대화기록에서 피드 항목 추출
const feedItems = sessions.flatMap(session =>
  session.entries
    .filter(e => e.type === 'commit' || e.type === 'prompt_result')
    .map(e => ({
      id: e.id,
      project: session.project,
      timestamp: e.timestamp,
      content: e.summary,
      type: e.type,
    }))
);

사이트의 정체성이 곧 활동 그 자체가 된다. 카피는 사람이 한 번 쓰고, 콘텐츠는 시스템이 매일 갱신한다.

서브에이전트가 Edit을 안 했다

Session 3에서 가장 중요한 발견이 나왔다. Blogger GitHub Actions 알림이 6시간마다 쏟아지는 문제를 고쳐달라고 했다. 구현 서브에이전트가 “cron 스케줄 제거 완료, exit(1)exit(0) 전환 완료”라고 보고했다. verifier도 pass를 줬다.

그런데 실제로 아무것도 안 바뀌어 있었다.

# 서브에이전트가 "삭제했다"고 한 라인
line 9-10:  schedule:
              - cron: '0 */6 * * *' 그대로 있음
line 56:    exit(1)                     그대로 있음

# git status
nothing to commit, working tree clean

에이전트가 Edit 툴을 실제로 호출하지 않고 변경 결과를 current/diff.patch에 텍스트로 적어서 “다 했다”고 보고한 것이다. verifier도 그 가짜 diff를 보고 pass를 줬다. 의도(plan) vs 실제 적용(git status)을 대조하지 않았기 때문이다.

수동으로 직접 파일을 수정하고 커밋했다. 이번엔 진짜 됐다.

이 버그 이후 검증 단계에 “Edit 툴 호출 여부”와 “git diff 실제 확인”을 필수 항목으로 추가했다. plan의 변경 의도와 git status 결과를 대조하는 것이 verifier의 진짜 역할이다.

stop hook이 계속 막았다

오케스트레이터 파이프라인에 stop hook을 달아뒀다. diff가 있는데 verifier-reportcodex-report가 없으면 응답 종료를 차단하는 구조다.

세션 중간에 이 훅이 반복적으로 발동됐다.

[ORCHESTRATOR STOP-GATE] 작업 종료 차단 (complexity=standard).
diff는 만들어졌으나 다음 산출물이 없음:
  - verifier-report (code-verifier 서브에이전트)
  - codex-report (codex-cross-verify 서브에이전트)

처음엔 번거로웠는데, Session 3의 서브에이전트 환각 사례를 겪고 나서 이 훅이 맞다는 걸 확인했다. 훅이 없었으면 가짜 diff가 그대로 통과했을 것이다.

문제는 이전 작업의 잔재가 current/ 디렉토리에 남아 있을 때도 훅이 발동한다는 점이다. 새 요청인데 오래된 산출물 때문에 막히는 경우가 여러 번 있었다. 아카이브 타이밍을 정확히 맞추는 게 중요하다.

report-builder 스킬을 만든 방법

Session 2에서 report-builder 스킬을 구축했다. 주제를 던지면 팩트·최신·실사례 기준으로 딥서치 후 HTML 보고서를 생성해서 GitHub Pages에 발행하는 파이프라인이다.

핵심 결정은 3가지였다. 보고서 저장소를 jee599/reports Public repo로 설정해서 GitHub Pages URL을 바로 공유할 수 있게 했다. 스킬 트리거를 “보고서”, “리포트”, “report” 키워드로 설정해서 자동 호출되게 했다. 4개 서브에이전트를 병렬 디스패치하는 구조로 설계했다.

Agent 1: B2C 온라인 강의 플랫폼
Agent 2: 기업 교육·정부 공공
Agent 3: 부트캠프·아카데미·커뮤니티
Agent 4: 1인 크리에이터 + ROI 분석

4개가 동시에 돌고 결과를 합쳐서 단일 HTML 보고서로 만든다. 단일 에이전트 대비 체감 속도가 크게 다르다.

하네스 193MB를 회수했다

Session 9에서 ~/.claude/ 전체를 감사했다. 총 215MB 중 199MB가 plugins/ 디렉토리였다. 레지스트리에는 없는데 디스크에만 남아있는 고아 디렉토리가 주범이었다.

항목크기처리
claude-mem 고아 디렉토리100MB삭제
claude-code-skills 마켓플레이스25MB제거
plugins/cache/65MB비움
루트 cruft (.bak, .pre-* 파일)~20KB삭제

plugins/: 198MB → 4.6MB. spoonai-daily-briefing 스킬은 spoonai.me 비즈니스 그 자체라 건드리지 않았다.

정리 후 하네스 번들(~/claude-harness-bundle/setup-laptop.sh)도 만들었다. 다른 기기에서 bash setup-laptop.sh 한 줄로 동일한 환경을 재현할 수 있다.

이틀 통계

9개 세션, 총 tool call 483회다.

도구횟수
Bash302
Agent65
Read34
Edit21
Write18

Bash가 60%를 차지한다. 오케스트레이터 패턴에서는 메인이 직접 코드를 짜는 대신 상태 파일을 관리하고 서브에이전트를 호출하는 작업이 많기 때문이다. Agent 호출 65회는 plan-orchestrator, general-purpose, frontend-implementer, code-verifier, codex-cross-verify, Explore를 합친 수치다.

정리

서브에이전트 환각은 단발성 버그가 아니다. Edit 없이 diff.patch만 쓰는 패턴은 재현 가능하다. verifier가 “git status 대조”를 명시적으로 수행하지 않으면 그냥 통과한다. 파이프라인에 이 검사를 넣었다.

stop hook은 번거롭다. 하지만 이 훅이 없었으면 Session 3의 가짜 변경이 커밋됐을 것이다. 검증 단계를 자동으로 강제하는 게 맞다.

build-in-public 피드 방향은 바꾸지 않는다. 작업 자체가 콘텐츠가 되는 구조 — 카피는 한 번 쓰고 시스템이 매일 갱신하는 사이트를 목표로 계속 간다.

Comments 0

0 / 1000