Conversation
WalkthroughPR은 레거시 AI 설명서를 제거하고, Claude 에이전트/스킬 정의 모음을 추가하여 코드 리뷰, 테스트, API 설명서, 백엔드 아키텍처 설계, 장애 대응 워크플로우를 자동화합니다. 도메인별 설명서 재구성, FCM 토큰 및 스터디 세션 시간 단위 조정(분→시간), MCP 서버 구성도 포함됩니다. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
Note
Due to the large number of review comments, Critical severity comments were prioritized as inline comments.
🟠 Major comments (19)
.claude/agents/backend-architect.md-4-4 (1)
4-4:⚠️ Potential issue | 🟠 Major설계 전용 에이전트에 과권한 도구가 포함되어 있습니다.
Line 4에서
Write, Edit, Bash까지 기본 허용하면, 아키텍처 제안 단계에서 불필요한 변경/실행 권한이 열립니다. 기본은Read, Grep, Glob로 제한하고, 산출물 생성이 꼭 필요할 때만 단계적으로 권한을 열어주는 구성이 더 안전합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/agents/backend-architect.md at line 4, The agent configuration line containing the tools list ("tools: Read, Write, Edit, Bash, Grep, Glob") grants excessive privileges for an architecture-only agent; change that declaration to the minimal set ("tools: Read, Grep, Glob") and remove Write, Edit, Bash from the default tools list in the same config entry for the backend-architect agent, and update any related comments or documentation to state that Write/Edit/Bash will be granted only in later steps when artifact creation or execution is explicitly required..claude/agents/code-reviewer.md-4-4 (1)
4-4:⚠️ Potential issue | 🟠 Major코드 리뷰 전용 에이전트 권한이 과도합니다.
Line 4의
Write,Edit,Bash는 리뷰 범위를 넘어 의도치 않은 변경/실행을 유발할 수 있습니다. 기본은 읽기 전용으로 두고, 수정 에이전트로 위임하는 구성이 안전합니다.권한 최소화 예시
-tools: Read, Write, Edit, Bash, Glob, Grep +tools: Read, Glob, Grep🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/agents/code-reviewer.md at line 4, The agent's permission list "tools: Read, Write, Edit, Bash, Glob, Grep" is overly broad; remove or revoke the dangerous capabilities (Write, Edit, Bash) and leave only read/listing/search privileges (e.g., "Read, Glob, Grep") so the reviewer remains read-only; if editing/execution is needed, delegate those actions to a separate modifier agent or CI job and document that delegation beside the permission string..mcp.json-6-14 (1)
6-14:⚠️ Potential issue | 🟠 Major외부 패키지 버전을 명시적으로 고정해주세요.
npx -y@upstash/context7-mcp와 `npx -y `@modelcontextprotocol/server-sequential-thinking(7행, 14행)처럼 버전을 지정하지 않으면 실행 시마다 최신 버전을 가져오므로, 브레이킹 체인지로 인한 예기치 않은 오류 발생 가능성과 CI/로컬/운영 환경 간 동작 불일치 위험이 있습니다. 현재 사용 가능한 최신 버전(@upstash/context7-mcp@2.1.6,@modelcontextprotocol/server-sequential-thinking@2025.12.18)을 참고하여 버전을 명시적으로 고정하기를 권장합니다.예시 변경
"context7": { "command": "npx", "args": [ "-y", - "@upstash/context7-mcp" + "@upstash/context7-mcp@2.1.6" ] }, "sequential-thinking": { "command": "npx", "args": [ "-y", - "@modelcontextprotocol/server-sequential-thinking" + "@modelcontextprotocol/server-sequential-thinking@2025.12.18" ] }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.mcp.json around lines 6 - 14, Pin the external packages in the npx args to specific versions: replace the occurrences of "@upstash/context7-mcp" and "@modelcontextprotocol/server-sequential-thinking" in the args arrays with their fixed versions (use `@upstash/context7-mcp`@2.1.6 and `@modelcontextprotocol/server-sequential-thinking`@2025.12.18) so the commands that use these identifiers always fetch the specified, versioned packages..claude/agents/test-engineer.md-215-229 (1)
215-229:⚠️ Potential issue | 🟠 Major외부 바이너리 의존성: 호스트에 설치되지 않을 수 있습니다.
Lines 220-221에서
pg_isready와redis-cli를 호스트에서 직접 실행하고 있습니다. 이 도구들이 설치되지 않은 환경에서는 실패합니다. Docker 컨테이너가 사용 중이므로docker-compose exec또는 HTTP health check를 통해 준비 상태를 확인하세요.🐳 Docker 컨테이너 상태 확인으로 개선
async waitForServices(timeout = 30000) { const startTime = Date.now(); while (Date.now() - startTime < timeout) { try { - execSync('pg_isready -h localhost -p 5433', { stdio: 'pipe' }); - execSync('redis-cli -p 6380 ping', { stdio: 'pipe' }); + // Check Postgres via docker-compose + execSync('docker-compose -f docker-compose.test.yml exec -T postgres pg_isready', { stdio: 'pipe' }); + // Check Redis via docker-compose + execSync('docker-compose -f docker-compose.test.yml exec -T redis redis-cli ping', { stdio: 'pipe' }); return; // Services are ready } catch (error) { await new Promise(resolve => setTimeout(resolve, 1000)); } } throw new Error('Test services failed to start within timeout'); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/agents/test-engineer.md around lines 215 - 229, The waitForServices function currently shells out with execSync to run external binaries (pg_isready and redis-cli) which may not exist on the host; replace those execSync checks in waitForServices with container-aware health checks: either run the commands via docker-compose exec / docker exec (so pg_isready/redis-cli run inside the DB/Redis containers) or, better, probe the services directly using native clients (e.g., a small Postgres connection attempt with node-postgres and a Redis PING via ioredis) or query Docker health status (docker inspect / docker-compose ps health) to determine readiness; update the code paths that call execSync to use the chosen container-aware/connection-based check and keep the same timeout/retry loop logic..claude/agents/test-engineer.md-633-670 (1)
633-670:⚠️ Potential issue | 🟠 Major0으로 나누기 및 빈 배열 처리: 요청이 없는 경우를 처리하세요.
Lines 647, 649, 654에서
requests.length가 0일 때 0으로 나누기가 발생하고, lines 652-658에서 빈responseTimes배열에 대한Math.min/max호출이Infinity를 반환합니다. 요청이 없는 경우를 명시적으로 처리해야 합니다.🛡️ 빈 결과에 대한 방어 코드 추가
analyzeResults(results) { const { requests, errors, startTime, endTime } = results; const totalDuration = endTime - startTime; + // Handle case when no requests were made + if (requests.length === 0) { + return { + summary: { + totalRequests: 0, + successfulRequests: 0, + failedRequests: errors.length, + errorRate: errors.length > 0 ? 1 : 0, + testDuration: totalDuration, + throughput: 0 + }, + responseTime: { + min: 0, + max: 0, + mean: 0, + p50: 0, + p90: 0, + p95: 0, + p99: 0 + }, + errors: { + total: errors.length, + byType: this.groupBy(errors, 'type'), + timeline: errors.map(e => ({ timestamp: e.timestamp, type: e.type })) + }, + recommendations: [] + }; + } + // Calculate metrics const responseTimes = requests.map(r => r.duration); const successfulRequests = requests.filter(r => r.status < 400); const failedRequests = requests.filter(r => r.status >= 400); const analysis = { summary: { totalRequests: requests.length, successfulRequests: successfulRequests.length, failedRequests: failedRequests.length + errors.length, errorRate: (failedRequests.length + errors.length) / requests.length, testDuration: totalDuration, throughput: (requests.length / totalDuration) * 1000 // requests per second }, responseTime: { min: Math.min(...responseTimes), max: Math.max(...responseTimes), mean: responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length, p50: this.percentile(responseTimes, 50), p90: this.percentile(responseTimes, 90), p95: this.percentile(responseTimes, 95), p99: this.percentile(responseTimes, 99) }, errors: { total: errors.length, byType: this.groupBy(errors, 'type'), timeline: errors.map(e => ({ timestamp: e.timestamp, type: e.type })) }, recommendations: this.generatePerformanceRecommendations( analysis.summary, analysis.responseTime ) }; this.logResults(analysis); return analysis; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/agents/test-engineer.md around lines 633 - 670, The analyzeResults method doesn't guard against no requests, causing divide-by-zero and Math.min/Math.max on empty arrays; update analyzeResults to detect when requests.length === 0 (or responseTimes.length === 0) and return a safe analysis or populate fields with sensible defaults (e.g., totalRequests: 0, successfulRequests: 0, failedRequests: errors.length, errorRate: errors.length ? 1 : 0, testDuration: totalDuration, throughput: 0) and ensure responseTime values (min, max, mean, percentiles) are set to null or 0 when responseTimes is empty; specifically change the calculations in analyzeResults that use requests.length, responseTimes.reduce, Math.min/Math.max, and this.percentile to use guarded logic or early return so no division by zero or Infinity/NaN values are produced..claude/skills/incident-response/SKILL.md-105-111 (1)
105-111: 🛠️ Refactor suggestion | 🟠 MajorJira 프로젝트 선택 로직의 모호성
"Atlassian MCP에서 사용 가능한 프로젝트 조회 후 적절한 프로젝트 선택"이라는 지시가 모호합니다. 여러 프로젝트가 존재할 때 자동 선택 기준이 명시되지 않아 에이전트가 임의로 선택할 수 있습니다.
다음 중 하나를 명시하세요:
- 프로젝트 키를 환경 변수나 설정으로 사전 정의 (예:
JIRA_PROJECT_KEY=GEUMP)- 프로젝트 이름 패턴 매칭 규칙 (예: "Geumpumta" 포함된 프로젝트 우선)
- 복수 프로젝트 발견 시 사용자에게 선택 요청
개선 예시
Atlassian MCP를 사용해 Jira에 티켓을 생성한다. - 프로젝트: 환경 변수 `JIRA_PROJECT_KEY` 사용. 미설정 시 "Geumpumta" 이름 포함 프로젝트 선택. 복수 발견 시 사용자에게 확인 요청. - 이슈 타입: Bug🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/incident-response/SKILL.md around lines 105 - 111, Clarify the Jira project selection logic in .claude/skills/incident-response/SKILL.md: state that the agent should first use the environment variable JIRA_PROJECT_KEY (if set), otherwise fall back to selecting the first project whose name contains "Geumpumta" (or another documented pattern), and if multiple matching projects remain, prompt the user to confirm the correct project; update the "프로젝트:" bullet and include the exact env var name JIRA_PROJECT_KEY and the fallback pattern so the agent logic (project selection) is unambiguous..claude/skills/incident-response/SKILL.md-203-204 (1)
203-204: 🛠️ Refactor suggestion | 🟠 MajorNotion 저장 실패 시 로컬 경로 생성 누락
"연결되지 않았으면 → 마크다운 파일로 로컬 저장"이라고 명시하지만,
docs/incidents/디렉토리가 존재하지 않을 경우 파일 생성이 실패합니다.phase-guide.mdLine 104-108에mkdir -p명령이 있지만 SKILL.md에는 명시되지 않았습니다.Phase 6 실행 순서에 디렉토리 생성 단계를 명시하세요:
개선된 Phase 6 플로우
### Notion MCP 연결 상태 확인 1. Notion MCP 연결 시도 2. **연결 성공** → Notion 페이지에 자동 저장 3. **연결 실패** → 로컬 저장: ```bash mkdir -p docs/incidents # YYYY-MM-DD-{티켓번호}.md 파일 생성저장 후 Notion 연결 방법 안내 (
templates.md참조)</details> <details> <summary>🤖 Prompt for AI Agents</summary>Verify each finding against the current code and only fix it if needed.
In @.claude/skills/incident-response/SKILL.md around lines 203 - 204, Update the
Phase 6 flow in SKILL.md to explicitly create the local incidents directory
before writing the markdown file: when Notion MCP connection fails in Phase 6,
add a step that runs a directory-creation command to ensure docs/incidents
exists (e.g., mkdir -p docs/incidents) prior to generating
YYYY-MM-DD-{ticket}.md, and then point users to templates.md for Notion
connection instructions; modify the Phase 6 section header and steps so the
directory creation is clearly listed between "연결 실패 → 로컬 저장" and the file
creation step.</details> </blockquote></details> <details> <summary>.claude/skills/incident-response/SKILL.md-133-143 (1)</summary><blockquote> `133-143`: _⚠️ Potential issue_ | _🟠 Major_ **Worktree 브랜치 베이스 선택의 위험성** Line 142에서 `git worktree add -b "$BRANCH" "$WORKTREE_PATH" dev`로 항상 `dev` 브랜치를 베이스로 사용하는데, 장애가 이미 `main`이나 `prod`에 배포된 경우 `dev`가 최신이 아닐 수 있습니다. 프로덕션 장애의 경우 다음을 고려하세요: 1. 장애가 발생한 브랜치를 베이스로 선택 (prod 장애 시 `main` 또는 `prod` 브랜치) 2. Phase 1에서 영향 범위(prod/dev)를 식별했으므로, 해당 정보를 Phase 4에서 활용 3. `dev` 고정이 의도된 것이라면 "핫픽스는 항상 dev 기준, prod 머지는 별도 진행" 명시 <details> <summary>조건부 베이스 브랜치 선택 로직 제안</summary> ```bash # Phase 1의 영향 범위(prod/dev)에 따라 베이스 브랜치 결정 if [[ "$AFFECTED_ENV" == "prod" ]]; then BASE_BRANCH="main" # 또는 "prod" else BASE_BRANCH="dev" fi git worktree add -b "$BRANCH" "$WORKTREE_PATH" "$BASE_BRANCH"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/incident-response/SKILL.md around lines 133 - 143, The script always uses dev as the base for git worktree (git worktree add -b "$BRANCH" "$WORKTREE_PATH" dev), which can be incorrect for prod-facing hotfixes; modify the logic that sets the base branch (used by BRANCH and WORKTREE_PATH) to choose BASE_BRANCH conditionally from the Phase-1 impact variable (e.g., AFFECTED_ENV) so that when AFFECTED_ENV=="prod" you set BASE_BRANCH to main or prod, otherwise dev, and then call git worktree add -b "$BRANCH" "$WORKTREE_PATH" "$BASE_BRANCH"; alternatively, if using dev is intentional, add a clear comment stating the policy ("hotfixes based on dev; prod merges handled separately")..claude/skills/incident-response/references/phase-guide.md-65-70 (1)
65-70:⚠️ Potential issue | 🟠 MajorWorktree 정리 명령의 안전성 문제
git worktree remove ../hotfix-{이전티켓}명령을 제시하지만, 해당 worktree에 커밋되지 않은 변경사항이 있을 경우 데이터 손실 위험이 있습니다.더 안전한 접근 방법:
안전한 worktree 정리 절차
### Worktree 경로 충돌 시 `../hotfix-{티켓번호}` 경로가 이미 존재하면: ```bash git worktree list # 기존 worktree 확인 + +# 변경사항 확인 +cd ../hotfix-{이전티켓} +git status +# 커밋되지 않은 변경이 있으면 백업 후 진행 + +cd - -git worktree remove ../hotfix-{이전티켓} # 불필요한 worktree 정리 +git worktree remove ../hotfix-{이전티켓} --force # 강제 제거 (주의) +# 또는 +git worktree remove ../hotfix-{이전티켓} # 변경사항 없을 때만 성공사용자에게 기존 worktree의 상태를 확인하도록 안내하고, 필요 시 백업이나 커밋을 먼저 수행하도록 경고하세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/incident-response/references/phase-guide.md around lines 65 - 70, Update the "Worktree 경로 충돌 시" section to warn about data loss and add a safe procedure: instruct users to run git worktree list, cd into the existing ../hotfix-{이전티켓} and run git status to check for uncommitted changes, advise committing or backing up any changes before removing the worktree, and show that git worktree remove ../hotfix-{이전티켓} only succeeds when no local changes exist while git worktree remove ../hotfix-{이전티켓} --force is available as a last-resort with a clear caution about potential data loss.src/main/java/com/gpt/geumpumtabackend/fcm/service/FcmMessageSender.java-54-57 (1)
54-57:⚠️ Potential issue | 🟠 MajorFCM 토큰 원문을 로그에 남기지 마세요.
디바이스 토큰은 민감한 식별자라 warn 로그에 전체 값을 남기면 운영 로그 유출 시 바로 노출됩니다. 토큰 대신 userId, 해시, 또는 suffix만 남기고, 같은 파일의 recovery 로그도 같은 원칙으로 맞추는 편이 안전합니다.
🔒 제안
- log.warn("FCM permanent error [{}] for token {}: {}", code, messageDto.getToken(), e.getMessage()); + log.warn("FCM permanent error [{}]: {}", code, e.getMessage());🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/gpt/geumpumtabackend/fcm/service/FcmMessageSender.java` around lines 54 - 57, The warn log in FcmMessageSender currently prints the full device token (messageDto.getToken()); change logging to avoid sensitive token leakage by logging a non-sensitive identifier instead (e.g., messageDto.getUserId() or a hashed/token-suffix), e.g., compute a short SHA256 or last 4-6 chars and log that with context in the warn message and in any corresponding recovery/error logs; update the log statements around MessagingErrorCode handling and the recovery path to use the same non-sensitive representation (refer to MessagingErrorCode, messageDto.getToken(), and PermanentFcmException to locate the spots)..claude/skills/skill-creator/agents/comparator.md-13-18 (1)
13-18:⚠️ Potential issue | 🟠 Major결과 파일 경로 계약이 입력 정의에 없습니다.
Line 89는 “specified path”에 저장하라고 하지만, Lines 13-18에는
output_path가 없습니다. 이 상태면 배치/병렬 실행에서 모두comparison.json기본값으로 떨어져 서로 덮어쓸 수 있으니, 입력 섹션에output_path를 명시하고 Step 7과 같은 이름으로 맞춰 주세요.Also applies to: 87-89
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/skill-creator/agents/comparator.md around lines 13 - 18, The inputs list is missing the "output_path" contract which Step 7 refers to as the “specified path”; add "output_path" to the prompt parameters alongside output_a_path, output_b_path, eval_prompt, and expectations, and ensure its name exactly matches the identifier used in Step 7 so outputs are written to the intended location instead of defaulting to comparison.json (which can cause overwrite in parallel/batch runs)..claude/skills/skill-creator/scripts/utils.py-24-47 (1)
24-47:⚠️ Potential issue | 🟠 Major필수 frontmatter 키 누락을 빈 문자열로 반환하지 마세요.
지금은
name이나description이 없거나 오타가 나도 delimiter만 맞으면 파싱 성공으로 처리합니다. 이 헬퍼를 쓰는 downstream script는 잘못된SKILL.md를 조용히 소비하게 되므로, 여기서 바로ValueError를 던지는 편이 안전합니다.수정 예시
while i < len(frontmatter_lines): line = frontmatter_lines[i] if line.startswith("name:"): name = line[len("name:"):].strip().strip('"').strip("'") @@ else: description = value.strip('"').strip("'") i += 1 + if not name: + raise ValueError("SKILL.md frontmatter missing required field: name") + if not description: + raise ValueError("SKILL.md frontmatter missing required field: description") + return name, description, content🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/skill-creator/scripts/utils.py around lines 24 - 47, The parser currently returns empty strings when required frontmatter keys are missing; after the frontmatter loop in the function that computes frontmatter_lines, name, description and content, validate that name and description are non-empty and if either is missing raise a ValueError with a clear message (e.g. include which key is missing). Modify the section after the while loop that produces name and description so it checks the variables name and description and raises ValueError instead of returning empty strings; reference the variables name, description, frontmatter_lines and the return at the end to locate where to add the validation..claude/skills/skill-creator/eval-viewer/viewer.html-1063-1066 (1)
1063-1066:⚠️ Potential issue | 🟠 Major완료 상태를 바로
in_progress로 덮어쓰고 있습니다.
showDoneDialog()에서status: "complete"를 전송한 뒤, 사용자가 OK를 누르면 Line 1065의saveCurrentFeedback()가 다시in_progress를 보냅니다. 최신 상태만 보는 소비자에서는 리뷰가 영원히 완료되지 않습니다.수정 예시
function closeDoneDialog() { - // Reset status back to in_progress - saveCurrentFeedback(); document.getElementById("done-overlay").classList.remove("visible"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/skill-creator/eval-viewer/viewer.html around lines 1063 - 1066, closeDoneDialog currently calls saveCurrentFeedback() which unconditionally resets the review status to "in_progress", overwriting the "complete" sent by showDoneDialog; change this so closing the dialog does not revert status: either update saveCurrentFeedback(status?) to accept an explicit status or a preserveStatus flag and call saveCurrentFeedback("complete") or saveCurrentFeedback({preserveStatus: true}) from closeDoneDialog, or remove the call from closeDoneDialog and only persist feedback when an explicit status change happens — reference functions closeDoneDialog, showDoneDialog, and saveCurrentFeedback when making the change..claude/skills/skill-creator/eval-viewer/viewer.html-1012-1022 (1)
1012-1022:⚠️ Potential issue | 🟠 Major
fetch응답의 HTTP 상태 코드를 확인하지 않습니다.
fetch는 네트워크 오류가 아니면 reject되지 않습니다. 현재 코드는response.ok를 확인하지 않아, 서버가 4xx/5xx를 반환해도.then()핸들러가 실행됩니다. 따라서 auto-save는"Saved"메시지를 표시하고 final submit은 완료 오버레이를 보여주지만, 실제로는 저장이 실패합니다.응답 상태를 검증하도록 수정이 필요합니다:
수정 예시
- }).then(() => { + }).then((resp) => { + if (!resp.ok) throw new Error(`HTTP ${resp.status}`); document.getElementById("feedback-status").textContent = "Saved";🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/skill-creator/eval-viewer/viewer.html around lines 1012 - 1022, The fetch auto-save currently treats any HTTP response as success; update the fetch("/api/feedback", { ... }) promise chain to inspect the Response object (check response.ok) inside the then handler and throw or reject when !response.ok so failures move to the catch path; on success set document.getElementById("feedback-status").textContent = "Saved", and on error (either network error or non-2xx) set the same element to "Will download on submit" and optionally log the response.status/response.statusText for debugging. Ensure the element id "feedback-status" and the POST body ({ reviews, status: "in_progress" }) remain unchanged..claude/skills/skill-creator/eval-viewer/generate_review.py-288-307 (1)
288-307:⚠️ Potential issue | 🟠 Major포트 점유 프로세스를 자동 종료하는 로직은 너무 공격적입니다.
이 도구는 단순히 로컬 리뷰 UI를 띄우는 용도인데, 지금 방식은 같은 포트를 쓰는 다른 서비스를 조용히
SIGTERM할 수 있습니다. 바로 아래에서 이미 bind 실패 시 임시 포트로 fallback 하므로, 강제 종료 대신 포트 충돌을 안내하거나 fallback만 사용하는 편이 안전합니다.Also applies to: 438-446
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/skill-creator/eval-viewer/generate_review.py around lines 288 - 307, The _kill_port function is too aggressive—do not SIGTERM other processes; instead change _kill_port to only detect/report that a port is in use (e.g., return a boolean or raise a non-fatal exception) and log a clear warning (use sys.stderr or processLogger) without killing PIDs, and update its callers (the code path that performs bind with fallback) to rely on the existing temporary-port fallback or surface a user-facing message when the port is occupied; remove any calls that send signal.SIGTERM and replace them with checks that log the conflict so the bind/fallback behavior handles the resolution..claude/skills/skill-creator/scripts/aggregate_benchmark.py-149-154 (1)
149-154:⚠️ Potential issue | 🟠 Major
output_chars를tokens대체값으로 쓰면 벤치마크 수치가 왜곡됩니다.여기서
execution_metrics.output_chars는 토큰 수가 아니라 출력 문자 수입니다.timing.json이 없을 때 이 값을tokens로 넣으면 평균/델타가 서로 다른 단위로 집계되어 결과가 의미 없어집니다. 토큰이 없으면 비워 두거나, 실제 토큰 필드를 별도로 읽어와야 합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/skill-creator/scripts/aggregate_benchmark.py around lines 149 - 154, The code currently uses execution_metrics.output_chars as a fallback for result["tokens"], which mixes characters with token counts and skews benchmarks; change the logic in aggregate_benchmark.py where grading is read so that you do NOT assign metrics.get("output_chars") to result["tokens"]; instead leave result["tokens"] unset/None when a true token count is missing, or attempt to read a real token field from metrics (e.g., "tokens", "output_tokens", "token_count") and only assign if present; update the block handling grading/get("execution_metrics") and result["tokens"] to reflect this so character counts are not treated as tokens..claude/skills/skill-creator/scripts/run_loop.py-24-44 (1)
24-44:⚠️ Potential issue | 🟠 Major소규모 eval 세트에서는 train 셋이 비어 곧바로 성공으로 종료됩니다.
max(1, ...)때문에 positive/negative가 각각 1개뿐이면 둘 다 test로 빠집니다. 그러면 아래 루프에서train_total == 0인데도train_failed == 0으로 간주되어 첫 iteration에all_passed로 끝납니다. 최소 한 샘플은 train에 남기도록 분할 크기를len(group) - 1로 상한 처리해야 합니다.🔧 제안 수정
- n_trigger_test = max(1, int(len(trigger) * holdout)) - n_no_trigger_test = max(1, int(len(no_trigger) * holdout)) + def test_size(group: list[dict]) -> int: + if len(group) <= 1: + return 0 + return min(len(group) - 1, max(1, int(len(group) * holdout))) + + n_trigger_test = test_size(trigger) + n_no_trigger_test = test_size(no_trigger)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/skill-creator/scripts/run_loop.py around lines 24 - 44, The split_eval_set function currently forces at least one test sample per class via max(1, ...), which can leave train empty for small groups; update the calculation of n_trigger_test and n_no_trigger_test to cap the test count at len(group)-1 so at least one sample remains in train (handle group sizes 0 or 1 by using max(0, len(group)-1) as the upper bound). Specifically, in split_eval_set adjust the test-size computation for the "trigger" and "no_trigger" lists so it becomes min(max(1, int(len(group) * holdout)), max(0, len(group) - 1)) (or equivalent) to ensure train_set is never empty when possible..claude/skills/skill-creator/eval-viewer/generate_review.py-279-281 (1)
279-281:⚠️ Potential issue | 🟠 Major임베드 JSON을 그대로
<script>에 넣으면 로컬 리뷰어가 스크립트 인젝션에 취약합니다.
runs/previous_feedback/출력 파일 내용은 임의 텍스트인데,json.dumps()결과를 스크립트 본문에 바로 삽입하면</script>가 포함된 데이터만으로 태그가 닫히고 뒤의 내용이 실행될 수 있습니다. 최소한</>/&는 이스케이프해서 넣어야 합니다.🔧 제안 수정
- data_json = json.dumps(embedded) + data_json = ( + json.dumps(embedded) + .replace("<", "\\u003c") + .replace(">", "\\u003e") + .replace("&", "\\u0026") + ) return template.replace("/*__EMBEDDED_DATA__*/", f"const EMBEDDED_DATA = {data_json};")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/skill-creator/eval-viewer/generate_review.py around lines 279 - 281, The code currently inserts data_json directly into the script via template.replace which allows script injection if fields contain "</script>" or raw "<", ">" or "&". After creating data_json (the json.dumps(embedded) result), produce a safe_json by escaping dangerous chars (e.g. data_json.replace('<', '\\u003c').replace('>', '\\u003e').replace('&', '\\u0026')) and then use that safe_json in the template.replace that injects the EMBEDDED_DATA; update the replacement call that writes EMBEDDED_DATA to use safe_json instead of data_json..claude/skills/skill-creator/scripts/run_eval.py-133-168 (1)
133-168:⚠️ Potential issue | 🟠 Major첫 번째 비관련 tool 호출에서 바로
False를 반환하면 trigger 탐지가 누락됩니다.스트리밍 경로와
assistantfallback 둘 다Skill/Read가 아닌 첫tool_use를 본 순간 바로 종료합니다. 그러면 같은 응답 안에서 뒤이어 오는 실제Skill/Read호출은 검사되지 않아 false negative가 생깁니다. 비관련 tool은 무시하고message_stop/result까지 계속 스캔해야 합니다.🔧 제안 수정
if se_type == "content_block_start": cb = se.get("content_block", {}) if cb.get("type") == "tool_use": tool_name = cb.get("name", "") if tool_name in ("Skill", "Read"): pending_tool_name = tool_name accumulated_json = "" - else: - return False + else: + pending_tool_name = None + accumulated_json = "" + continue ... elif event.get("type") == "assistant": message = event.get("message", {}) for content_item in message.get("content", []): if content_item.get("type") != "tool_use": continue tool_name = content_item.get("name", "") tool_input = content_item.get("input", {}) if tool_name == "Skill" and clean_name in tool_input.get("skill", ""): - triggered = True + return True elif tool_name == "Read" and clean_name in tool_input.get("file_path", ""): - triggered = True - return triggered + return True🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/skill-creator/scripts/run_eval.py around lines 133 - 168, The streaming and fallback logic prematurely returns False on seeing the first non-Skill/Read tool_use, causing later Skill/Read calls in the same response to be missed; update the content_block_start branch (variables: se_type, cb, tool_name, pending_tool_name, accumulated_json) to not return False for unrelated tools but instead skip/ignore them and continue scanning until message_stop/result, and update the assistant fallback loop (variables: event, message, content_item, tool_name, tool_input, triggered) to avoid returning inside the for-loop—accumulate/set triggered when Skill/Read is found and only return triggered after finishing scanning all content items (or after message_end) so later tool_use entries are checked.
🟡 Minor comments (19)
.claude/agents/api-documenter.md-16-24 (1)
16-24:⚠️ Potential issue | 🟡 Minor체크리스트 항목의 문법 오류 수정 필요
Line 24의 "Versioning clear consistently"는 문법적으로 올바르지 않습니다. 다른 체크리스트 항목들과의 일관성을 위해 "Versioning documented clearly" 또는 유사한 형태로 수정해야 합니다.
📝 문법 수정 제안
- Authentication documented clearly - Try-it-out functionality enabled - Multi-language examples provided -- Versioning clear consistently +- Versioning documented clearly🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/agents/api-documenter.md around lines 16 - 24, Replace the grammatically incorrect checklist item "Versioning clear consistently" with a consistent, parallel phrasing such as "Versioning documented clearly" (or "Versioning documented consistently") in the API documentation checklist; ensure the updated item matches the tone and structure of the other items like "Authentication documented clearly" and "Error documentation comprehensive"..claude/skills/incident-response/references/phase-guide.md-16-20 (1)
16-20:⚠️ Potential issue | 🟡 MinorGrafana Alert 상태 해석에
error상태 누락SKILL.md Line 39에서
states: ["firing", "error", "pending"]을 조회하지만, 이 가이드에는error상태의 해석이 없습니다.error상태는 쿼리 실행 오류를 의미하므로 처리 방법이 필요합니다.error 상태 해석 추가
### Grafana Alert 상태 해석 - `Alerting (NoData)`: 쿼리 결과가 없음 → 쿼리 레이블 오류이거나 스크레이핑 중단 - `Alerting`: 실제 조건 충족 → 진짜 장애 - `Pending`: 조건 충족 중이나 `for` 기간 미달 → 주시 필요 +- `Error`: Alert 쿼리 실행 실패 → Grafana 설정 오류, datasource 연결 끊김, 잘못된 쿼리 구문
Error상태 발견 시 장애 분석보다 Alert 설정 자체를 먼저 점검하도록 안내하세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/incident-response/references/phase-guide.md around lines 16 - 20, The guide is missing an interpretation for Grafana Alert "error" even though the code checks states: ["firing", "error", "pending"]; update the Grafana Alert 상태 해석 section to add a short entry explaining that `Error` means the query execution failed (e.g., syntax, datasource, or scraping error) and instruct readers to first verify alert query/configuration and datasource/scraping health before treating it as an incident; reference the existing bullets (Alerting (NoData), Alerting, Pending) and add a fourth bullet for `Error` with the recommended troubleshooting steps..claude/skills/incident-response/SKILL.md-64-74 (1)
64-74:⚠️ Potential issue | 🟡 Minor에러 코드 매핑이 CLAUDE.md와 일관성 유지 필요
CLAUDE.md(Context snippet 3)에 정의된 에러 코드 접두사는
C, S, T, U, M, ST, W, I, B, SE, F이지만, 이 테이블에는M(메일)과I(이미지) 매핑이 누락되었습니다.📋 누락된 매핑 추가
| 에러 접두사 | 도메인 경로 | |------------|------------| | ST (학습) | `study/` | | SE (시즌) | `rank/` | | U (사용자) | `user/` | | T (토큰) | `token/` | | F (FCM) | `fcm/` | | B (게시판) | `board/` | | W (WiFi) | `wifi/` | +| M (메일) | `user/` (메일 인증) | +| I (이미지) | `image/` 또는 `global/` | | C, S (공통)| `global/` |CLAUDE.md와 동기화 유지를 위해 향후 에러 코드 추가 시 두 파일을 함께 업데이트하세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/incident-response/SKILL.md around lines 64 - 74, The error code → domain mapping table in SKILL.md is missing the M (mail) and I (image) prefixes present in CLAUDE.md; update the table used in the "에러 코드 → 도메인 매핑" section to include rows for M → `mail/` and I → `image/` (keeping the same format as other rows) and ensure the set of prefixes (C, S, T, U, M, ST, W, I, B, SE, F) matches CLAUDE.md so both files stay synchronized when new prefixes are added..claude/skills/incident-response/references/grafana-queries.md-35-44 (1)
35-44:⚠️ Potential issue | 🟡 Minor응답 시간 쿼리에 잠재적인 정확도 문제
Line 37의
by (le, uri)그룹화는 URI별 p99를 계산하지만, Line 42의 p95 쿼리는by (le)만 사용하여 전체 집계를 수행합니다. 의도적인 차이가 아니라면 불일치입니다.🔍 일관성 확보를 위한 수정 제안
p95도 URI별로 분석하려면:
# p95 응답시간 histogram_quantile(0.95, - sum(rate(http_server_requests_seconds_bucket{job="geumpumta-backend-prod"}[5m])) by (le) + sum(rate(http_server_requests_seconds_bucket{job="geumpumta-backend-prod"}[5m])) by (le, uri) )p99도 전체 집계로 통일하려면:
# p99 응답시간 (초) histogram_quantile(0.99, - sum(rate(http_server_requests_seconds_bucket{job="geumpumta-backend-prod"}[5m])) by (le, uri) + sum(rate(http_server_requests_seconds_bucket{job="geumpumta-backend-prod"}[5m])) by (le) )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/incident-response/references/grafana-queries.md around lines 35 - 44, The two Prometheus queries are inconsistent: the p99 query uses sum(... ) by (le, uri) to compute URI-level p99 while the p95 query uses sum(... ) by (le) for overall aggregation; decide on the intended dimension and make them consistent — either change the p95 expression to sum(rate(http_server_requests_seconds_bucket{job="geumpumta-backend-prod"}[5m])) by (le, uri) to compute p95 per URI (matching the p99’s URI granularity) or change the p99 expression to sum(... ) by (le) to compute both p95 and p99 as global aggregates; update the corresponding histogram_quantile calls (histogram_quantile(0.95, ...) and histogram_quantile(0.99, ...)) accordingly so both use the same grouping key..claude/skills/incident-response/references/phase-guide.md-10-14 (1)
10-14:⚠️ Potential issue | 🟡 Minor심각도 판단 기준의 모호한 경계
Critical과 High 사이의 5xx 에러율 경계(10%)가 명확하지만, High와 Medium의 경계(3%)는 Line 13에만 표현되고 Line 14에는 "< 3%"로 표현되어 정확히 3%일 때의 분류가 모호합니다.
경계값 명확화
| 심각도 | 기준 | |--------|------| -| Critical | prod 인스턴스 다운 / 5xx 에러율 > 10% / 전면 서비스 불가 | -| High | 특정 기능 불가 / 5xx 에러율 3~10% / 응답시간 p99 > 5초 | -| Medium | 일부 기능 저하 / 에러율 < 3% / 응답시간 p99 3~5초 | +| Critical | prod 인스턴스 다운 / 5xx 에러율 ≥ 10% / 전면 서비스 불가 | +| High | 특정 기능 불가 / 5xx 에러율 3~10% (미만) / 응답시간 p99 > 5초 | +| Medium | 일부 기능 저하 / 5xx 에러율 < 3% / 응답시간 p99 3~5초 |또는 오버랩을 제거하여
10% 이상,3% 이상 10% 미만,3% 미만으로 표현하세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/incident-response/references/phase-guide.md around lines 10 - 14, The severity table has an ambiguous boundary between High and Medium for 5xx error rate (Line 13 shows "3~10%" while Line 14 shows "< 3%"), so update the table to use non-overlapping, explicit ranges such as "10% 이상", "3% 이상 10% 미만", and "3% 미만" (or ">=10%", ">=3% and <10%", "<3%") to remove ambiguity; modify the relevant table rows (the "High" and "Medium" rows) so the 5xx error-rate criteria are written consistently and unambiguously..claude/skills/incident-response/references/grafana-queries.md-68-87 (1)
68-87:⚠️ Potential issue | 🟡 MinorRedis 모니터링 쿼리의 job 레이블을 표준 명명 규칙에 맞게 수정 필요
Prometheus의 표준 job naming convention에 따르면 Redis exporter의 job 이름은
redis_exporter입니다. 현재 쿼리의up{job="redis"}레이블은 표준 규칙과 일치하지 않아 데이터를 반환하지 못할 가능성이 높습니다. MySQL의up{job="mysql"}은 표준 규칙과 일치합니다.실제 환경의 Prometheus 설정에서 job 이름을 확인한 후, 다음과 같이 수정하세요:
- Redis:
up{job="redis_exporter"}(또는 실제 환경의 job 이름으로 변경)- Redis 메모리 쿼리도 해당 job 이름에 맞게 필터링 추가
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/incident-response/references/grafana-queries.md around lines 68 - 87, The Redis queries use a non-standard job label (up{job="redis"}) and must be updated to the exporter job name used in Prometheus (e.g., up{job="redis_exporter"}); update the up{job="redis"} expression and also add the same job filter to redis_memory_used_bytes (or replace with redis_memory_used_bytes{job="redis_exporter"}) so both queries use the actual Redis exporter job name from your Prometheus config; verify the real job name in your environment and use that string consistently..claude/skills/incident-response/references/templates.md-67-68 (1)
67-68:⚠️ Potential issue | 🟡 MinorGradle 테스트 명령어의 중괄호 표기법 제거 필요
./gradlew test --tests "{테스트클래스명}"의 중괄호는 Gradle 공식 문법이 아닙니다. Gradle의--tests옵션은 패턴 문법을 사용하며, 따옴표는 공백이나 특수문자가 포함된 패턴에만 필요합니다.수정 제안
-- [ ] 단위 테스트 통과 (`./gradlew test --tests "{테스트클래스명}"`) +- [ ] 단위 테스트 통과 (`./gradlew test --tests MyServiceTest` 또는 `./gradlew test --tests "*ServiceTest"`)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/incident-response/references/templates.md around lines 67 - 68, 교체할 텍스트 './gradlew test --tests "{테스트클래스명}"'에서 Gradle의 패턴 문법과 중괄호 사용이 잘못되었으므로 중괄호를 제거하고 필요 시에만 따옴표를 사용하도록 수정하세요; 예를 들어 './gradlew test --tests 테스트클래스명' 또는 공백/특수문자가 포함된 패턴일 때 './gradlew test --tests "com.example.MyTest"'처럼 바꾸고, 해당 변경을 체크리스트 항목(현재 문자열이 있는 줄)에서 반영하십시오.src/main/java/com/gpt/geumpumtabackend/rank/CLAUDE.md-84-84 (1)
84-84:⚠️ Potential issue | 🟡 Minor테이블 전후에 빈 줄을 넣어 주세요.
Line 84와 Line 117 주변은 markdownlint MD058 경고가 납니다. 표 앞뒤 공백 줄만 추가해도 린트가 정리됩니다.
Also applies to: 117-117
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/gpt/geumpumtabackend/rank/CLAUDE.md` at line 84, Add a blank line before and after the Markdown table(s) containing the row "| Cron | 작업 |" (and the similar table around the other occurrence at line ~117) to satisfy markdownlint MD058; open the file CLAUDE.md, locate the table rows that match "| Cron | 작업 |" and ensure there is an empty line both immediately above and immediately below each table.src/main/java/com/gpt/geumpumtabackend/fcm/CLAUDE.md-112-115 (1)
112-115:⚠️ Potential issue | 🟡 Minor문서가 제거된 구현명을 아직 가리키고 있습니다.
Line 114의
isPermanentError()는 현재FcmMessageSender에 없는 메서드입니다. 실제 구현처럼NON_RETRYABLE_ERROR_CODES와 해당 분기 로직을 가리키도록 수정해 두는 편이 안전합니다.📝 제안
-3. 재시도 불가 에러 추가 시 `FcmMessageSender.send()`의 `isPermanentError()` 분기에 추가 +3. 재시도 불가 에러 추가 시 `FcmMessageSender`의 `NON_RETRYABLE_ERROR_CODES`와 해당 처리 분기를 함께 업데이트🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/gpt/geumpumtabackend/fcm/CLAUDE.md` around lines 112 - 115, The doc references a non-existent method isPermanentError(); update the wording to point to the actual retry logic: instruct callers to use the NON_RETRYABLE_ERROR_CODES constant and the retry branching inside FcmMessageSender.send() instead of isPermanentError(), and mention adding new non-retryable codes to NON_RETRYABLE_ERROR_CODES when introducing permanent errors; reference FcmService for adding new per-notification methods (e.g., sendMaxFocusNotification) and ResponseUtil.createSuccessResponse() for response format.CLAUDE.md-70-73 (1)
70-73:⚠️ Potential issue | 🟡 Minor최대 공부시간 스케줄 설명이 구현과 다릅니다.
여기는 10분 크론으로 적혀 있지만,
src/main/java/com/gpt/geumpumtabackend/study/scheduler/MaxFocusStudyScheduler.javaLine 22는@Scheduled(fixedRate = 1000)입니다. 운영 기준 문서가 이렇게 어긋나면 점검 주기를 완전히 다르게 이해하게 됩니다.📝 제안
- 최대 공부시간 `0 */10 * * * *` — 3시간 초과 세션 자동 종료 + FCM + 최대 공부시간 `매초 실행 (`@Scheduled`(fixedRate = 1000))` — 3시간 초과 세션 자동 종료 + FCM🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@CLAUDE.md` around lines 70 - 73, The documentation for "최대 공부시간" states a 10-minute cron but the implementation in MaxFocusStudyScheduler (class MaxFocusStudyScheduler, currently annotated with `@Scheduled`(fixedRate = 1000)) runs every second; update either the code or the doc to match: change the `@Scheduled` annotation in MaxFocusStudyScheduler to the intended 10-minute schedule (e.g., `@Scheduled`(cron = "0 */10 * * * *") or `@Scheduled`(fixedRate = 600_000)) or alternatively update the README entry to reflect the actual fixedRate of 1000 if that behavior is intended.src/main/java/com/gpt/geumpumtabackend/rank/CLAUDE.md-20-20 (1)
20-20:⚠️ Potential issue | 🟡 Minor코드 펜스 언어를 지정해 주세요.
Line 20과 Line 67의 fenced block에 언어가 없어 markdownlint MD040가 발생합니다. 디렉터리 트리와 예시는
text지정이면 충분합니다.Also applies to: 67-67
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/gpt/geumpumtabackend/rank/CLAUDE.md` at line 20, CLAUDE.md의 두 fenced code block(디렉터리 트리와 예시)에 언어가 빠져 markdownlint MD040이 발생하니, 각각의 ``` 를 ```text 로 변경하여 코드 펜스 언어를 지정하세요; 파일 내 디렉터리 트리 블록과 예시 블록(현재 언어 미지정된 fenced blocks)을 찾아 모두 `text` 언어 태그를 추가하면 됩니다..claude/skills/spring-review/SKILL.md-130-156 (1)
130-156:⚠️ Potential issue | 🟡 Minor리뷰 출력 예시 fence가 중간에 끊깁니다.
Lines 132-151에서 바깥 예시를 triple-backtick으로 열어 둔 상태에서 내부에 또 triple-backtick
javafence를 넣고 있어, Line 141에서 바깥 fence가 조기 종료됩니다. 바깥 블록은 네 개 backtick +md/text로 감싸고, Line 154의 단독 예시도 언어 태그를 달아 주세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/spring-review/SKILL.md around lines 130 - 156, The outer example block under "## 리뷰 출력 형식" uses triple-backticks and contains an inner ```java fence which prematurely closes the outer block; change the outer fence to four backticks and add a language tag (e.g., "md" or "text") so the inner ```java remains valid, and also add an explicit language tag to the standalone example at the end of that block (the single-line example starting with "**총평**: 아키텍처...") so all fences are properly nested and rendered..claude/skills/spring-core/references/exceptions.md-49-60 (1)
49-60:⚠️ Potential issue | 🟡 Minor텍스트 다이어그램 블록에 언어 태그를 붙여 주세요.
Line 51의 fenced block은
text같은 info string이 없어 markdownlint MD040 경고가 납니다. 문서 lint를 깨끗하게 유지하려면 여기서 명시해 두는 편이 좋습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/spring-core/references/exceptions.md around lines 49 - 60, The fenced code block showing the exception flow (the block containing "BusinessException(ExceptionType)" and "GlobalExceptionHandler (`@RestControllerAdvice`)") lacks an info string and triggers markdownlint MD040; update the opening fence to include a language tag such as "text" (e.g., change ``` to ```text) so the block is explicitly tagged and the lint warning is resolved..claude/skills/skill-creator/scripts/generate_report.py-205-209 (1)
205-209:⚠️ Potential issue | 🟡 Minor빈 history에서 max() 호출 시 ValueError 발생 가능
history가 빈 리스트인 경우max(history, ...)호출 시ValueError: max() arg is an empty sequence예외가 발생합니다.🐛 수정 제안
# Find best iteration for highlighting + best_iter = None + if history: - if test_queries: - best_iter = max(history, key=lambda h: h.get("test_passed") or 0).get("iteration") - else: - best_iter = max(history, key=lambda h: h.get("train_passed", h.get("passed", 0))).get("iteration") + if test_queries: + best_iter = max(history, key=lambda h: h.get("test_passed") or 0).get("iteration") + else: + best_iter = max(history, key=lambda h: h.get("train_passed", h.get("passed", 0))).get("iteration")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/skill-creator/scripts/generate_report.py around lines 205 - 209, Guard against an empty history before calling max(): compute the best entry with entry = max(history, key=..., default=None) in both branches (when test_queries is truthy and falsy) and then set best_iter = entry.get("iteration") if entry is not None else None; update the code that uses best_iter accordingly. Reference variables: history, test_queries, best_iter..claude/skills/spring-core/references/templates.md-213-214 (1)
213-214:⚠️ Potential issue | 🟡 Minor
GlobalPageResponse.from()→GlobalPageResponse.create()메서드명 불일치템플릿에서
GlobalPageResponse.from()을 사용하고 있지만, 실제GlobalPageResponse클래스는create()메서드를 제공합니다. 이대로 사용 시 컴파일 에러가 발생합니다.🐛 수정 제안
// Service Page<Example> examples = exampleRepository.findAll(PageRequest.of(page, size)); -return GlobalPageResponse.from(examples.map(ExampleResponse::from)); +return GlobalPageResponse.create(examples.map(ExampleResponse::from));🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/spring-core/references/templates.md around lines 213 - 214, The template calls GlobalPageResponse.from(...) but the class exposes GlobalPageResponse.create(...), so update the call site (where exampleRepository.findAll(PageRequest.of(page, size)) is assigned to examples and then passed into GlobalPageResponse) to use GlobalPageResponse.create(...) instead of from; ensure you still pass the examples.map(ExampleResponse::from) result into create and leave ExampleResponse::from unchanged unless its API differs..claude/skills/skill-creator/scripts/quick_validate.py-52-63 (1)
52-63:⚠️ Potential issue | 🟡 Minor빈
name값에 대한 검증 누락
name필드가 존재하지만 빈 문자열("")이거나 공백만 있는 경우(" "), 현재 로직은 이를 유효한 것으로 처리합니다. Line 53에서 키 존재만 확인하고, line 63에서if name:이 falsy일 때 검증을 건너뛰기 때문입니다.🐛 수정 제안
# Check required fields if 'name' not in frontmatter: return False, "Missing 'name' in frontmatter" if 'description' not in frontmatter: return False, "Missing 'description' in frontmatter" # Extract name for validation name = frontmatter.get('name', '') if not isinstance(name, str): return False, f"Name must be a string, got {type(name).__name__}" name = name.strip() - if name: + if not name: + return False, "Name cannot be empty" + else: # Check naming convention (kebab-case: lowercase with hyphens)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/skill-creator/scripts/quick_validate.py around lines 52 - 63, The current validation only checks for the presence of 'name' in frontmatter but allows empty or whitespace-only values; update the validation in quick_validate.py around the name extraction (the variable name and surrounding checks) to treat empty or whitespace-only strings as invalid by trimming the string (name = name.strip()) and returning False with a clear message like "Missing or empty 'name' in frontmatter" when name == "". Ensure this check executes before any further name-specific validation logic..claude/skills/skill-creator/references/schemas.md-90-149 (1)
90-149:⚠️ Potential issue | 🟡 Minor예시 JSON의 수치가 서로 맞지 않습니다.
grading.json예시는expectations가 2개인데summary.total이 3이고,timing.json예시는duration_ms/total_duration_seconds가 아래의 executor+grader duration과 맞지 않습니다. 이 파일이 아티팩트 계약 문서 역할을 하므로, 샘플 값이 어긋나면 구현이나 검증 코드가 잘못 따라가기 쉽습니다.Also applies to: 203-214
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/skill-creator/references/schemas.md around lines 90 - 149, The sample JSON has inconsistent aggregate fields: ensure "summary.total" equals the actual count of items in "expectations" and that "summary.passed"/"summary.failed" match the booleans (and recompute "pass_rate"), ensure "timing.total_duration_seconds" equals the sum of "timing.executor_duration_seconds" and "timing.grader_duration_seconds", and ensure "execution_metrics.total_tool_calls" equals the sum of the entries under "execution_metrics.tool_calls"; update the blocks containing "expectations", "summary", "timing" and "execution_metrics" (look for those exact keys) to have consistent derived values, and apply the same fixes to the other example block referenced (lines ~203-214)..claude/skills/skill-creator/scripts/aggregate_benchmark.py-263-272 (1)
263-272:⚠️ Potential issue | 🟡 Minor실제 반복 횟수 대신
runs_per_configuration을 3으로 고정하면 메타데이터가 틀어집니다.이 값은 벤치마크 디렉터리에 실제로 존재하는 run 수를 반영해야 하는데, 지금은 항상 3으로 기록됩니다. 반복 횟수를 바꾸거나 일부 run이 빠진 경우
benchmark.json과benchmark.md가 잘못된 실험 규모를 보여주게 됩니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/skill-creator/scripts/aggregate_benchmark.py around lines 263 - 272, The benchmark metadata currently hardcodes "runs_per_configuration": 3; instead compute the actual number of runs and set that value dynamically before constructing the benchmark dict. Locate where the benchmark dict is assembled (the variable named benchmark and its "runs_per_configuration" key) and replace the hardcoded 3 with a computed count derived from the real run artifacts (e.g., count of run subdirectories/files in the benchmark directory or the length of the list of runs for each configuration, using eval_ids or the directory listing used elsewhere in this script) so the JSON/MD reflect the true experiment size..claude/skills/skill-creator/scripts/improve_description.py-163-183 (1)
163-183:⚠️ Potential issue | 🟡 Minor1024자 제한이 실제로는 보장되지 않습니다.
재작성 결과
shortened도 1024자를 넘을 수 있는데, 여기서는 다시 검증하지 않고 그대로 반환합니다. 이 함수가 “하드 리밋”용 safety net이라면 마지막에 강제 클램프나 예외가 필요합니다.🔧 제안 수정
shorten_text = _call_claude(shorten_prompt, model) match = re.search(r"<new_description>(.*?)</new_description>", shorten_text, re.DOTALL) shortened = match.group(1).strip().strip('"') if match else shorten_text.strip().strip('"') transcript["rewrite_prompt"] = shorten_prompt transcript["rewrite_response"] = shorten_text - transcript["rewrite_description"] = shortened - transcript["rewrite_char_count"] = len(shortened) - description = shortened + if len(shortened) > 1024: + shortened = shortened[:1024].rstrip() + transcript["rewrite_description"] = shortened + transcript["rewrite_char_count"] = len(shortened) + description = shortened🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.claude/skills/skill-creator/scripts/improve_description.py around lines 163 - 183, The re-write step for long descriptions can still produce a string longer than the 1024-char hard limit (variables: shortened, description, transcript, _call_claude, shorten_prompt), so after extracting shortened you must verify and enforce the 1024 limit: check len(shortened) and if it exceeds 1024 either (a) truncate safely (e.g., cut to 1024 preserving word boundaries) and update transcript["rewrite_description"] and transcript["rewrite_char_count"], or (b) raise a clear exception so callers can retry; implement the chosen enforcement immediately after the shortened assignment and before assigning description = shortened so the function never returns a >1024-character description.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cbb9407e-a2be-4465-933e-21b231085c92
📒 Files selected for processing (46)
.ai/ARCHITECTURE.md.ai/TESTING.md.ai/USE-CASES.md.claude/agents/api-documenter.md.claude/agents/backend-architect.md.claude/agents/code-reviewer.md.claude/agents/test-engineer.md.claude/skills/incident-response/SKILL.md.claude/skills/incident-response/references/grafana-queries.md.claude/skills/incident-response/references/phase-guide.md.claude/skills/incident-response/references/templates.md.claude/skills/skill-creator/SKILL.md.claude/skills/skill-creator/agents/analyzer.md.claude/skills/skill-creator/agents/comparator.md.claude/skills/skill-creator/agents/grader.md.claude/skills/skill-creator/assets/eval_review.html.claude/skills/skill-creator/eval-viewer/generate_review.py.claude/skills/skill-creator/eval-viewer/viewer.html.claude/skills/skill-creator/references/schemas.md.claude/skills/skill-creator/scripts/__init__.py.claude/skills/skill-creator/scripts/aggregate_benchmark.py.claude/skills/skill-creator/scripts/generate_report.py.claude/skills/skill-creator/scripts/improve_description.py.claude/skills/skill-creator/scripts/package_skill.py.claude/skills/skill-creator/scripts/quick_validate.py.claude/skills/skill-creator/scripts/run_eval.py.claude/skills/skill-creator/scripts/run_loop.py.claude/skills/skill-creator/scripts/utils.py.claude/skills/spring-core/SKILL.md.claude/skills/spring-core/references/exceptions.md.claude/skills/spring-core/references/templates.md.claude/skills/spring-review/SKILL.md.claude/skills/spring-test/SKILL.md.claude/skills/spring-test/evals/evals.json.mcp.jsonCLAUDE.mdsrc/main/java/com/gpt/geumpumtabackend/board/dto/BoardListResponse.javasrc/main/java/com/gpt/geumpumtabackend/fcm/CLAUDE.mdsrc/main/java/com/gpt/geumpumtabackend/fcm/exception/PermanentFcmException.javasrc/main/java/com/gpt/geumpumtabackend/fcm/service/FcmMessageSender.javasrc/main/java/com/gpt/geumpumtabackend/fcm/service/FcmService.javasrc/main/java/com/gpt/geumpumtabackend/rank/CLAUDE.mdsrc/main/java/com/gpt/geumpumtabackend/rank/dto/response/SeasonDepartmentRankingResponse.javasrc/main/java/com/gpt/geumpumtabackend/study/domain/StudySession.javasrc/main/java/com/gpt/geumpumtabackend/study/scheduler/MaxFocusStudyScheduler.javasrc/main/java/com/gpt/geumpumtabackend/study/service/StudySessionService.java
💤 Files with no reviewable changes (6)
- src/main/java/com/gpt/geumpumtabackend/board/dto/BoardListResponse.java
- .ai/ARCHITECTURE.md
- .ai/USE-CASES.md
- src/main/java/com/gpt/geumpumtabackend/rank/dto/response/SeasonDepartmentRankingResponse.java
- src/main/java/com/gpt/geumpumtabackend/fcm/service/FcmService.java
- .ai/TESTING.md
| @Retryable( | ||
| retryFor = FirebaseMessagingException.class, | ||
| noRetryFor = PermanentFcmException.class, |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
Spring Retry 에서 @retryable(retryFor = FirebaseMessagingException.class, noRetryFor = PermanentFcmException.class) 메서드가 RuntimeException 래퍼를 던지고 @recover(FirebaseMessagingException, ...)만 있을 때, @recover가 실행되는지 확인해 주세요.
💡 Result:
아니요, @Recover(FirebaseMessagingException, ...) 메서드는 실행되지 않습니다. Spring Retry에서 @Retryable(retryFor = FirebaseMessagingException.class, noRetryFor = PermanentFcmException.class) 메서드가 RuntimeException 래퍼를 던질 때: 1. PermanentFcmException이 FirebaseMessagingException의 서브클래스이고 noRetryFor에 지정되어 있으면, 해당 예외는 재시도 대상에서 제외됩니다. BinaryExceptionClassifier (SubclassClassifier 기반)가 예외 타입과 서브클래스를 계층적으로 매칭하여 재시도 여부를 결정합니다. noRetryFor가 매칭되면 즉시 재시도하지 않고 ExhaustedRetryException을 발생시킵니다. (출처 18, 16, 21) 2. 재시도가 발생하지 않았으므로 모든 재시도 횟수가 소진된 후가 아니며, RecoverAnnotationRecoveryHandler의 recover가 호출되지 않습니다. recover는 재시도 횟수 소진 후 (ExhaustedRetryException 발생 시) 호출되며, cause.getClass (원래 예외 타입)을 SubclassClassifier로 @Recover 메서드와 매칭합니다. (출처 29, 30) 3. RuntimeException 래퍼라도 cause가 PermanentFcmException이면 noRetryFor 매칭으로 재시도 생략, @Recover 스킵. retryFor만 매칭되는 FirebaseMessagingException (PermanentFcmException 아님)이 재시도 후 소진되면 @Recover(FirebaseMessagingException)가 실행됩니다. noRetryFor 예외는 원래 예외 그대로 상위 호출자에게 전파됩니다.
Citations:
- 1: https://docs.spring.io/spring-retry/docs/current/apidocs/org/springframework/retry/annotation/Retryable.html
- 2: https://docs.spring.io/spring-framework/reference/core/resilience.html
- 3: https://stackoverflow.com/questions/38601998/trying-to-exclude-an-exception-using-retryable-causes-exhaustedretryexception
- 4: https://docs.spring.io/spring-retry/docs/current/apidocs/org/springframework/classify/BinaryExceptionClassifier.html
- 5: https://docs.spring.io/spring-retry/docs/api/current/org/springframework/retry/annotation/Retryable.html
- 6: https://github.com/spring-projects/spring-retry
- 7: https://github.com/spring-projects/spring-retry/blob/master/src/main/java/org/springframework/retry/annotation/AnnotationAwareRetryOperationsInterceptor.java
- 8: https://www.seaxiang.com/blog/650d6b4cf61a489fa9ba231b65bd1e8f
- 9: https://docs.spring.io/spring-retry/docs/api/current/org/springframework/retry/annotation/RecoverAnnotationRecoveryHandler.html
🏁 Script executed:
find . -name "FcmMessageSender.java" -type fRepository: Geumpumta/backend
Length of output: 135
🏁 Script executed:
cat -n ./src/main/java/com/gpt/geumpumtabackend/fcm/service/FcmMessageSender.javaRepository: Geumpumta/backend
Length of output: 3338
@Recover 메서드가 PermanentFcmException을 받지 않습니다.
현재 구조에서는 PermanentFcmException이 noRetryFor로 지정되어 있어, 이 예외가 발생하면 재시도 없이 즉시 상위 호출자로 전파됩니다. @Recover 메서드(Line 64)는 재시도 횟수 소진 후에만 호출되므로, PermanentFcmException에 대한 공통 예외 처리가 동작하지 않습니다. 호출자 측에서 PermanentFcmException을 별도로 처리해야 하거나, PermanentFcmException용 @Recover 메서드를 추가해야 합니다.
또한 Line 56의 로그에서 messageDto.getToken()을 그대로 노출하고 있는데, FCM 토큰은 민감한 정보이므로 마스킹하거나 로그에서 제외하는 것이 좋습니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/gpt/geumpumtabackend/fcm/service/FcmMessageSender.java`
around lines 30 - 32, The `@Recover` method on FcmMessageSender doesn't handle
PermanentFcmException because that exception is listed in
`@Retryable`(noRetryFor=PermanentFcmException.class) and is propagated
immediately; add a new `@Recover` method that accepts PermanentFcmException (e.g.,
recover(PermanentFcmException ex, MessageDto messageDto) or similar signature
used today) to centralize handling and logging for permanent failures, or
alternatively remove PermanentFcmException from noRetryFor if you want it to
trigger retry+recover; also update logging in the send flow (references:
FcmMessageSender, `@Retryable`, `@Recover`, PermanentFcmException,
messageDto.getToken()) to avoid printing raw FCM tokens—mask or omit
messageDto.getToken() in logs (e.g., replace with truncated/masked value or
remove entirely).
🚀 1. 개요
📝 2. 주요 변경 사항
Summary by CodeRabbit
릴리스 노트
버그 수정
개선 사항