oh-my-customcode 0.143.0 → 0.145.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/templates/.claude/rules/MUST-completion-verification.md +3 -1
- package/templates/.claude/rules/MUST-intent-transparency.md +18 -8
- package/templates/.claude/rules/SHOULD-memory-integration.md +42 -0
- package/templates/.claude/skills/systematic-debugging/SKILL.md +51 -0
- package/templates/.claude/skills/systematic-debugging/phases/amplification-detection.md +143 -0
- package/templates/.claude/skills/systematic-debugging/phases/fault-injection.md +163 -0
- package/templates/.claude/skills/systematic-debugging/phases/retry-cache-timeout-audit.md +131 -0
- package/templates/.claude/skills/systematic-debugging/phases/timeline-correlation.md +114 -0
- package/templates/CLAUDE.md +8 -1
- package/templates/guides/agentmemory-migration/phase-1-coexist.md +261 -0
- package/templates/guides/claude-code/15-version-compatibility.md +158 -0
- package/templates/guides/external-tools/ecc-absorption-decisions.md +232 -0
- package/templates/manifest.json +1 -1
package/dist/cli/index.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -16,6 +16,7 @@ Before declaring any task `[Done]`, verify completion against task-type-specific
|
|
|
16
16
|
| Git Operations | Operation succeeded (check exit code), working tree clean |
|
|
17
17
|
| Code Review | All findings addressed or explicitly deferred with justification |
|
|
18
18
|
| Agent/Skill Creation | Frontmatter valid, referenced skills exist, routing updated |
|
|
19
|
+
| UI/Frontend | Browser render verified (dev server running + page loaded), no console errors, visual output matches intent; **CSS/style changes**: capture before/after visual diff or screenshot; type-check passing alone is NOT sufficient |
|
|
19
20
|
|
|
20
21
|
## Optional: Quantitative Evidence (advisory, added v0.114.0, #1034)
|
|
21
22
|
|
|
@@ -70,7 +71,7 @@ Subagents often report failures as "pre-existing", "baseline", or "unchanged". T
|
|
|
70
71
|
Never accept "pre-existing" without direct base-branch evidence. A false "pre-existing" claim can mask a regression introduced by the current change.
|
|
71
72
|
-->
|
|
72
73
|
|
|
73
|
-
## Common False Completion Patterns —
|
|
74
|
+
## Common False Completion Patterns — 8 anti-patterns including "Command executed" without exit code check, "Waiting for manual publish" when CI auto-publishes, "UI changes done" without browser render. See full table via Read tool.
|
|
74
75
|
|
|
75
76
|
<!-- DETAIL: Common False Completion Patterns
|
|
76
77
|
|
|
@@ -83,6 +84,7 @@ Never accept "pre-existing" without direct base-branch evidence. A false "pre-ex
|
|
|
83
84
|
| "Tests pass" | Only ran subset | Run full test suite |
|
|
84
85
|
| "Waiting for manual publish" | External CI/CD auto-publishes on merge | Check `.github/workflows/` BEFORE assuming manual step |
|
|
85
86
|
| "Subagent said pre-existing" | Claim not verified against base branch | Run test on base branch, compare directly |
|
|
87
|
+
| "UI changes done" / "CSS updated" | type-check passes but browser render not verified; visual output unknown | Start dev server, open browser, confirm visual output; capture screenshot or describe what was seen |
|
|
86
88
|
-->
|
|
87
89
|
|
|
88
90
|
## Completion Contract Format — [Contract] + [Done] with criterion/evidence pairs. See template via Read tool.
|
|
@@ -39,7 +39,7 @@ Display reasoning when routing to agents. Users must always know which agent was
|
|
|
39
39
|
|
|
40
40
|
Users can specify agent directly with `@{agent-name} {command}`. Override bypasses detection.
|
|
41
41
|
|
|
42
|
-
## User Directive Persistence — Named tool/skill/workflow preferences persist entire session. Anti-pattern: treating autonomous mode as clean slate. See full spec via Read tool.
|
|
42
|
+
## User Directive Persistence — Named tool/skill/workflow preferences persist entire session. Anti-pattern: treating autonomous mode as clean slate or re-asking already-rejected questions. See full spec via Read tool.
|
|
43
43
|
|
|
44
44
|
<!-- DETAIL: User Directive Persistence
|
|
45
45
|
When a user explicitly names a tool, skill, or workflow (e.g., "use /pipeline auto-dev", "always run tests with bun test"), this preference persists for the entire session — including after autonomous mode transitions.
|
|
@@ -53,20 +53,30 @@ When a user explicitly names a tool, skill, or workflow (e.g., "use /pipeline au
|
|
|
53
53
|
| "from now on" | Entire session + memory save candidate |
|
|
54
54
|
| "for this task" | Current task only |
|
|
55
55
|
| Named slash command | Subsequent similar invocations |
|
|
56
|
+
| AskUserQuestion rejected / directive overridden | That question/approach must NOT recur this session |
|
|
56
57
|
|
|
57
|
-
### Cycle Start Self-Check
|
|
58
|
+
### Cycle Start Self-Check (MANDATORY)
|
|
58
59
|
|
|
59
|
-
At the start of every
|
|
60
|
-
1. Review recent user messages in the conversation
|
|
61
|
-
2. Identify any named tool/skill/workflow directives
|
|
62
|
-
3. Apply those directives unless explicitly rescinded
|
|
63
|
-
4. If unsure whether a directive applies, default to the stated preference
|
|
60
|
+
At the start of every new task, issue, or autonomous sub-loop, answer these three questions before proceeding:
|
|
64
61
|
|
|
65
|
-
**
|
|
62
|
+
1. **Preferred tool/skill/workflow?** — Did the user explicitly name a tool or workflow earlier in this session? If YES, use it. Do NOT fall back to the default without re-confirmation.
|
|
63
|
+
2. **Rejected interaction patterns?** — Did the user reject a question format (e.g., AskUserQuestion) or specific approach? If YES, that pattern must NOT recur in this session.
|
|
64
|
+
3. **Override rescinded?** — Has the user explicitly cancelled a prior directive since stating it? If NO, the directive is still active.
|
|
65
|
+
|
|
66
|
+
| Check | Fail Condition | Required Action |
|
|
67
|
+
|-------|---------------|----------------|
|
|
68
|
+
| Preferred tool/skill | About to use a different tool/skill | Switch to user-specified one |
|
|
69
|
+
| Rejected AskUserQuestion | About to AskUserQuestion again on same topic | Answer with best judgment or inline question (free text) |
|
|
70
|
+
| Rejected approach | About to repeat the same approach | Choose alternative approach |
|
|
71
|
+
|
|
72
|
+
**Anti-pattern 1**: Treating autonomous mode as a clean slate that discards earlier user preferences. Autonomous mode means "continue without per-step confirmation" — NOT "reset user directives".
|
|
73
|
+
|
|
74
|
+
**Anti-pattern 2**: User rejects an AskUserQuestion (or the interaction style) → agent falls back to free-text phrasing of the same question in the next turn. If the user has indicated they do not want a specific interaction pattern, do NOT re-ask via different formatting — make a judgment call and proceed.
|
|
66
75
|
|
|
67
76
|
### Cross-reference
|
|
68
77
|
|
|
69
78
|
- Related memory: session v0.87.2~v0.88.0 (issue #869) — `/pipeline auto-dev` preference was lost after autonomous mode transition
|
|
79
|
+
- Related issue: #1188 item #4 — AskUserQuestion rejected, agent re-asked via free text in next turn (2026-05-19)
|
|
70
80
|
-->
|
|
71
81
|
|
|
72
82
|
## Agent Triggers
|
|
@@ -381,3 +381,45 @@ MCP tools (claude-mem, episodic-memory) are **orchestrator-scoped** and not inhe
|
|
|
381
381
|
- MCP saves are **non-blocking**: memory failure MUST NOT prevent session from ending
|
|
382
382
|
- If claude-mem unavailable: skip, log warning
|
|
383
383
|
- episodic-memory: no action needed (auto-indexed after session)
|
|
384
|
+
|
|
385
|
+
## Dual-Backend Advisory (AgentMemory + claude-mem)
|
|
386
|
+
|
|
387
|
+
#1169 Phase 1 (COEXIST) 단계에서 두 memory backend 동시 활성 가능:
|
|
388
|
+
|
|
389
|
+
| 상황 | 권장 |
|
|
390
|
+
|------|------|
|
|
391
|
+
| claude-mem 단독 | 기본값 — 현 운영 유지 |
|
|
392
|
+
| AgentMemory 단독 | Phase 2 (SWITCH) 이후 진행 |
|
|
393
|
+
| 둘 다 활성 (COEXIST) | Phase 1 한정 — `memory-aggregator`가 결과 병합 |
|
|
394
|
+
|
|
395
|
+
### 충돌 감지
|
|
396
|
+
|
|
397
|
+
`.mcp.json`에 두 서버(`claude-mem`, `agentmemory`) 동시 등록 시 첫 호출 시점에 advisory 출력 권장:
|
|
398
|
+
|
|
399
|
+
```
|
|
400
|
+
[Advisory] Dual memory backend detected (Phase 1 COEXIST)
|
|
401
|
+
- claude-mem: active (Chroma)
|
|
402
|
+
- agentmemory: active (SQLite)
|
|
403
|
+
Phase 2 SWITCH 진입 전까지 두 backend 유지
|
|
404
|
+
가이드: guides/agentmemory-migration/phase-1-coexist.md
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
이 advisory는 경고가 아닙니다. Phase 1 COEXIST에서는 정상 상태입니다.
|
|
408
|
+
|
|
409
|
+
### Session-End Self-Check (COEXIST 확장)
|
|
410
|
+
|
|
411
|
+
Phase 1 COEXIST 기간 중 세션 종료 시:
|
|
412
|
+
|
|
413
|
+
1. sys-memory-keeper가 MEMORY.md 갱신? → YES: 계속
|
|
414
|
+
2. claude-mem 저장 시도? → YES (기존 항목)
|
|
415
|
+
3. AgentMemory 저장 시도? → YES (COEXIST 추가)
|
|
416
|
+
세 단계 모두 완료 후 사용자에게 확인. 둘 중 하나 실패해도 비차단.
|
|
417
|
+
|
|
418
|
+
### Phase 2 진입 전 필수 조건
|
|
419
|
+
|
|
420
|
+
- 1주 measure 결과 (`scripts/measure-claude-mem-usage.sh`) GO 판정
|
|
421
|
+
- 자산 처리표 사용자 검토 완료 (12 plugin skill 처리 방향 결정)
|
|
422
|
+
- 30분 롤백 절차 검증 (Chroma 백업 + 복원 테스트)
|
|
423
|
+
|
|
424
|
+
Refs: #1169 본문 조치 3 (택1 강제), 조치 4 (롤백 절차),
|
|
425
|
+
`guides/agentmemory-migration/phase-1-coexist.md`.
|
|
@@ -27,6 +27,7 @@ user-invocable: false
|
|
|
27
27
|
4. **한 번에 하나의 가설만 검증한다.**
|
|
28
28
|
5. **수정 시 "while I'm here" 리팩터링을 금지한다.**
|
|
29
29
|
6. **수정 시도가 3번 실패하면 추가 패치 전에 구조적 문제를 의심한다.**
|
|
30
|
+
7. **retry/cache/timeout을 변경하기 전에 false-fix 가능성을 점검한다.** 에러율 감소가 원인 해소인지 증상 억제인지 구분하지 않은 수정은 유효하지 않다.
|
|
30
31
|
|
|
31
32
|
이 과정을 어기는 것은 디버깅 실패로 본다.
|
|
32
33
|
|
|
@@ -64,6 +65,38 @@ user-invocable: false
|
|
|
64
65
|
|
|
65
66
|
반드시 아래 순서로 진행한다.
|
|
66
67
|
|
|
68
|
+
### Phase 0. Blocker Triage (Pre-Debug Gate)
|
|
69
|
+
|
|
70
|
+
**Trigger**: When any external dependency, tool, or resource appears unavailable.
|
|
71
|
+
|
|
72
|
+
Before declaring a task blocked or unsolvable, exhaust this checklist:
|
|
73
|
+
|
|
74
|
+
| # | Workaround Path | Check |
|
|
75
|
+
|---|----------------|-------|
|
|
76
|
+
| 1 | Environment bypass | Can the dependency be bypassed in the current environment? |
|
|
77
|
+
| 2 | Alternative tool | Is there an alternative tool, library, or approach? |
|
|
78
|
+
| 3 | Partial solution | Can the task be partially completed without the dependency? |
|
|
79
|
+
| 4 | Install/configure | Can the dependency be installed or configured now? |
|
|
80
|
+
| 5 | Existing credentials | Are related tokens, auth files, or configs already present? |
|
|
81
|
+
|
|
82
|
+
**Rules**:
|
|
83
|
+
- Minimum 3 workaround paths MUST be explored before declaring "blocked"
|
|
84
|
+
- Each explored path must be documented with outcome
|
|
85
|
+
- "Session unsolvable" declarations MUST include the list of attempted workaround paths
|
|
86
|
+
- If a workaround is found, proceed with debugging using that workaround
|
|
87
|
+
|
|
88
|
+
**Output format**:
|
|
89
|
+
```
|
|
90
|
+
[Blocker Triage]
|
|
91
|
+
├── Dependency: {what is unavailable}
|
|
92
|
+
├── Path 1: {attempted} → {outcome}
|
|
93
|
+
├── Path 2: {attempted} → {outcome}
|
|
94
|
+
├── Path 3: {attempted} → {outcome}
|
|
95
|
+
└── Verdict: {proceed with workaround N | genuinely blocked — reason}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
If verdict is "genuinely blocked", escalate to user with full triage report. Do NOT silently abandon the task.
|
|
99
|
+
|
|
67
100
|
### Phase 1. Define The Problem
|
|
68
101
|
|
|
69
102
|
먼저 문제를 축약한다.
|
|
@@ -286,3 +319,21 @@ This skill includes reference documents for specific debugging techniques:
|
|
|
286
319
|
- `condition-based-waiting.md` — Replacing arbitrary delays with condition-based polling
|
|
287
320
|
- `find-polluter.sh` — Bisection script for finding test pollution sources
|
|
288
321
|
- `condition-based-waiting-example.ts` — Complete implementation of condition-based waiting utilities
|
|
322
|
+
|
|
323
|
+
## Extended Phases
|
|
324
|
+
|
|
325
|
+
장애 분석 및 운영 디버깅을 위한 확장 절차. 기본 Phase 1-7과 함께 사용한다.
|
|
326
|
+
|
|
327
|
+
- `phases/timeline-correlation.md` — 배포/설정 변경 타임라인과 장애 시점 상관관계 추적
|
|
328
|
+
- `phases/retry-cache-timeout-audit.md` — retry/cache/timeout false-fix 안티패턴 체크리스트 (Hard Gate #7 구현)
|
|
329
|
+
- `phases/amplification-detection.md` — retry storm 및 error cascading 시그널 탐지
|
|
330
|
+
- `phases/fault-injection.md` — 가설 검증을 위한 의도적 장애 주입 절차
|
|
331
|
+
|
|
332
|
+
### 사용 가이드
|
|
333
|
+
|
|
334
|
+
| 상황 | 참조 |
|
|
335
|
+
|------|------|
|
|
336
|
+
| "언제부터 깨졌는가?" | `phases/timeline-correlation.md` |
|
|
337
|
+
| "retry/timeout 늘리면 되지 않나?" | `phases/retry-cache-timeout-audit.md` |
|
|
338
|
+
| 에러가 여러 서비스로 퍼졌다 | `phases/amplification-detection.md` |
|
|
339
|
+
| 가설은 있지만 재현이 안 된다 | `phases/fault-injection.md` |
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Amplification Detection
|
|
2
|
+
|
|
3
|
+
retry storm 및 에러 cascading 시그널을 탐지하는 절차다.
|
|
4
|
+
|
|
5
|
+
## 목적
|
|
6
|
+
|
|
7
|
+
단일 장애가 retry/timeout 연쇄로 증폭되면 원인과 증상이 분리된다.
|
|
8
|
+
원인은 한 곳이지만 에러는 시스템 전체에서 나타난다.
|
|
9
|
+
증폭을 먼저 탐지하지 않으면 증상을 원인으로 착각하고 엉뚱한 곳을 고친다.
|
|
10
|
+
|
|
11
|
+
## 증폭의 3가지 유형
|
|
12
|
+
|
|
13
|
+
### 1. Retry Storm
|
|
14
|
+
|
|
15
|
+
단일 실패가 재시도로 인해 N배 트래픽으로 증폭되는 패턴.
|
|
16
|
+
|
|
17
|
+
시그널:
|
|
18
|
+
- 업스트림 에러율이 급상승하는 동시에 요청 수도 급상승
|
|
19
|
+
- 동일 에러가 짧은 시간(1~5초) 안에 burst 발생
|
|
20
|
+
- 업스트림 서비스 로그에 동일 request_id가 N회 기록됨
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# 동일 에러의 burst 패턴 확인
|
|
24
|
+
grep "ERROR" application.log \
|
|
25
|
+
| awk '{print $1, $2}' \
|
|
26
|
+
| uniq -c \
|
|
27
|
+
| sort -rn \
|
|
28
|
+
| head -20
|
|
29
|
+
|
|
30
|
+
# 초당 에러 수 추이
|
|
31
|
+
grep "ERROR" application.log \
|
|
32
|
+
| awk '{print $1"T"substr($2,1,5)}' \
|
|
33
|
+
| sort | uniq -c
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. Error Cascading
|
|
37
|
+
|
|
38
|
+
한 서비스의 실패가 의존 서비스로 전파되는 패턴.
|
|
39
|
+
|
|
40
|
+
시그널:
|
|
41
|
+
- 여러 서비스에서 에러가 동시에 또는 순차적으로 발생
|
|
42
|
+
- 에러 발생 순서가 dependency graph 방향과 일치
|
|
43
|
+
- 하나의 서비스가 회복되자 연쇄적으로 다른 서비스도 회복
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
Dependency graph 예:
|
|
47
|
+
Service A → Service B → Service C
|
|
48
|
+
|
|
49
|
+
Cascading 시그널:
|
|
50
|
+
14:47:00 Service C ERROR (DB timeout)
|
|
51
|
+
14:47:03 Service B ERROR (C call failed)
|
|
52
|
+
14:47:05 Service A ERROR (B call failed)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 3. Connection Pool Exhaustion
|
|
56
|
+
|
|
57
|
+
retry/cascading으로 커넥션이 소진되어 새 요청이 큐에 쌓이는 패턴.
|
|
58
|
+
|
|
59
|
+
시그널:
|
|
60
|
+
- 응답 시간이 급격히 증가 (정상 50ms → 10s)
|
|
61
|
+
- `connection pool exhausted` 또는 `too many connections` 에러
|
|
62
|
+
- 이전에는 없던 timeout이 급증
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
# PostgreSQL 커넥션 수 확인
|
|
66
|
+
psql -c "SELECT count(*), state FROM pg_stat_activity GROUP BY state;"
|
|
67
|
+
|
|
68
|
+
# MySQL 커넥션 상태
|
|
69
|
+
mysql -e "SHOW STATUS LIKE 'Threads_connected';"
|
|
70
|
+
|
|
71
|
+
# Redis 커넥션
|
|
72
|
+
redis-cli INFO clients | grep connected_clients
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 탐지 절차
|
|
76
|
+
|
|
77
|
+
### Step 1. Rate 변화 추적
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
# 에러 발생 전후 1분 단위 에러 수 추이
|
|
81
|
+
grep "ERROR" app.log \
|
|
82
|
+
| awk '{print substr($2,1,5)}' \
|
|
83
|
+
| sort | uniq -c
|
|
84
|
+
|
|
85
|
+
# 정상: 에러 수가 점진적으로 증가
|
|
86
|
+
# 증폭: 에러 수가 특정 시점에서 급격히 증가(3배 이상)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Step 2. Dependency Graph 분석
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# 서비스 A의 에러 시작 시각
|
|
93
|
+
grep "ERROR" service-a.log | head -1
|
|
94
|
+
|
|
95
|
+
# 서비스 B의 에러 시작 시각
|
|
96
|
+
grep "ERROR" service-b.log | head -1
|
|
97
|
+
|
|
98
|
+
# 시각 차이가 dependency latency와 일치하면 cascading
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Step 3. 증폭 여부 판정
|
|
102
|
+
|
|
103
|
+
| 관측 | 판정 |
|
|
104
|
+
|------|------|
|
|
105
|
+
| 요청 수 증가 없이 에러율만 증가 | 단순 장애 (증폭 아님) |
|
|
106
|
+
| 요청 수와 에러율이 동시 급증 | Retry storm 의심 |
|
|
107
|
+
| 에러가 여러 서비스로 순차 전파 | Cascading 의심 |
|
|
108
|
+
| 응답 시간이 커넥션 한계 시점에서 급증 | Pool exhaustion 의심 |
|
|
109
|
+
|
|
110
|
+
## 증폭 확인 후 대응
|
|
111
|
+
|
|
112
|
+
증폭이 확인되면 원인과 증폭을 분리해서 처리한다.
|
|
113
|
+
|
|
114
|
+
1. **증폭 차단 먼저** (circuit breaker, retry 일시 비활성화, rate limit)
|
|
115
|
+
2. **원인 격리** (증폭이 차단된 상태에서 Phase 4 진행)
|
|
116
|
+
3. **원인 수정 후 증폭 방어 보강** (backoff 설정, circuit breaker 임계값)
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
# Circuit breaker 예시 (빠른 장애 격리)
|
|
120
|
+
from circuitbreaker import circuit
|
|
121
|
+
|
|
122
|
+
@circuit(failure_threshold=5, recovery_timeout=30)
|
|
123
|
+
def call_external_service():
|
|
124
|
+
...
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 출력 형식
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
[Amplification Detection]
|
|
131
|
+
├── Pattern: retry storm | cascading | pool exhaustion | none
|
|
132
|
+
├── Evidence:
|
|
133
|
+
│ ├── Rate change: <normal_rate> → <burst_rate> at <timestamp>
|
|
134
|
+
│ └── Dependency: <service_a> → <service_b> at +<Ns>
|
|
135
|
+
├── Root cause candidate: <upstream service or resource>
|
|
136
|
+
└── Immediate action: <circuit break | retry off | pool increase>
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 연관
|
|
140
|
+
|
|
141
|
+
- `retry-cache-timeout-audit.md` — retry storm의 구조적 원인 감사
|
|
142
|
+
- `timeline-correlation.md` — 증폭 시작 시각과 배포/변경 상관관계 확인
|
|
143
|
+
- Phase 4 (Isolate Root Cause) — 증폭 차단 후 원인 격리
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Fault Injection
|
|
2
|
+
|
|
3
|
+
가설 검증을 위해 의도적으로 장애를 주입하는 절차다.
|
|
4
|
+
|
|
5
|
+
## 목적
|
|
6
|
+
|
|
7
|
+
가설이 맞는지 확인하는 가장 확실한 방법은 직접 그 조건을 만드는 것이다.
|
|
8
|
+
재현이 어려운 간헐 버그, 네트워크 장애, 의존 서비스 실패를 시뮬레이션해 가설을 검증한다.
|
|
9
|
+
|
|
10
|
+
## 전제 조건
|
|
11
|
+
|
|
12
|
+
다음 조건을 모두 만족해야 fault injection을 시작한다.
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
[ ] 1. Phase 4에서 단일 원인 가설이 수립되어 있다
|
|
16
|
+
[ ] 2. Rollback 수단이 준비되어 있다 (코드, 설정, 인프라)
|
|
17
|
+
[ ] 3. 주입 대상이 격리 가능한 환경이다 (dev/staging 우선)
|
|
18
|
+
[ ] 4. 주입 범위와 기간이 명시되어 있다
|
|
19
|
+
[ ] 5. 관측 수단이 준비되어 있다 (로그, 메트릭, 알림)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**prod 직접 주입은 금지한다.** 예외: prod에서만 재현되고 안전하게 격리할 수 있는 경우에만, 명시적 승인 후 진행한다.
|
|
23
|
+
|
|
24
|
+
## 절차
|
|
25
|
+
|
|
26
|
+
### Step 1. 가설 명시
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
Hypothesis: <root cause> because <evidence>
|
|
30
|
+
Prediction: If I inject <fault>, then I expect to see <symptom>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
예시:
|
|
34
|
+
```
|
|
35
|
+
Hypothesis: DB connection pool exhaustion causes 503 because pool_size=5 under load
|
|
36
|
+
Prediction: If I set pool_size=1 and send 10 concurrent requests,
|
|
37
|
+
I expect to see connection timeout errors matching production pattern
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Step 2. 최소 주입 설계
|
|
41
|
+
|
|
42
|
+
가설을 검증하는 데 필요한 최소한의 주입만 설계한다.
|
|
43
|
+
|
|
44
|
+
| 유형 | 도구/방법 |
|
|
45
|
+
|------|---------|
|
|
46
|
+
| 네트워크 지연/단절 | `tc netem`, Toxiproxy, Chaos Monkey |
|
|
47
|
+
| 서비스 응답 오류 | Mock 서버, WireMock, 환경변수 override |
|
|
48
|
+
| 리소스 고갈 | `ulimit`, 설정 변경 (pool_size, max_connections) |
|
|
49
|
+
| 디스크 오류 | `dd if=/dev/full`, 디스크 용량 제한 |
|
|
50
|
+
| CPU/Memory 부하 | `stress-ng`, `yes > /dev/null` |
|
|
51
|
+
| 의존 서비스 다운 | 프로세스 종료, 포트 차단 |
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 네트워크 지연 주입 예시 (Linux tc)
|
|
55
|
+
sudo tc qdisc add dev eth0 root netem delay 500ms 100ms
|
|
56
|
+
|
|
57
|
+
# 제거
|
|
58
|
+
sudo tc qdisc del dev eth0 root
|
|
59
|
+
|
|
60
|
+
# Toxiproxy (더 안전한 방법)
|
|
61
|
+
toxiproxy-cli toxic add -t latency -a latency=500 proxy_name
|
|
62
|
+
toxiproxy-cli toxic remove proxy_name
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Step 3. 주입 실행 및 관측
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 1. 기준 메트릭 기록 (주입 전)
|
|
69
|
+
# 2. 주입
|
|
70
|
+
# 3. 동일 조건으로 트래픽/요청 실행
|
|
71
|
+
# 4. 관측 (로그, 메트릭, 에러 패턴)
|
|
72
|
+
# 5. 즉시 rollback
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
관측 체크리스트:
|
|
76
|
+
```
|
|
77
|
+
[ ] 예측한 증상이 나타났는가?
|
|
78
|
+
[ ] 증상의 형태가 실제 장애와 동일한가?
|
|
79
|
+
[ ] 예측하지 못한 부작용이 있는가?
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Step 4. 검증 및 결론
|
|
83
|
+
|
|
84
|
+
| 관측 결과 | 결론 |
|
|
85
|
+
|---------|------|
|
|
86
|
+
| 예측한 증상이 정확히 재현됨 | 가설 확인 → Phase 6으로 진행 |
|
|
87
|
+
| 증상은 나타나지만 형태가 다름 | 가설 부분 확인 → 가설 수정 후 재시도 |
|
|
88
|
+
| 증상이 전혀 나타나지 않음 | 가설 기각 → Phase 4로 돌아가 새 가설 수립 |
|
|
89
|
+
| 예측 외 부작용 발생 | 즉시 rollback → 영향 평가 후 재설계 |
|
|
90
|
+
|
|
91
|
+
### Step 5. Rollback 확인
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# 주입 해제 확인
|
|
95
|
+
# 설정 원복 확인
|
|
96
|
+
# 메트릭 정상 복귀 확인 (주입 전과 동일한 기준값)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
rollback 후 5분 이상 메트릭을 관찰한다. 즉시 정상화되지 않는 경우 지속 모니터링.
|
|
100
|
+
|
|
101
|
+
## 빠른 참조 — 주입 유형별
|
|
102
|
+
|
|
103
|
+
### DB 커넥션 풀 고갈 검증
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
# 설정 임시 변경
|
|
107
|
+
DATABASE_POOL_SIZE = 1 # 실제 설정값보다 극단적으로 낮게
|
|
108
|
+
|
|
109
|
+
# 동시 요청 실행
|
|
110
|
+
import concurrent.futures
|
|
111
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=20) as ex:
|
|
112
|
+
futures = [ex.submit(make_request) for _ in range(20)]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 외부 API 응답 지연 검증
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# WireMock 또는 httpretty로 mock
|
|
119
|
+
import responses
|
|
120
|
+
|
|
121
|
+
@responses.activate
|
|
122
|
+
def test_timeout_handling():
|
|
123
|
+
responses.add(responses.GET, 'http://api.example.com',
|
|
124
|
+
body=Exception('Connection timeout'))
|
|
125
|
+
result = call_external_api()
|
|
126
|
+
assert result == expected_fallback
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 디스크 고갈 검증
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
# 임시 파일로 공간 채우기 (주의: 복구 가능)
|
|
133
|
+
fallocate -l 10G /tmp/test_fill.img
|
|
134
|
+
# 검증 후
|
|
135
|
+
rm /tmp/test_fill.img
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 출력 형식
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
[Fault Injection]
|
|
142
|
+
├── Hypothesis: <원인 가설>
|
|
143
|
+
├── Prediction: <예상 증상>
|
|
144
|
+
├── Injection: <주입 유형 및 방법>
|
|
145
|
+
├── Environment: dev | staging | prod (승인 필요)
|
|
146
|
+
├── Observation: <실제 관측 결과>
|
|
147
|
+
├── Match: yes | partial | no
|
|
148
|
+
├── Rollback: completed at <timestamp>
|
|
149
|
+
└── Conclusion: confirmed | revised | rejected
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## 주의
|
|
153
|
+
|
|
154
|
+
- 주입은 항상 가설 확인 후 즉시 rollback한다. "조금 더 두어보자"는 금지다.
|
|
155
|
+
- 의존 서비스가 있는 환경에서 주입 시 파급 범위를 미리 파악한다.
|
|
156
|
+
- prod 주입은 incident 대응 중 재현이 불가피할 때만, 팀 승인 후 진행한다.
|
|
157
|
+
- 주입 기록(시각, 방법, rollback 시각)을 incident 타임라인에 남긴다.
|
|
158
|
+
|
|
159
|
+
## 연관
|
|
160
|
+
|
|
161
|
+
- Phase 4 (Isolate Root Cause) — 가설이 수립된 이후에만 fault injection 시작
|
|
162
|
+
- `amplification-detection.md` — retry storm 가설 검증 시 주입 방법
|
|
163
|
+
- `timeline-correlation.md` — 주입 이벤트를 타임라인에 기록
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Retry / Cache / Timeout Audit
|
|
2
|
+
|
|
3
|
+
retry, cache, timeout 변경이 실제 원인을 숨기는 false-fix 안티패턴을 식별하는 체크리스트다.
|
|
4
|
+
|
|
5
|
+
## 배경
|
|
6
|
+
|
|
7
|
+
retry/cache/timeout은 빠른 증상 억제 수단처럼 보이지만, 대부분의 경우 근본 원인을 가리는 밴드에이드다.
|
|
8
|
+
|
|
9
|
+
- **retry** → 실패율을 낮추는 것처럼 보이지만 실패 원인은 그대로다
|
|
10
|
+
- **cache** → 잘못된 응답을 잠시 숨기지만 TTL 이후 다시 드러난다
|
|
11
|
+
- **timeout** → 늘리면 에러가 줄지만 지연의 원인은 해소되지 않는다
|
|
12
|
+
|
|
13
|
+
이 세 가지를 바꾸기 전에 아래 체크리스트를 통과해야 한다.
|
|
14
|
+
|
|
15
|
+
## 안티패턴 목록
|
|
16
|
+
|
|
17
|
+
### 1. try-catch swallow (예외 삼키기)
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
# 안티패턴
|
|
21
|
+
try:
|
|
22
|
+
result = call_external_api()
|
|
23
|
+
except Exception:
|
|
24
|
+
return default_value # 원인 불명, 로그도 없음
|
|
25
|
+
|
|
26
|
+
# 올바른 방향
|
|
27
|
+
try:
|
|
28
|
+
result = call_external_api()
|
|
29
|
+
except SpecificError as e:
|
|
30
|
+
logger.error("API call failed: %s", e, exc_info=True)
|
|
31
|
+
raise # 또는 명시적 fallback with 로깅
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
체크: 예외를 삼키는 지점에서 실제 에러율이 측정되고 있는가?
|
|
35
|
+
|
|
36
|
+
### 2. Retry storm (재시도 폭풍)
|
|
37
|
+
|
|
38
|
+
```yaml
|
|
39
|
+
# 안티패턴 — 모든 요청이 3배로 증폭
|
|
40
|
+
retries: 3
|
|
41
|
+
retry_on: [500, 503]
|
|
42
|
+
backoff: 0 # 지수 백오프 없음
|
|
43
|
+
|
|
44
|
+
# 올바른 방향
|
|
45
|
+
retries: 3
|
|
46
|
+
retry_on: [429, 503] # 재시도 가능한 코드만
|
|
47
|
+
backoff_multiplier: 2
|
|
48
|
+
max_backoff: 30s
|
|
49
|
+
jitter: true
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
체크: 업스트림 서비스가 이미 과부하 상태에서 retry가 부하를 더 키우고 있지 않은가?
|
|
53
|
+
|
|
54
|
+
### 3. Cache mask (캐시로 가리기)
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
# 안티패턴 — 버그가 있는 응답이 캐시에 고정됨
|
|
58
|
+
@cache(ttl=3600)
|
|
59
|
+
def get_user_permissions(user_id):
|
|
60
|
+
return fetch_permissions(user_id) # 버그 있음
|
|
61
|
+
|
|
62
|
+
# 체크 포인트
|
|
63
|
+
# - 잘못된 데이터가 캐시에 들어갔을 때 무효화 수단이 있는가?
|
|
64
|
+
# - 캐시 히트율이 올라가면서 버그 재현율이 낮아진 것처럼 보이지 않는가?
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
체크: 에러율 감소가 캐시 히트율 증가와 시간적으로 일치하는가?
|
|
68
|
+
|
|
69
|
+
### 4. Timeout shift (타임아웃 연장)
|
|
70
|
+
|
|
71
|
+
```yaml
|
|
72
|
+
# 안티패턴 — 근본 원인 없이 타임아웃만 늘림
|
|
73
|
+
timeout: 30s # 기존 10s → 30s로 변경
|
|
74
|
+
|
|
75
|
+
# 올바른 방향: 먼저 확인할 것
|
|
76
|
+
# - 왜 10s 안에 응답하지 못하는가?
|
|
77
|
+
# - DB 쿼리 slow query log 확인
|
|
78
|
+
# - 외부 API 응답 시간 추이
|
|
79
|
+
# - N+1 쿼리 여부
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
체크: 타임아웃 연장 후 p99 응답 시간이 어떻게 변했는가?
|
|
83
|
+
|
|
84
|
+
## 호출 경로 감사 체크리스트
|
|
85
|
+
|
|
86
|
+
수정 전에 실제 호출 경로에서 아래 항목을 모두 확인한다.
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
[ ] 1. 현재 retry 설정 위치를 모두 찾았다
|
|
90
|
+
- HTTP client config
|
|
91
|
+
- Message queue consumer
|
|
92
|
+
- Background job 설정
|
|
93
|
+
- SDK 내부 기본값 (라이브러리 문서 확인)
|
|
94
|
+
|
|
95
|
+
[ ] 2. 각 retry 지점의 부작용을 평가했다
|
|
96
|
+
- 멱등성(idempotency)이 보장되는가?
|
|
97
|
+
- 부분 성공 후 재시도 시 중복 처리가 발생하지 않는가?
|
|
98
|
+
|
|
99
|
+
[ ] 3. 현재 cache 레이어를 모두 식별했다
|
|
100
|
+
- In-process cache (dict, lru_cache)
|
|
101
|
+
- Distributed cache (Redis, Memcached)
|
|
102
|
+
- CDN / Reverse proxy cache
|
|
103
|
+
- DB query cache
|
|
104
|
+
|
|
105
|
+
[ ] 4. 각 cache의 일관성 위험을 평가했다
|
|
106
|
+
- TTL 내 stale data가 얼마나 오래 노출되는가?
|
|
107
|
+
- Cache invalidation 수단이 있는가?
|
|
108
|
+
|
|
109
|
+
[ ] 5. timeout 설정 전/후 레이턴시 분포를 확인했다
|
|
110
|
+
- p50, p95, p99 변화
|
|
111
|
+
- 타임아웃 에러 vs 실제 slow 응답 구분
|
|
112
|
+
|
|
113
|
+
[ ] 6. 변경 이후 실제 원인이 해소되었는지 확인했다
|
|
114
|
+
- 에러율이 줄었는가, 아니면 에러가 숨겨졌는가?
|
|
115
|
+
- 메트릭(retry count, cache hit rate, timeout rate)이 정상 범위로 돌아왔는가?
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 판정 기준
|
|
119
|
+
|
|
120
|
+
| 상황 | 판정 |
|
|
121
|
+
|------|------|
|
|
122
|
+
| retry 줄이면 에러율이 올라간다 | 원인 미해소 — false-fix |
|
|
123
|
+
| cache 끄면 에러가 다시 나타난다 | 원인 미해소 — false-fix |
|
|
124
|
+
| timeout 줄이면 에러가 돌아온다 | 원인 미해소 — false-fix |
|
|
125
|
+
| 위 변경 후에도 에러율 정상 | 원인 해소 — valid fix |
|
|
126
|
+
|
|
127
|
+
## 연관
|
|
128
|
+
|
|
129
|
+
- `amplification-detection.md` — retry storm 시그널 탐지
|
|
130
|
+
- Phase 4 (Isolate Root Cause) — false-fix 판정 후 실제 원인 가설 수립
|
|
131
|
+
- Hard Gate #6 (SKILL.md) — retry/cache/timeout 변경 전 false-fix 가능성 점검
|