ci: CI 를 실제로 돌게 만들고 그 사이 쌓인 위반 정리 - #172
Merged
Merged
Conversation
`lint.yml` 의 트리거가 main/develop 이었다. 이 저장소의 기본 브랜치는 dev 이고 (`origin/HEAD -> origin/dev`) develop 브랜치는 존재하지 않는다. 즉 워크플로가 **한 번도 실행된 적이 없다**. 그 사이 dev 에 eslint 에러 3건, flake8 에러 4건, 의존성 취약점 13건이 그대로 쌓였다. 트리거를 dev·main 으로 고치는 것만으로는 부족해서, 돌면 바로 깨지던 것들도 같이 정리했다. - `npm run type-check` 를 호출하는데 package.json 에 해당 스크립트가 없었다 → 추가 - Go 린트를 `./backend` 에서 실행 — go.mod 가 없어 조용히 통과했고, 정작 Go 코드가 있는 `realtime/` 은 검증된 적이 없다 → working-directory 를 realtime 으로 - Go 버전이 1.22 하드코딩 (realtime/go.mod 는 1.26.3) → `go-version-file` 로 위임 - `./gradlew checkstyleMain` — build.gradle 에 checkstyle 플러그인이 없어 태스크 부재 → 제거하고 플러그인 도입 PR 에서 추가하도록 주석으로 남김 - 테스트 잡이 아예 없었다. vitest 68건 / JUnit 39파일 / pytest 321건 / go 11파일이 전부 CI 밖 → 레이어별 4개 잡으로 병렬 실행 + 프론트 build 까지 `lint.yml` → `ci.yml` 로 이름 변경(더 이상 lint 만 돌지 않는다). 브랜치 전략 문서도 실제(dev 기준)와 일치시켰다 — 이 문서가 develop 을 전제한 게 애초 원인이었다.
dev 에 eslint 에러 3건이 살아있었다 — CI 가 돌지 않아 잡히지 않았다. `useTypewriter`, `useInterviewSocket` 이 렌더 함수 본문에서 `ref.current = x` 로 최신 prop 을 ref 에 밀어넣고 있었다. React 는 렌더를 순수하게 유지해야 하는데(중단·재실행 가능성) 렌더 중 ref 를 쓰면 재실행 시 값이 어긋난다. 커밋 후 이펙트로 옮겼다. 두 훅 모두 ref 를 읽는 이펙트(타자기 인터벌 / WS 연결)보다 **먼저** 선언해서, 같은 커밋 안에서 ref 가 항상 먼저 최신화되도록 순서를 보장했다. useRef 초기값이 이미 첫 렌더의 값이므로 마운트 시점 동작도 그대로다. 동작 변경 의도 없음. eslint 0건, tsc 통과, vitest 68/68 통과.
`npm audit` 13건(high 9). semver 범위 안에서 전부 해결되어 package.json 변경 없이 lockfile 만 갱신했다. 결과: 0건. 특히 **react-router-dom 은 프로덕션 의존성**이고 권고 5건이 걸려 있었다 — `<Link>`·`useNavigate` 의 백슬래시 open redirect, RSC 에러 핸들러 XSS, deserializeErrors 임의 constructor 주입, 라우트 매칭 DoS, RSC CSRF 우회. react-router-dom 7.15.0 → 7.18.2 vite 는 dev 서버 계열(optimized deps `.map` path traversal, `server.fs.deny` 우회, WebSocket 임의 파일 읽기)로 배포물에는 영향이 없지만 개발자 로컬이 노출된다. vite 8.0.x → 8.2.1 검증: eslint 0건, tsc 통과, vitest 68/68, `npm run build` 성공.
CI 를 켜면 걸리는 것들. 미사용 import 3건과 미정의 이름 1건. `test_followup_consumer.py` 의 F821 은 실제 문제였다. `_make_req()` 가 반환형을 문자열 `"GenerateFollowupRequest"` 로 적고 그 타입을 함수 본문에서 지역 import 했는데, 모듈 최상단이 이미 같은 모듈(`ai_server.model.messages.followup`)에서 다른 심볼을 import 하고 있고 `from __future__ import annotations` 도 걸려 있다. 지역 import 와 문자열 인용이 둘 다 불필요해서 최상단 import 로 합쳤다. pytest 321건 통과 유지.
`black --check` 가 22 파일에서 실패하고 있었다. CI 에서 이 게이트를 켜기 위해 `uv run black .` 결과를 그대로 커밋한다. 포맷만 바뀌었고 로직 변경은 없다 (pytest 321건 그대로 통과). 대부분 최근 음성/TTS 작업 경로(`voice/stt/*`, `voice/tts/*`, tts_consumer, llm_logging_callback)와 그 테스트다 — 포맷 검사가 한 번도 돌지 않아 누적됐다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
변경 사항
전면 감사에서 나온 P0/P1 3건. CI 가 한 번도 실행된 적이 없다는 게 뿌리라서 그것부터 고쳤다.
1. CI 가 실행되지 않고 있었다 (P0)
.github/workflows/lint.yml의 트리거가main/develop인데, 이 저장소의 기본 브랜치는dev이고 (origin/HEAD -> origin/dev)develop브랜치는 존재하지 않는다. PR·push 어디서도 실행되지 않았다.트리거만 고쳐도 바로 깨지던 것들이 있어서 같이 정리했다:
npm run type-check호출 — package.json 에 스크립트 없음"type-check": "tsc -b"추가./backend에서 실행 (go.mod 없음 → 조용히 통과). 정작realtime/은 미검증realtime으로realtime/go.mod는 1.26.3)go-version-file: realtime/go.mod./gradlew checkstyleMain— build.gradle 에 플러그인 없어 태스크 부재build까지lint.yml→ci.yml로 이름 변경(더 이상 lint 만 돌지 않음).docs/git-conventions.md의 브랜치 전략도 실제(dev기준)와 일치시켰다 — 이 문서가develop을 전제한 게 애초 원인이었다.2. 그 사이 쌓인 lint 위반 (P1)
react-hooks/refs):useTypewriter,useInterviewSocket이 렌더 본문에서ref.current = x로 최신 prop 을 밀어넣고 있었다. 커밋 후 이펙트로 옮기고, ref 를 읽는 이펙트(타자기 인터벌 / WS 연결)보다 먼저 선언해 같은 커밋 안에서 ref 가 항상 먼저 최신화되게 순서를 보장했다.useRef초기값이 이미 첫 렌더 값이라 마운트 동작은 그대로. 동작 변경 의도 없음._make_req()가 반환형을 문자열로 적고 그 타입을 함수 본문에서 지역 import 했는데, 모듈 최상단이 이미 같은 모듈에서 import 하고 있고from __future__ import annotations도 걸려 있어 둘 다 불필요했다.uv run black .결과를 별도 커밋으로 분리했다(로직 변경 없음).3. 의존성 취약점 (P1)
npm audit13건(high 9) → 0건. semver 범위 안에서 전부 해결되어package.json변경 없이 lockfile 만 갱신.react-router-dom7.15.0 → 7.18.2 — 프로덕션 의존성이고 권고 5건:<Link>·useNavigate백슬래시 open redirect, RSC 에러 핸들러 XSS,deserializeErrors임의 constructor 주입, 라우트 매칭 DoS, RSC CSRF 우회vite8.0.x → 8.2.1 — dev 서버 계열(path traversal,server.fs.deny우회, WS 임의 파일 읽기). 배포물 영향 없지만 개발자 로컬이 노출Dependabot 이 별도로 잡고 있던
postcss·js-yaml·brace-expansion·@babel/core알림 14건도 이 lockfile 에서 모두 패치 버전 이상이 됐다(postcss 8.5.26, js-yaml 4.3.1, brace-expansion 1.1.18/5.0.9, @babel/core 7.29.7).테스트
로컬 검증 (모두 통과):
eslint0건 ·tsc -b·vitest68/68 ·npm run build·npm audit0건./gradlew cleanTest testBUILD SUCCESSFULflake80건 ·black --check0건 ·pytest321 passedrealtime(Go) 잡은 이 PR 에서 처음 실행된다. 로컬에 Go 툴체인이 없어 검증하지 못했고, 이 코드가 CI 로 검증된 적이 한 번도 없다(이전 워크플로가 잘못된 디렉토리에서 돌았음). 여기서 빨간불이 나면 그건 이 PR 이 만든 회귀가 아니라 드러난 기존 상태다.
영향 범위
리뷰어 체크포인트
ci.yml의 트리거 브랜치가 팀 운영과 맞는지 (dev+main으로 뒀다)939e3d8)은 포맷 전용이라 건너뛰고 봐도 된다