지식위키

하네스 안티패턴 — 코드에서 실제로 관찰된 위험·실패 설계 14선

하네스 안티패턴 — 코드에서 실제로 관찰된 위험·실패 설계 14선

한 줄 요약

같은 기법이 “어디에 쓰였느냐”에 따라 명약이 되기도 독약이 되기도 한다 — 안티패턴은 기법 자체가 아니라 “잘못된 곳에 쓴 기법”이다. 왜 배우나: 베스트 차용안만 외우면 그 그림자(같은 코드가 동시에 보여주는 하면 안 되는 것)를 놓쳐, 내 스택(Claude+Codex+OMC) 운영에서 그대로 지뢰를 밟는다.


그림

3_안티패턴-diagram.svg

아래는 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 훅에선 정답이지만, 보안·권한·검증 게이트에선 사고다. 그래서 우리는 “기법이 좋다/나쁘다”가 아니라 “이 행동이 되돌릴 수 있나?”를 먼저 묻는다.


핵심 정리

각 안티패턴은 (안티패턴 / 왜 위험 / 대안) 의 한 줄 요지로 압축한다. “어디서 관찰됐나”는 아래 근거 콜아웃으로 분산했다.

#안티패턴한 줄 대안
1fail-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 카운터+재진입 가드+예외통과를 짝으로
8score만 보고 goal drift 무시다중 AND 게이트 + drift 임계값 + 롤백
9exit 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, OMC run.cjs stale-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-loop MAX_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). 대안: ouroboros combined_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·system32 trusted, 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줄 요약:

  1. 안티패턴은 기법이 아니라 맥락이다 — fail-open은 advisory엔 약, 보안 게이트엔 독.
  2. 5개 착각으로 묶인다: 강제력의 거짓말(A) · 우회 표면(B) · 루프 폭주(C) · 번들 비대(D) · 신뢰 경계 구멍(E).
  3. 공통 처방은 셋 — 가역성으로 fail 방향 결정 · 멈춤 장치를 짝으로 · 절대규칙은 산문 아닌 런타임으로.

스스로 답해보기:

  • 내 훅이 크래시했을 때 그 행동은 통과되는가 막히는가? 그게 가역적 행동이라 그런가, 아니면 비가역인데 실수로 fail-open인가?
  • 내 검증 게이트는 “명령을 돌렸음”을 보는가, “exit_code가 통과했고 변경파일을 실제로 덮었음”을 보는가?
  • 내 자율 루프에는 MAX 카운터·재진입 가드·예외통과가 모두 달려 있는가? 셋 중 하나라도 빠지면 어떤 실패모드가 생기나?

[!info] 메타 교훈 — 안티패턴을 가르는 다섯 줄

  1. fail-open/fail-closed는 프레임워크 단위가 아니라 행동의 가역성 단위로 결정한다(gajae 읽기lenient/쓰기strict가 모범).
  2. 강제력은 위치가 결정한다 — “어디 들어가느냐”보다 “모델이 무시할 수 있느냐 / 런타임이 막느냐”(authority ladder).
  3. 모든 자율 루프는 멈춤 장치와 짝으로만 존재한다.
  4. 강력한 주입·자동화일수록 우회/오탐/자기강화 표면이 커진다(정규식 면제어·셀프승인·에코 재주입·exit2 자동복구는 편의가 곧 구멍).
  5. 솔직한 한계 명시가 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_내패턴_개선백로그