oh-my-customcode 0.144.0 → 0.146.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/hooks/hooks.json +10 -0
- package/templates/.claude/hooks/scripts/session-reflection.sh +182 -0
- package/templates/.claude/rules/MUST-completion-verification.md +3 -1
- package/templates/.claude/rules/MUST-intent-transparency.md +18 -8
- 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/guides/claude-code/15-version-compatibility.md +158 -0
- package/templates/manifest.json +1 -1
package/dist/cli/index.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -546,6 +546,16 @@
|
|
|
546
546
|
],
|
|
547
547
|
"description": "Auto-extract failure patterns from session outcomes (advisory, exit 0)"
|
|
548
548
|
},
|
|
549
|
+
{
|
|
550
|
+
"matcher": "*",
|
|
551
|
+
"hooks": [
|
|
552
|
+
{
|
|
553
|
+
"type": "command",
|
|
554
|
+
"command": "bash .claude/hooks/scripts/session-reflection.sh"
|
|
555
|
+
}
|
|
556
|
+
],
|
|
557
|
+
"description": "Transcript-based self-reflection: detect R007/R008 violations and log to .claude/outputs/reflections/ (Phase 1 MVP, advisory, exit 0)"
|
|
558
|
+
},
|
|
549
559
|
{
|
|
550
560
|
"matcher": "*",
|
|
551
561
|
"hooks": [
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# session-reflection.sh — Stop hook: transcript-based self-reflection for R007/R008 violations
|
|
3
|
+
# Phase 1 MVP: detects header absence (R007) and tool prefix absence (R008)
|
|
4
|
+
# Advisory-only: always exits 0, never blocks session end
|
|
5
|
+
#
|
|
6
|
+
# 환경변수 override (테스트/디버깅용):
|
|
7
|
+
# OMCUSTOM_SESSION_REFLECTION=off — 분석 완전 비활성화
|
|
8
|
+
# OMCUSTOM_TRANSCRIPT_BASE — transcript 디렉토리 경로 override
|
|
9
|
+
# OMCUSTOM_PROJECT_ROOT — 프로젝트 루트 override (.claude/outputs/reflections 기준)
|
|
10
|
+
|
|
11
|
+
set -euo pipefail
|
|
12
|
+
|
|
13
|
+
# ── Stop hook 프로토콜: stdin을 먼저 읽음 ──
|
|
14
|
+
input=$(cat)
|
|
15
|
+
|
|
16
|
+
# ── Opt-out 체크 ──
|
|
17
|
+
if [ "${OMCUSTOM_SESSION_REFLECTION:-}" = "off" ]; then
|
|
18
|
+
echo "$input"
|
|
19
|
+
exit 0
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
# ── jq 의존성 체크 ──
|
|
23
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
24
|
+
echo "$input"
|
|
25
|
+
exit 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# ── session_id 추출 ──
|
|
29
|
+
session_id=$(echo "$input" | jq -r '.session_id // empty' 2>/dev/null)
|
|
30
|
+
if [ -z "$session_id" ]; then
|
|
31
|
+
echo "$input"
|
|
32
|
+
exit 0
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
# ── 경로 결정 (환경변수 override 지원) ──
|
|
36
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
37
|
+
|
|
38
|
+
# transcript 기본 경로 (OMCUSTOM_TRANSCRIPT_BASE 로 override 가능)
|
|
39
|
+
TRANSCRIPT_BASE="${OMCUSTOM_TRANSCRIPT_BASE:-${HOME}/.claude/projects/-Users-sangyi-workspace-projects-oh-my-customcode}"
|
|
40
|
+
TRANSCRIPT_PATH="${TRANSCRIPT_BASE}/${session_id}.jsonl"
|
|
41
|
+
|
|
42
|
+
if [ ! -f "$TRANSCRIPT_PATH" ]; then
|
|
43
|
+
echo "$input"
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# 프로젝트 루트 (OMCUSTOM_PROJECT_ROOT 로 override 가능)
|
|
48
|
+
PROJECT_ROOT="${OMCUSTOM_PROJECT_ROOT:-$(cd "${SCRIPT_DIR}/../../.." && pwd)}"
|
|
49
|
+
|
|
50
|
+
# ── 백그라운드 분석 스크립트를 임시 파일로 생성 ──
|
|
51
|
+
# nohup bash -c "..." 또는 heredoc 방식은 글로브 확장/shell escape/stdin 충돌 문제 발생.
|
|
52
|
+
# 임시 파일 방식은 이 문제를 완전히 회피한다.
|
|
53
|
+
WORKER_SCRIPT="/tmp/.claude-reflection-worker-${PPID}-$$.sh"
|
|
54
|
+
|
|
55
|
+
cat > "$WORKER_SCRIPT" <<'WORKER_EOF'
|
|
56
|
+
#!/usr/bin/env bash
|
|
57
|
+
# Background worker: transcript analysis for R007/R008 violations
|
|
58
|
+
|
|
59
|
+
set -euo pipefail
|
|
60
|
+
|
|
61
|
+
TRANSCRIPT_PATH="$1"
|
|
62
|
+
PROJECT_ROOT="$2"
|
|
63
|
+
SESSION_ID="$3"
|
|
64
|
+
SCRIPT_DIR="$4"
|
|
65
|
+
|
|
66
|
+
# 출력 디렉토리 준비
|
|
67
|
+
OUTPUT_DIR="${PROJECT_ROOT}/.claude/outputs/reflections"
|
|
68
|
+
mkdir -p "${OUTPUT_DIR}"
|
|
69
|
+
|
|
70
|
+
LOG_FILE="${OUTPUT_DIR}/$(date +%Y-%m-%d).md"
|
|
71
|
+
ISO8601="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
72
|
+
|
|
73
|
+
# ── transcript 파싱 ──
|
|
74
|
+
r007_violations=0
|
|
75
|
+
r008_violations=0
|
|
76
|
+
total_turns=0
|
|
77
|
+
sample_count=0
|
|
78
|
+
sample_lines=""
|
|
79
|
+
|
|
80
|
+
while IFS= read -r line; do
|
|
81
|
+
role=$(echo "$line" | jq -r '.role // empty' 2>/dev/null) || continue
|
|
82
|
+
[ "$role" = "assistant" ] || continue
|
|
83
|
+
|
|
84
|
+
total_turns=$((total_turns + 1))
|
|
85
|
+
turn_idx=$total_turns
|
|
86
|
+
|
|
87
|
+
# content 배열 파싱
|
|
88
|
+
content_raw=$(echo "$line" | jq -c '.content // []' 2>/dev/null) || continue
|
|
89
|
+
|
|
90
|
+
# ── R007: 첫 번째 text 블록의 첫 줄 체크 ──
|
|
91
|
+
first_text=$(echo "$content_raw" | jq -r '[.[] | select(.type == "text")][0].text // empty' 2>/dev/null) || true
|
|
92
|
+
if [ -n "$first_text" ]; then
|
|
93
|
+
first_line=$(printf '%s' "$first_text" | head -1)
|
|
94
|
+
# R007 패턴: '┌─ Agent:' 또는 '[anything]' 단축 형태
|
|
95
|
+
if ! printf '%s' "$first_line" | grep -qE '(^┌─ Agent:|^\[.+\])'; then
|
|
96
|
+
r007_violations=$((r007_violations + 1))
|
|
97
|
+
if [ $sample_count -lt 3 ]; then
|
|
98
|
+
# 120자 truncate (secret-filter.sh는 PostToolUse용이라 여기서는 단순 truncate)
|
|
99
|
+
safe_text=$(printf '%s' "$first_line" | head -c 120)
|
|
100
|
+
sample_lines="${sample_lines}
|
|
101
|
+
- [R007 turn ${turn_idx}]: ${safe_text}"
|
|
102
|
+
sample_count=$((sample_count + 1))
|
|
103
|
+
fi
|
|
104
|
+
fi
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# ── R008: tool_use 블록 직전 text에 prefix 체크 ──
|
|
108
|
+
content_length=$(echo "$content_raw" | jq 'length' 2>/dev/null) || continue
|
|
109
|
+
|
|
110
|
+
i=0
|
|
111
|
+
while [ $i -lt "$content_length" ]; do
|
|
112
|
+
block_type=$(echo "$content_raw" | jq -r ".[$i].type // empty" 2>/dev/null) || { i=$((i+1)); continue; }
|
|
113
|
+
|
|
114
|
+
if [ "$block_type" = "tool_use" ]; then
|
|
115
|
+
tool_name=$(echo "$content_raw" | jq -r ".[$i].name // empty" 2>/dev/null) || true
|
|
116
|
+
|
|
117
|
+
# 직전 블록이 text이고 R008 prefix를 포함하는지 체크
|
|
118
|
+
has_prefix=false
|
|
119
|
+
if [ $i -gt 0 ]; then
|
|
120
|
+
prev_type=$(echo "$content_raw" | jq -r ".[$(( i - 1 ))].type // empty" 2>/dev/null) || true
|
|
121
|
+
if [ "$prev_type" = "text" ]; then
|
|
122
|
+
prev_text=$(echo "$content_raw" | jq -r ".[$(( i - 1 ))].text // empty" 2>/dev/null) || true
|
|
123
|
+
# R008 패턴: '[agent-name][model] → Tool:' 또는 '→ Target:'
|
|
124
|
+
if printf '%s' "$prev_text" | grep -qE '\[.+\]\[.+\] ?(→|->|—>) ?(Tool|Target):'; then
|
|
125
|
+
has_prefix=true
|
|
126
|
+
fi
|
|
127
|
+
fi
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
if [ "$has_prefix" = "false" ]; then
|
|
131
|
+
r008_violations=$((r008_violations + 1))
|
|
132
|
+
if [ $sample_count -lt 3 ]; then
|
|
133
|
+
sample_lines="${sample_lines}
|
|
134
|
+
- [R008 turn ${turn_idx}]: ${tool_name}, missing prefix"
|
|
135
|
+
sample_count=$((sample_count + 1))
|
|
136
|
+
fi
|
|
137
|
+
fi
|
|
138
|
+
fi
|
|
139
|
+
|
|
140
|
+
i=$((i+1))
|
|
141
|
+
done
|
|
142
|
+
|
|
143
|
+
done < "$TRANSCRIPT_PATH"
|
|
144
|
+
|
|
145
|
+
# ── 로그 작성 ──
|
|
146
|
+
{
|
|
147
|
+
echo ""
|
|
148
|
+
echo "## Session ${SESSION_ID} — ${ISO8601}"
|
|
149
|
+
echo ""
|
|
150
|
+
echo "- **R007 violations**: ${r007_violations}"
|
|
151
|
+
echo "- **R008 violations**: ${r008_violations}"
|
|
152
|
+
echo "- Total assistant turns analyzed: ${total_turns}"
|
|
153
|
+
if [ $sample_count -gt 0 ]; then
|
|
154
|
+
echo ""
|
|
155
|
+
echo "### Sample violations (최대 3개)"
|
|
156
|
+
printf '%s\n' "${sample_lines}"
|
|
157
|
+
fi
|
|
158
|
+
} >> "${LOG_FILE}"
|
|
159
|
+
|
|
160
|
+
echo "[session-reflection] Analysis complete: R007=${r007_violations} R008=${r008_violations} turns=${total_turns}" >&2
|
|
161
|
+
WORKER_EOF
|
|
162
|
+
|
|
163
|
+
chmod +x "$WORKER_SCRIPT"
|
|
164
|
+
|
|
165
|
+
# ── 백그라운드로 실행 (Stop hook 시간 예산 <3s 준수) ──
|
|
166
|
+
# setsid로 부모 종료 후에도 실행 지속; 완료 후 임시 파일 자정리
|
|
167
|
+
WORKER_ERR_LOG="/tmp/.claude-reflection-err-${PPID}.log"
|
|
168
|
+
|
|
169
|
+
(
|
|
170
|
+
bash "$WORKER_SCRIPT" \
|
|
171
|
+
"$TRANSCRIPT_PATH" \
|
|
172
|
+
"$PROJECT_ROOT" \
|
|
173
|
+
"$session_id" \
|
|
174
|
+
"$SCRIPT_DIR" \
|
|
175
|
+
2>>"$WORKER_ERR_LOG"
|
|
176
|
+
rm -f "$WORKER_SCRIPT"
|
|
177
|
+
) &
|
|
178
|
+
disown $! 2>/dev/null || true
|
|
179
|
+
|
|
180
|
+
# ── 즉시 stdin pass-through 후 exit 0 (Stop hook 체인 유지) ──
|
|
181
|
+
echo "$input"
|
|
182
|
+
exit 0
|
|
@@ -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
|
|
@@ -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 가능성 점검
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Timeline Correlation
|
|
2
|
+
|
|
3
|
+
장애 시점과 코드 변경/배포/설정 변경의 상관관계를 추적하는 절차다.
|
|
4
|
+
|
|
5
|
+
## 목적
|
|
6
|
+
|
|
7
|
+
"언제부터 깨졌는가"를 정확히 특정하면 원인 후보 범위가 90% 이상 줄어든다.
|
|
8
|
+
타임라인 없이 수정에 들어가는 것은 Phase 4(원인 격리) 없이 Phase 6(수정)으로 뛰는 것과 같다.
|
|
9
|
+
|
|
10
|
+
## 절차
|
|
11
|
+
|
|
12
|
+
### Step 1. 장애 발생 시각 고정
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# 모니터링/로그에서 최초 에러 타임스탬프 확인
|
|
16
|
+
grep -E "ERROR|FATAL|Exception" application.log | head -5
|
|
17
|
+
|
|
18
|
+
# 알림 기록, on-call 채널, Sentry/Datadog alert 시각 메모
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
출력 형식:
|
|
22
|
+
```
|
|
23
|
+
Incident start: YYYY-MM-DD HH:MM:SS UTC
|
|
24
|
+
First observed: <source> (log | alert | user report)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Step 2. 코드 변경 이력 수집
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# 장애 시점 전 72시간 커밋 목록
|
|
31
|
+
git log --since="72 hours ago" --until="<incident_time>" \
|
|
32
|
+
--format="%h %ad %an %s" --date=short
|
|
33
|
+
|
|
34
|
+
# 특정 서비스 경로만
|
|
35
|
+
git log --since="72 hours ago" --format="%h %ad %s" --date=iso \
|
|
36
|
+
-- src/service/ config/
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Step 3. 배포 이력 수집
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# GitHub Actions / CI 배포 로그에서 타임스탬프 확인
|
|
43
|
+
gh run list --limit 20 --json createdAt,status,name \
|
|
44
|
+
| jq '.[] | select(.status=="completed") | {name,createdAt}'
|
|
45
|
+
|
|
46
|
+
# 쿠버네티스 배포 이력
|
|
47
|
+
kubectl rollout history deployment/<name>
|
|
48
|
+
kubectl describe deployment/<name> | grep "Updated:"
|
|
49
|
+
|
|
50
|
+
# Docker 이미지 태그 변경 시각
|
|
51
|
+
docker image inspect <image>:<tag> | jq '.[0].Created'
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Step 4. 설정 변경 이력 수집
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# 환경 변수/시크릿 변경 (Vault, AWS SSM, k8s secret)
|
|
58
|
+
# 변경 이력이 있다면 해당 시스템의 audit log 확인
|
|
59
|
+
|
|
60
|
+
# 데이터베이스 스키마 마이그레이션 시각
|
|
61
|
+
# (Alembic, Flyway, Liquibase 등 migration 실행 로그)
|
|
62
|
+
|
|
63
|
+
# 인프라 변경 (Terraform state history, AWS CloudTrail)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Step 5. 타임라인 정렬
|
|
67
|
+
|
|
68
|
+
수집한 이벤트를 시간 순으로 정렬한다.
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
Timeline:
|
|
72
|
+
T-48h 2026-05-17 10:22 commit abc123 — "Update retry config"
|
|
73
|
+
T-24h 2026-05-18 14:05 deploy v2.3.1 — production
|
|
74
|
+
T-6h 2026-05-19 08:30 config change — DB_POOL_SIZE 10→5
|
|
75
|
+
T-0 2026-05-19 14:47 INCIDENT — 500 error spike
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Step 6. 의심 후보 식별
|
|
79
|
+
|
|
80
|
+
타임라인에서 장애 직전 변경을 의심 후보로 분류한다.
|
|
81
|
+
|
|
82
|
+
| 우선도 | 기준 |
|
|
83
|
+
|--------|------|
|
|
84
|
+
| High | 장애 시작 0~2시간 전 변경 |
|
|
85
|
+
| Medium | 장애 시작 2~24시간 전 변경 |
|
|
86
|
+
| Low | 그 이전 변경 (간헐 버그 제외) |
|
|
87
|
+
|
|
88
|
+
간헐 버그는 시작 시각 특정이 어렵다 — 첫 관측 시각을 기준으로 삼되 Low 우선도 후보도 배제하지 않는다.
|
|
89
|
+
|
|
90
|
+
## 출력 형식
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
[Timeline Correlation]
|
|
94
|
+
├── Incident start: <timestamp>
|
|
95
|
+
├── Window: <N> hours searched
|
|
96
|
+
├── Events found: <count>
|
|
97
|
+
│ ├── Code changes: <N>
|
|
98
|
+
│ ├── Deploys: <N>
|
|
99
|
+
│ └── Config changes: <N>
|
|
100
|
+
├── Suspects (High): <commit/deploy/config>
|
|
101
|
+
└── Next: Phase 4 (Isolate Root Cause) with suspect list
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 주의
|
|
105
|
+
|
|
106
|
+
- 타임라인 자체가 가설이다. 상관관계가 인과관계를 보장하지 않는다.
|
|
107
|
+
- 변경이 없는 기간에 발생한 장애는 외부 의존성(DB, 서드파티 API, 인프라) 변화를 확인한다.
|
|
108
|
+
- 의심 후보가 없으면 관측 범위를 넓히거나 Phase 3(증거 수집)으로 돌아간다.
|
|
109
|
+
|
|
110
|
+
## 연관
|
|
111
|
+
|
|
112
|
+
- Phase 3 (Gather Evidence) — 타임라인은 증거 수집의 입력으로 사용
|
|
113
|
+
- Phase 4 (Isolate Root Cause) — 의심 후보가 가설 수립의 출발점
|
|
114
|
+
- `amplification-detection.md` — 배포 후 retry storm이 감지되면 타임라인과 교차 검증
|
|
@@ -483,6 +483,162 @@ docs/superpowers/plans/**
|
|
|
483
483
|
| v2.1.140 | P3 audit: managed `extraKnownMarketplaces` 영속화 + plugin.json default folder 무시 경고 | P3 follow-up |
|
|
484
484
|
| v2.1.141 | P3: `terminalSequence` hook 검토 + cli-flags `--cwd` 추가. R010 `/bg` 권한 모드 유지 노트 추가 (완료) | P3 follow-up |
|
|
485
485
|
| v2.1.142 | Fast Mode Opus 4.7 전환 확인 (필요 시 `CLAUDE_CODE_OPUS_4_6_FAST_MODE_OVERRIDE=1`). `MCP_TOOL_TIMEOUT` 선택적 설정. | P3 follow-up |
|
|
486
|
+
| v2.1.143 | 직접 변경 불필요. `worktree.bgIsolation: "none"` opt-in 시 파일 소유권 규율 필수. Stop hook 8회 block cap 인지. | P3 follow-up |
|
|
487
|
+
| v2.1.144 | 호환 가능. CLAUDE.md `omcustomMinClaudeCode` v2.1.121 유지. macOS bg session FDA crash fix 확인. | None |
|
|
488
|
+
| v2.1.145 | docs-only. `claude agents --json` HUD 강화, Stop/SubagentStop hook `background_tasks`/`session_crons` 활용, status line GitHub PR 통합 — 별도 follow-up 권장. | P3 follow-up |
|
|
489
|
+
|
|
490
|
+
## v2.1.144 (2026-05-19) — 호환성 점검
|
|
491
|
+
|
|
492
|
+
> Issue: #1187 — CC v2.1.144 compatibility documentation
|
|
493
|
+
|
|
494
|
+
### `/resume` 백그라운드 세션 지원
|
|
495
|
+
|
|
496
|
+
`claude --bg` 또는 agent view로 시작된 세션이 `/resume` 목록에 표시됩니다 (`bg` 배지로 구분).
|
|
497
|
+
|
|
498
|
+
**oh-my-customcode 연관**: R018 Agent Teams 장시간 세션 및 `/bg` 플로우 복귀가 편리해집니다. 직접 변경 불필요.
|
|
499
|
+
|
|
500
|
+
### 백그라운드 subagent 완료 알림에 경과 시간 추가
|
|
501
|
+
|
|
502
|
+
백그라운드 subagent 완료 알림에 "Agent completed · 3h 2m 5s" 형태로 경과 시간이 표시됩니다.
|
|
503
|
+
|
|
504
|
+
**oh-my-customcode 연관**: R009 병렬 에이전트 성능 추적에 유용. `claude agents` 뷰에서 장시간 실행 감지(2x+ 지속 시 split 권장 — R009 Adaptive Parallel Splitting)에 활용 가능.
|
|
505
|
+
|
|
506
|
+
### `/plugin` Browse/Discover pane 마지막 업데이트 시각 표시
|
|
507
|
+
|
|
508
|
+
플러그인 마지막 업데이트 시각이 표시됩니다.
|
|
509
|
+
|
|
510
|
+
**oh-my-customcode 연관**: 필수/권장 플러그인(superpowers, context7 등) 버전 신선도 모니터링에 유용. 직접 변경 불필요.
|
|
511
|
+
|
|
512
|
+
### `/model` 현재 세션만 변경 (`d` 키로 새 세션 기본값 설정)
|
|
513
|
+
|
|
514
|
+
`/model`이 현재 세션만 변경하며, `d` 키로 새 세션 기본값을 별도 설정합니다.
|
|
515
|
+
|
|
516
|
+
**oh-my-customcode 연관**: R006 agent frontmatter `model:` 설정이 세션 기본값과 독립적으로 동작하는 동작과 정합. 직접 변경 불필요.
|
|
517
|
+
|
|
518
|
+
### "extra usage" → "usage credits" 명명 변경
|
|
519
|
+
|
|
520
|
+
`/extra-usage` → `/usage-credits` (구 명령 호환 유지).
|
|
521
|
+
|
|
522
|
+
**oh-my-customcode 연관**: CLAUDE.md 슬래시 커맨드 표 및 가이드 문서에서 `/extra-usage` 언급이 있다면 `/usage-credits`로 업데이트 권장. 현재 oh-my-customcode 문서에는 해당 커맨드 직접 노출 없음 — 영향 없음.
|
|
523
|
+
|
|
524
|
+
### 시작 시 `api.anthropic.com` 도달 불가 시 타임아웃 개선
|
|
525
|
+
|
|
526
|
+
75초 멈춤 → 15초 timeout으로 수정. captive portal/firewall/VPN 환경에서 시작 지연 대폭 감소.
|
|
527
|
+
|
|
528
|
+
**oh-my-customcode 연관**: R001 안전 규칙 및 네트워크 제한 환경에서의 CI 실행 안정성 개선. 직접 변경 불필요.
|
|
529
|
+
|
|
530
|
+
### 터미널 렌더링 수정
|
|
531
|
+
|
|
532
|
+
- 윈도 리사이즈 누락 후 터미널 출력 깨짐 → 다음 프레임에 self-heal (Ctrl+L 불필요)
|
|
533
|
+
- 긴 세션의 점진적 터미널 디스플레이 손상 수정
|
|
534
|
+
- VS Code 스피너 애니메이션 색상 수 감소로 렌더링 글리치 완화
|
|
535
|
+
|
|
536
|
+
**oh-my-customcode 연관**: R012 HUD statusline 및 장시간 병렬 에이전트 세션 가독성 개선. 직접 변경 불필요.
|
|
537
|
+
|
|
538
|
+
### macOS 배경 세션 "exit 1 before init" crash 수정 (Full Disk Access 영역)
|
|
539
|
+
|
|
540
|
+
macOS Full Disk Access 권한 영역에서 배경 세션이 초기화 전에 exit 1로 종료되던 v2.1.143 regression이 수정되었습니다.
|
|
541
|
+
|
|
542
|
+
**oh-my-customcode 연관**: R011 메모리 동작 영향 없음. macOS 환경에서 `/bg` 기반 자동화 실행 안정성 복원.
|
|
543
|
+
|
|
544
|
+
### oh-my-customcode 연관 평가
|
|
545
|
+
|
|
546
|
+
| 변경 | 영향 영역 | Action |
|
|
547
|
+
|------|----------|--------|
|
|
548
|
+
| `/resume` bg 세션 표시 | R018 세션 복귀 편의성 | None |
|
|
549
|
+
| 백그라운드 subagent 경과 시간 | R009 성능 추적 | None (수동 활용 가능) |
|
|
550
|
+
| `/plugin` 업데이트 시각 | 플러그인 신선도 확인 | None |
|
|
551
|
+
| `/model` 세션 vs 기본값 분리 | R006 model 설정과 정합 | None |
|
|
552
|
+
| `/usage-credits` 명명 변경 | 문서 참조 | None (현재 노출 없음) |
|
|
553
|
+
| 15초 startup timeout | CI/네트워크 환경 안정성 | None |
|
|
554
|
+
| 터미널 렌더링 수정 | R012 HUD 가독성 | None |
|
|
555
|
+
| macOS bg session crash fix | R011 메모리, `/bg` 플로우 | None |
|
|
556
|
+
|
|
557
|
+
**Action items**: 호환 가능. CLAUDE.md `omcustomMinClaudeCode` 헤더는 v2.1.121 유지 (신규 기능 의존 없음).
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## v2.1.145 (2026-05-19) — 호환성 점검
|
|
562
|
+
|
|
563
|
+
> Issue: #1191 — CC v2.1.145 compatibility documentation
|
|
564
|
+
|
|
565
|
+
### `claude agents --json` — 라이브 세션 JSON 출력
|
|
566
|
+
|
|
567
|
+
`claude agents --json` 플래그로 라이브 Claude 세션 목록을 JSON으로 출력합니다. tmux-resurrect, status bar, session picker 등 외부 스크립팅 통합에 활용할 수 있습니다.
|
|
568
|
+
|
|
569
|
+
**oh-my-customcode 연관**: R012 HUD/statusline 강화 후보. `.claude/statusline.sh`가 `claude agents --json`을 파싱하여 활성 에이전트 수를 status bar에 표시하는 통합이 가능합니다. 별도 follow-up 권장 (P3).
|
|
570
|
+
|
|
571
|
+
### OTEL `agent_id` / `parent_agent_id` 속성 + 배경 subagent span nesting 수정
|
|
572
|
+
|
|
573
|
+
`claude_code.tool` OTEL span에 `agent_id`와 `parent_agent_id` 속성이 추가되었습니다. 배경 subagent span nesting도 수정되었습니다.
|
|
574
|
+
|
|
575
|
+
**oh-my-customcode 연관**: `monitoring-setup` 스킬 및 R018 Agent Teams 트레이싱에서 에이전트 계층 구조 추적이 개선됩니다. 호환 가능 — 기존 OTEL 설정 변경 불필요.
|
|
576
|
+
|
|
577
|
+
### Status line JSON에 GitHub repo/PR 정보 자동 포함
|
|
578
|
+
|
|
579
|
+
`.claude/statusline.sh`가 JSON 입력을 받는 경우 GitHub repo 및 PR 정보가 자동 포함됩니다.
|
|
580
|
+
|
|
581
|
+
**oh-my-customcode 연관**: R012 statusline 강화 가능. 현재 `.claude/statusline.sh`가 branch 정보를 표시하는데, GitHub PR 번호/상태를 추가로 표시하는 통합이 가능합니다. 별도 follow-up 권장 (P3).
|
|
582
|
+
|
|
583
|
+
### `/plugin` Discover/Browse 화면에 설치 전 상세 정보 표시
|
|
584
|
+
|
|
585
|
+
설치 전 commands/agents/skills/hooks/MCP/LSP 서버 목록을 확인할 수 있습니다.
|
|
586
|
+
|
|
587
|
+
**oh-my-customcode 연관**: R013 ecomode 및 토큰 효율 측면에서 플러그인 도입 전 비용/기능 점검 개선. 직접 변경 불필요.
|
|
588
|
+
|
|
589
|
+
### `claude agents` 탭 제목에 awaiting-input 카운트 표시
|
|
590
|
+
|
|
591
|
+
**oh-my-customcode 연관**: R009 병렬 에이전트 모니터링 개선. 사용자 입력 대기 에이전트를 탭 제목에서 즉시 확인 가능.
|
|
592
|
+
|
|
593
|
+
### Stop / SubagentStop hook 입력에 `background_tasks`, `session_crons` 필드 추가
|
|
594
|
+
|
|
595
|
+
Stop 및 SubagentStop hook의 입력 JSON에 `background_tasks`와 `session_crons` 필드가 추가되었습니다.
|
|
596
|
+
|
|
597
|
+
**oh-my-customcode 연관**: `.claude/hooks/` 내 `feedback-collector.sh` 및 Stop hook 스크립트와 호환됩니다 (옵션 필드이므로 기존 스크립트 영향 없음). hook input schema를 활용하는 고급 패턴(background task 완료 확인 등)에서 활용 가능 — 별도 follow-up 권장.
|
|
598
|
+
|
|
599
|
+
### Bash 명령 bare variable assignment 자동 승인 우회 취약점 수정
|
|
600
|
+
|
|
601
|
+
non-allowlisted env var의 bare variable assignment가 자동 승인을 우회하던 취약점이 수정되었습니다.
|
|
602
|
+
|
|
603
|
+
**oh-my-customcode 연관**: R002 권한 규칙 강화. `bypassPermissions` 모드 하 Bash 도구 사용 시 의도치 않은 환경 변수 주입 경로가 차단됩니다. 직접 harness 변경 불필요.
|
|
604
|
+
|
|
605
|
+
### Agent Teams non-ASCII teammate name 수정
|
|
606
|
+
|
|
607
|
+
Agent Teams 멤버 이름에 non-ASCII 문자(한국어 포함)가 포함된 경우의 버그가 수정되었습니다.
|
|
608
|
+
|
|
609
|
+
**oh-my-customcode 연관**: R018 Agent Teams에서 한국어 멤버 이름을 사용하는 경우 영향. v2.1.145로 업그레이드 후 검증 권장.
|
|
610
|
+
|
|
611
|
+
### 기타 수정
|
|
612
|
+
|
|
613
|
+
- 슬래시 커맨드/`@`-mention 제안 목록에 마우스 hover/click 지원 (fullscreen)
|
|
614
|
+
- 터미널 리사이즈/리포커스 후 스피너/경과시간 freeze 수정
|
|
615
|
+
- Task list random order 수정
|
|
616
|
+
- MCP prompt slash command missing argument 에러 메시지 개선
|
|
617
|
+
|
|
618
|
+
**oh-my-customcode 연관**: 영향 없음 (UX/안정성 개선).
|
|
619
|
+
|
|
620
|
+
### oh-my-customcode 연관 평가
|
|
621
|
+
|
|
622
|
+
| 변경 | 영향 영역 | 상태 |
|
|
623
|
+
|------|----------|------|
|
|
624
|
+
| `claude agents --json` | R012 HUD/statusline 통합 가능 | P3 follow-up 권장 |
|
|
625
|
+
| OTEL `agent_id`/`parent_agent_id` + bg subagent nesting | `monitoring-setup` 스킬 / R018 Agent Teams 트레이싱 | 호환, 후속 검토 |
|
|
626
|
+
| Stop/SubagentStop hook의 `background_tasks`, `session_crons` 필드 | `feedback-collector.sh` 호환 OK (옵션 필드) | hook input schema 갱신 후보 |
|
|
627
|
+
| Status line JSON에 GitHub repo + PR 정보 | `.claude/statusline.sh` 강화 가능 (R012) | P3 follow-up 권장 |
|
|
628
|
+
| Bare variable assignment auto-approve bypass fix | R002 permissions 강화 | 호환 |
|
|
629
|
+
| Agent Teams non-ASCII teammate name fix | R018 한국어 멤버 사용 시 영향 | v2.1.145 업그레이드 후 검증 권장 |
|
|
630
|
+
| `/plugin` 상세 정보 사전 표시 | R013 플러그인 비용 점검 | None |
|
|
631
|
+
| `claude agents` awaiting-input 카운트 | R009 병렬 모니터링 개선 | None |
|
|
632
|
+
| 기타 UX/안정성 수정 | 일반 개선 | None |
|
|
633
|
+
|
|
634
|
+
**Action items**:
|
|
635
|
+
- 본 릴리스에서 코드 변경 없음 (docs-only)
|
|
636
|
+
- 후속 follow-up 후보 (별도 이슈로 등록 권장):
|
|
637
|
+
1. `claude agents --json`을 활용한 HUD 강화 (R012)
|
|
638
|
+
2. Hook input schema에 `background_tasks` / `session_crons` 활용
|
|
639
|
+
3. Status line JSON GitHub PR 정보 통합 (R012)
|
|
640
|
+
|
|
641
|
+
---
|
|
486
642
|
|
|
487
643
|
## References
|
|
488
644
|
|
|
@@ -494,6 +650,8 @@ docs/superpowers/plans/**
|
|
|
494
650
|
- #1137 — Claude Code v2.1.141 compatibility documentation
|
|
495
651
|
- #1158 — Claude Code v2.1.142 compatibility documentation
|
|
496
652
|
- #1147 — .gitignore nested .md pattern limitation note
|
|
653
|
+
- #1187 — Claude Code v2.1.144 compatibility documentation
|
|
654
|
+
- #1191 — Claude Code v2.1.145 compatibility documentation
|
|
497
655
|
- `.claude/skills/claude-native/` — auto-generation source
|
|
498
656
|
- `.claude/rules/SHOULD-hud-statusline.md` — R012 statusline integration
|
|
499
657
|
- `.claude/rules/MUST-agent-design.md` — R006 agent frontmatter spec
|
package/templates/manifest.json
CHANGED