하네스 안티패턴 — 코드에서 실제로 관찰된 위험·실패 설계 14선
하네스 안티패턴 — 코드에서 실제로 관찰된 위험·실패 설계 14선
한 줄 요약
같은 기법이 “어디에 쓰였느냐”에 따라 명약이 되기도 독약이 되기도 한다 — 안티패턴은 기법 자체가 아니라 “잘못된 곳에 쓴 기법”이다. 왜 배우나: 베스트 차용안만 외우면 그 그림자(같은 코드가 동시에 보여주는 하면 안 되는 것)를 놓쳐, 내 스택(Claude+Codex+OMC) 운영에서 그대로 지뢰를 밟는다.
그림
아래는 14개 안티패턴을 5개 묶음(A~E)으로 나눈 지도다. 각 묶음은 “무엇을 잘못 믿었나”라는 한 가지 착각으로 묶인다.
flowchart TD
Q{"이 설계가<br/>안티패턴인가?"}
Q --> A["A. 강제력의 거짓말<br/>막는 척하지만 안 막음"]
Q --> B["B. 검증의 우회 표면<br/>통과가 너무 쉬움"]
Q --> C["C. 자율 루프의 폭주<br/>멈출 줄 모름"]
Q --> D["D. 컨텍스트·번들 비대<br/>다 욱여넣음"]
Q --> E["E. 신뢰 경계의 구멍<br/>가장 약한 곳이 뚫림"]
A --> A1["1 fail-open을 보안게이트에"]
A --> A2["2 감지만 하고 안 막는 게이트"]
A --> A3["3 산문 규칙을 강제로 착각"]
B --> B1["4 정규식 한 겹에 위험판정"]
B --> B2["5 셀프승인 writer=reviewer"]
B --> B3["6 돌렸음=통과 success≠ran"]
C --> C1["7 가드 없는 Stop 재주입"]
C --> C2["8 score만 보고 goal drift 무시"]
C --> C3["9 exit 2를 재시도로 자동복구"]
D --> D1["10 거대 번들 운명공동체"]
D --> D2["11 마커 없는 자유본문"]
D --> D3["12 거대 카탈로그 라우팅 혼란"]
E --> E1["13 전역 느슨·프로젝트만 봉인"]
E --> E2["14 비밀정보 평문 영속"]
flowchart LR
행동{"이 행동은<br/>되돌릴 수 있나?"}
행동 -->|"가역: Stop·재시도"| 오픈["fail-open<br/>생산성 보호 = 정답"]
행동 -->|"비가역: 권한·커밋·승인·입고"| 클로즈["fail-closed<br/>또는 명시적 degraded"]
오픈 -.같은 패턴을 비가역에 쓰면.-> 함정["안티패턴 1번<br/>가드를 죽이면 곧 허용"]
쉽게 풀기
안티패턴을 이해하는 가장 쉬운 비유는 건물 보안이다.
- A. 강제력의 거짓말 — 출입문에 “외부인 출입금지”라고 붙여만 두고 문은 항상 열려 있는 상태. 더 나쁜 건, 경비원이 쓰러지면(크래시) 자동으로 문이 열리도록 설계한 것이다. 공격자는 문을 통과할 필요 없이 경비원을 쓰러뜨리기만 하면 된다.
- B. 검증의 우회 표면 — 검문소를 하나만 두고, 거기에 “샘플이라고 말하면 통과” 같은 면제 규칙을 붙인 꼴. 검문하는 사람과 통과하려는 사람이 같은 인물(셀프승인)이기도 하다. “검사를 돌렸다”는 것과 “검사를 통과했다”는 건 전혀 다른데 이를 같게 본다.
- C. 자율 루프의 폭주 — “통과할 때까지 다시 해” 라고만 시키고 “몇 번까지”를 안 정한 자동화. 로봇이 벽에 부딪히며 영원히 같은 동작을 반복한다. 점수만 보고 칭찬하다 보니 로봇이 점수만 올리고 원래 목적에서 멀어져도(goal drift) 모른다.
- D. 컨텍스트·번들 비대 — 공구 수백 개를 한 상자에 다 넣어 들고 다니는 정비공. 상자 하나가 망가지면 모든 공구를 못 쓰고(운명공동체), 무거워서 정작 필요한 공구를 못 찾는다(토큰 자살).
- E. 신뢰 경계의 구멍 — 금고는 잠갔는데 현관문은 열어둔 집. 그리고 비밀번호를 포스트잇에 적어 책상에 붙여둔 것(평문 영속). 가드는 항상 가장 약한 레이어에서 뚫린다.
핵심 원리 한 줄: fail-open(실패 시 통과)은 advisory 훅에선 정답이지만, 보안·권한·검증 게이트에선 사고다. 그래서 우리는 “기법이 좋다/나쁘다”가 아니라 “이 행동이 되돌릴 수 있나?”를 먼저 묻는다.
핵심 정리
각 안티패턴은 (안티패턴 / 왜 위험 / 대안) 의 한 줄 요지로 압축한다. “어디서 관찰됐나”는 아래 근거 콜아웃으로 분산했다.
| # | 안티패턴 | 한 줄 대안 |
|---|---|---|
| 1 | fail-open을 보안/검증 게이트에 | 비가역 행동은 fail-closed 또는 명시적 degraded |
| 2 | 감지만 하고 안 막는 ADVISORY 게이트 | 무한루프 가드를 달고 차단하라 (도망치지 말고) |
| 3 | 산문 규칙을 강제로 착각 | authority ladder — 절대규칙은 permissions.deny+hook |
| 4 | 정규식 한 겹에 위험 판정 | defense in depth — 메타문자 전면거부+realpath+다중겹 |
| 5 | 셀프승인 (writer=reviewer) | 검증을 독립 컨텍스트로 분리 |
| 6 | ”돌렸음=통과” (success≠ran) | exit_code 우선 판정 + 커버리지 별도 열 |
| 7 | 가드 없는 Stop 재주입 | MAX 카운터+재진입 가드+예외통과를 짝으로 |
| 8 | score만 보고 goal drift 무시 | 다중 AND 게이트 + drift 임계값 + 롤백 |
| 9 | exit 2를 재시도로 자동복구 | 운영신호만 bounded retry, 검증실패는 incident+멈춤 |
| 10 | 거대 번들 (운명공동체) | progressive disclosure + core/rare 서버 분리 |
| 11 | 마커 없는 자유본문 컨텍스트 | 가변 섹션을 고유 XML 마커로 감싸기 |
| 12 | 거대 카탈로그 (라우팅 혼란) | 좁게 시작, 검증된 패턴만 표면으로 승격 |
| 13 | 전역 느슨·프로젝트만 봉인 | 역할별 프로파일 분리, 일관된 권한계약 |
| 14 | 비밀정보 평문 영속 | 모든 저장 sink 직전 결정론적 redact() |
[!note] 분류 원칙 같은 기법이 맥락에 따라 안티패턴이 된다. fail-open은 advisory 훅에선 정답, 보안 게이트에선 사고다. 그래서 “기법 자체”가 아니라 “잘못된 곳에 쓴 기법”을 안티패턴으로 본다. 7개 하네스(Claude Code · Codex · OMC · gajae-code · ouroboros · fable-ish · 내 패턴)를 10개 축으로 횡단분석하며 모은 카탈로그다.
[!warning] A. 강제력의 거짓말 — 근거(어디서 관찰됐나)
- 1 fail-open: fable-ish 전 훅
try/except→exit0, OMCrun.cjsstale-path 폴백, 내 패턴 “모든 에러경로{continue:true}”. 이들은 advisory라 옳지만 같은 패턴을 권한/검증/모델라우팅 강제에 쓰면 가드 버그가 곧 우회로다. 30_hooks Codex 교차검증: “advisory엔 맞지만 권한/검증 hook에선 실패=우회”. 정답 모델은 Codex Guardian이 타임아웃/비-JSON을 거부로 처리하는 것(70_guardrails-sandbox). “fail-open 철칙”이라는 슬로건 자체가 안티패턴.- 2 ADVISORY 비차단: OMC
verify-deliverables.mjs가 산출물 누락을 감지하나continue:true, suppressOutput:true로 조용히 통과 — 되먹임 경로가 사실상 꺼짐(#3209 무한루프 회피 대가). 80_verification “문화는 최강, 강제력엔 구멍”. 대안은 fable-ish 차단형 Stop(changed_files && !verified → decision:block)을 이식하되MAX_STOP_BLOCKS=2+stop_hook_active재진입 가드+예외통과를 같이 둘 것(90_self-improvement).- 3 산문=강제 착각: 내 패턴 자기인정 “PreToolUse 차단 hooks가 settings.json에 전무, 규율이 산문 의존”(3_학습인사이트 원칙10). gajae “자연어 .md 계약 의존”. Claude Code 설계 자체가 “CLAUDE.md=system 뒤 user 메시지=강제 아닌 컨텍스트”(20_context-assembly). authority ladder:
system/instructions(못 무시) >hook decision:block>tool permission deny>additionalContext>user memory(증발 가능).
[!warning] B. 검증의 우회 표면 — 근거
- 4 정규식 한 겹: fable-ish
SECRET_REQUEST_RE/DESTRUCTIVE_REQUEST_RE+ 면제SAMPLE_RE— “샘플/예시” 단어 하나로 sensitive 해제(70_guardrails-sandbox). 자체 측정 16개 위험패턴 중 4개만 잡힘(3_학습인사이트 원칙5). 내.gjc/차단 정규식도 symlink·../·대소문자·WSL경로·Bash redirection으로 우회(1_내패턴_개선백로그). 대안: 셸 메타문자 전면거부 →realpath심링크 해소 → OS샌드박스+정적판정+LLM검토 다중겹(Codex 4중).- 5 셀프승인: 7개 전부가 금지로 수렴 = 강한 안티패턴 증거. OMC
<execution_protocols>self-approve 금지, 내 패턴writer≠reviewer, ouroboros 구현=UNRESTRICTED/평가=READ_ONLY(80_verification 공통패턴4). OMC “false approval은 10~100배 비싸다”. LLM 리뷰어는 트랜스크립트를 untrusted evidence로 라벨.- 6 success≠ran: fable-ish가 피한 방식이 교훈 —
verified=verification_results[].success is True가 하나라도 있어야 함(80_verification 차용안1). exit_code를 안 보고 “테스트 돌림=통과”가 흔한 함정. 대안: exit_code 우선→텍스트 폴백, coverage_relation(변경파일 실제 덮었나) 별도 열,stated_but_unstarted정규식 가드.
[!warning] C. 자율 루프의 폭주 — 근거
- 7 가드 없는 Stop: 피하는 장치가 7개에 보편 — CC “8연속 block이면 강제종료”, fable-ish
MAX_STOP_BLOCKS=2+stop_hook_active, ralph-loopMAX_ITERATIONS, 내 패턴 “2회 실패→quarantine”(10_agent-loop 공통패턴2). 예외통과(user input·권한 대기·context-limit·auth failure)는 반드시 통과시킬 것.- 8 goal drift 무시: OMC self-improve가
best_score만 봄 → “점수는 오르는데 goal 이탈 못 잡음”(90_self-improvement 차용안2). 대안: ouroboroscombined_drift = goal0.5+constraint0.3+ontology0.2 ≤0.3+ no-regression 롤백(git reset --hard) + sealed_files로 평가기 자기수정(보상 해킹) 차단. 단 drift는 hard-stop보다 critic escalation/quarantine이 오탐에 안전.- 9 exit 2 자동복구: Codex 교차검증 경고 — “
exit 2는 ‘재시도’가 아니라 ‘멈춰야 하는 게이트 실패’”(1_내패턴_개선백로그). 자동복구는 운영신호(429/refresh_token invalid/session 차단)에만 bounded retry, 검증 실패는 incident+멈춤.
[!warning] D. 컨텍스트·번들 비대 — 근거
- 10 거대 번들: OMC/내 패턴 단일
t서버 — 토큰 절약 장점이 있으나 50_tools-mcp 단점 “한 프로세스 운명공동체, 권한 분리 불가”. 대안: progressive disclosure(BM25+deferred), core/rare 분리, 새 도구는 배열 끝 append로 캐시 breakpoint 보존(50_tools-mcp 차용안1).- 11 자유본문: 내 CLAUDE.md “OMC 외엔 자유본문”(20_context-assembly 차용안1) → 압축 때 통째로 증발. Codex만 고유 마커로
context_manager핀포인트 교체 가능. 대안: 가변 섹션을<project_env>…</project_env>XML 마커로. 단 CC 플러그인은 자동 교체 권한이 없어 가독성·편집안정성 향상까지가 현실적 한계.- 12 거대 카탈로그: OMC가 “zero learning curve” 표방하나 19에이전트·40스킬·11훅으로 라우팅 복잡, 별도 mode-selection-guide 필요(3_학습인사이트 원칙8). 파괴적 변경도 빈발(swarm→team 개명). 대안: gajae(4스킬+4에이전트)·fable-ish(3훅+1스킬)처럼 좁게 시작, 검증된 패턴만 승격, 카탈로그 예산 강등을 빌드타임 린트로.
[!warning] E. 신뢰 경계의 구멍 — 근거
- 13 전역 느슨: 내 패턴 자기진단 — 프로젝트는 봉인했는데 전역은
approval_policy=on-failure,/tmp·/home·system32trusted,skipDangerousModePermissionPrompt:true,sudo npm허용(1_내패턴_개선백로그 항목3). “대량생성 워커가 글로벌 권한으로 도는 단일 사고점”. 대안: 전역에서system32·/home제거,skipDangerousMode=false, 역할별 프로파일(오케스트레이터=READ/untrusted, 워커=WRITE-only).- 14 평문 영속: 내 패턴 자기진단 “샤드에 비번 위치 평문은 위험”(100_state-memory 차용안2), “refresh_token·PAT·API key·memory flush 로그는 redaction 먼저”(1_내패턴_개선백로그). 대안: fable
redact()(4종 정규식sk-/ghp_/xox/key=value)을 모든 저장 sink 직전 강제(LLM 없이 결정론적, 비용 0) + OS credential store + rotation.
실제 예시
안티패턴 1 vs 대안 — 행동의 가역성으로 fail 방향 결정
// hooks/guard.cjs — 같은 코드베이스가 두 정책을 섞어 써야 한다
// (안티패턴) 모든 에러경로를 무조건 통과 — 보안 게이트에 쓰면 크래시=허용
function badGuard(input) {
try {
return checkPermission(input);
} catch (e) {
return { continue: true }; // 공격자가 가드를 죽이기만 하면 통과
}
}
// (대안) 행동의 가역성으로 fail 방향을 가른다
function goodGuard(input) {
try {
return checkPermission(input);
} catch (e) {
if (isReversible(input)) {
return { continue: true }; // 가역(Stop·재시도) → fail-open
}
return { decision: "block", // 비가역(커밋·권한·입고) → fail-closed
reason: "guard error on irreversible action (degraded mode)" };
}
}
안티패턴 6 — “돌렸음”이 아니라 “통과했음”을 봐야 한다
# verify/ledger.py — fable-ish가 이 함정을 피한 핵심 한 줄
# (안티패턴) 명령을 실행했다는 사실만으로 완료 처리
verified = len(verification_results) > 0 # success≠ran
# (대안) exit_code 기반 success가 하나라도 있어야 verified
verified = any(r["success"] is True for r in verification_results)
# + 변경파일을 실제로 덮었는지는 별도 열로 분리
coverage_ok = all(f in covered_files for f in changed_files)
안티패턴 7 — 루프 연장은 멈춤 장치와 짝으로만
# hooks/stop_gate.py — 차단과 가드는 항상 함께
MAX_STOP_BLOCKS = 2
def should_block_stop(state):
# 예외통과: 이것들은 반드시 통과시킨다 (가드 자체가 사고 원천이 되지 않게)
if state.reason in ("context-limit", "user-abort", "auth-failure"):
return False
if state.stop_hook_active and state.block_count >= MAX_STOP_BLOCKS:
return False # 핑퐁 방지: 두 번 막고 양보
return state.changed_files and not state.verified
안티패턴 14 — 모든 저장 sink 직전 결정론적 redaction
# state/persist.py — wiki·state·notepad·log 공통 진입점
import re
_PATTERNS = [r"sk-[A-Za-z0-9]{20,}", r"ghp_[A-Za-z0-9]{36}",
r"xox[baprs]-[A-Za-z0-9-]+", r"(?i)(password|token|api[_-]?key)\s*=\s*\S+"]
def redact(text: str) -> str: # LLM 없이 결정론적, 비용 0
for p in _PATTERNS:
text = re.sub(p, "[REDACTED]", text)
return text
def write_shard(path, content):
save(path, redact(content)) # 디스크에 적히기 전에 무조건 통과
요약 & 셀프체크
3줄 요약:
- 안티패턴은 기법이 아니라 맥락이다 — fail-open은 advisory엔 약, 보안 게이트엔 독.
- 5개 착각으로 묶인다: 강제력의 거짓말(A) · 우회 표면(B) · 루프 폭주(C) · 번들 비대(D) · 신뢰 경계 구멍(E).
- 공통 처방은 셋 — 가역성으로 fail 방향 결정 · 멈춤 장치를 짝으로 · 절대규칙은 산문 아닌 런타임으로.
스스로 답해보기:
- 내 훅이 크래시했을 때 그 행동은 통과되는가 막히는가? 그게 가역적 행동이라 그런가, 아니면 비가역인데 실수로 fail-open인가?
- 내 검증 게이트는 “명령을 돌렸음”을 보는가, “exit_code가 통과했고 변경파일을 실제로 덮었음”을 보는가?
- 내 자율 루프에는 MAX 카운터·재진입 가드·예외통과가 모두 달려 있는가? 셋 중 하나라도 빠지면 어떤 실패모드가 생기나?
[!info] 메타 교훈 — 안티패턴을 가르는 다섯 줄
- fail-open/fail-closed는 프레임워크 단위가 아니라 행동의 가역성 단위로 결정한다(gajae 읽기lenient/쓰기strict가 모범).
- 강제력은 위치가 결정한다 — “어디 들어가느냐”보다 “모델이 무시할 수 있느냐 / 런타임이 막느냐”(authority ladder).
- 모든 자율 루프는 멈춤 장치와 짝으로만 존재한다.
- 강력한 주입·자동화일수록 우회/오탐/자기강화 표면이 커진다(정규식 면제어·셀프승인·에코 재주입·exit2 자동복구는 편의가 곧 구멍).
- 솔직한 한계 명시가 1차 방어다 — fable-ish “우리는 보안 경계가 아니다”, OMC “advisory layer only”가 오용을 막는다.
[!tip] Codex 교차검증·메타검증 보존 본 카탈로그의 핵심 판정들은 Codex 교차검증으로 검증됨: ①”advisory hook엔 fail-open이 맞지만 권한/검증/정책 hook에선 실패=우회”(30_hooks) ②”
exit 2는 재시도가 아니라 멈춰야 하는 게이트 실패 신호”(1_내패턴_개선백로그) ③정규식 차단은 symlink·../·WSL경로·Bash redirection으로 우회 가능하므로realpath+메타문자 전면거부+다중겹 필요 ④redaction 대상은 refresh_token·PAT·API key·memory flush 로그를 우선 ⑤drift는 hard-stop보다 critic escalation/quarantine이 오탐에 안전 ⑥자유본문 마커화는 CC 플러그인에 자동 교체 권한이 없어 가독성·편집안정성 향상까지가 현실적 한계.
연결
하네스엔지니어링이란 · HOME · 1_하네스_설계원칙 · 2_설계_플레이북 · 30_hooks · 70_guardrails-sandbox · 80_verification · 90_self-improvement · 10_agent-loop · 20_context-assembly · 50_tools-mcp · 100_state-memory · 3_학습인사이트 · 1_내패턴_개선백로그