오늘은 새 기능 구현보다 git 운영, 코드 리뷰, 배포 환경 트러블슈팅에 집중한 하루였다. 팀원들이 쏟아내는 PR을 계속 pull 받아 확인하면서, 그 과정에서 발견한 문제들을 바로 고쳐서 다시 PR로 올리는 흐름으로 진행했다.
tsconfig.app.tsbuildinfo — 의미 없는 git 충돌의 원인 제거TypeScript 증분 빌드 캐시 파일(frontend/tsconfig.app.tsbuildinfo)이 git에 추적되고 있었다. 로컬에서 빌드할 때마다 내용이 바뀌는 파일이라, git pull 할 때마다 "로컬 변경사항이 있어 merge가 막힘" 같은 상황이 반복됐다.
해결: frontend/.gitignore에 *.tsbuildinfo 추가 + git rm --cached로 기존 추적 해제. 이후로는 이 파일 때문에 충돌이 재발하지 않았다.
main 최신화 확인팀 CLAUDE.md(AI 작업 규칙 문서)의 "PR 생성 규칙"에 git fetch/pull로 origin/main을 먼저 확인하는 단계를 1번으로 추가했다. 로컬이 원격보다 뒤처진 걸 모른 채 작업하다가 뒤늦게 충돌을 발견하는 상황을 막기 위함.
feature/be-workschedule 브랜치를 리뷰하면서 두 가지를 발견했다.
workSchedule은 필수"라고 주석엔 적혀 있는데 실제 검증 코드가 없었다. @AssertTrue로 교차 필드 검증을 추가해서, workType != LIVE_IN인데 workSchedule이 비어 있으면 400 에러가 나가도록 고쳤다.@DataJpaTest 셋업(회원/시설 프로필 생성 로직)을 거의 그대로 복붙하고 있었다. 하나로 합쳐서 앞으로 필드가 추가될 때 두 픽스처가 따로 놀며 깨지는 위험을 없앴다.feature/be-facility-type-applicant 브랜치를 리뷰하다가, git이 충돌 없이 자동 머지하지만 실제로는 컴파일이 깨지는 상황을 발견했다.
원인은 SearchCondition이라는 record(불변 데이터 클래스)에 있었다. 이 브랜치가 facilityTypes라는 필드를 새로 추가하면서 생성자 인자가 12개 → 13개로 늘어났는데, 그 사이에 먼저 머지된 다른 PR이 이 record를 옛날 12개짜리 생성자로 호출하는 테스트 코드를 추가해버린 것. git은 서로 다른 줄을 건드렸으니 "충돌 없음"으로 판단하지만, Java 컴파일러는 인자 개수가 안 맞는다고 에러를 낸다.
error: constructor SearchCondition in record SearchCondition cannot be applied to given types;
required: ...,List<FacilityType>,... (13개)
found: ...,<null>,... (12개)
이런 건 GitHub의 "Merge conflict" 표시로는 절대 안 잡힌다. 로컬에서 실제로 머지를 시도해보고 ./gradlew compileTestJava를 돌려봐야 나온다. record/데이터 클래스에 필드를 추가할 때는 포지셔널 생성자를 쓰는 모든 곳을 실제로 컴파일까지 돌려서 확인해야 한다는 걸 다시 확인한 케이스.
프론트 팀원이 로컬(localhost:5173)에서 배포된 Render 백엔드로 API를 호출하면 CORS 403이 났다. 원인은 프론트 설정이 아니라 서버 쪽 화이트리스트 문제였다 — Render의 CORS_ALLOWED_ORIGINS 환경변수가 Vercel 도메인만 등록돼 있어서 localhost가 허용 목록에 없었던 것.
해결책은 두 가지:
1. 백엔드를 로컬에서 직접 실행(./gradlew bootRun) — 로컬 기본 CORS 설정엔 localhost:5173이 이미 포함됨
2. Render 환경변수에 localhost:5173 추가 (대시보드 접근 권한 필요)
이 내용을 README 트러블슈팅 섹션에 8번 항목으로 정리해서 문서화해뒀다.
그런데 알고 보니 Render 대시보드 로그인(구글 계정)이 막혀서 팀원 누구도 환경변수를 못 건드리는 상황이었다. 로컬 백엔드로 우회하면 되지 않냐고 했는데, 회원가입 시 본인인증 코드를 확인할 방법이 없다는 게 다음 문제로 나왔다.
파고 보니 이것도 이미 목업(mock) 시스템으로 설계돼 있었다 — 실제 SMS/이메일 발송 없이 서버 로그로만 코드가 찍히는데, local 프로필에서 실행하면 API 응답(devCodeHint)에 코드가 그대로 실려서 프론트 화면에 토스트로 뜨도록 이미 구현돼 있었다. 문제는 Render(prod)에서는 이 값이 항상 null이라 로그 접근 권한 없인 확인 불가능하다는 것.
프론트 팀원들이 다른 기능 개발을 진행할 수 있도록, 회원가입 시 본인인증을 임시로 비활성화했다. 나중에 되돌리기 쉽게 하드코딩 삭제 대신 플래그로 처리했다.
carematch.verification.required-for-signup 설정 추가 (기본 false). MemberService.registerJobSeeker/registerFacility에서 이 플래그가 켜져 있을 때만 인증 여부를 검사하도록 감쌈. 기본값이 false라 Render 환경변수를 못 건드려도 머지 즉시 배포에 적용되는 게 포인트.Signup 폼의 클라이언트 검증(validate())도 별도로 !verified를 막고 있어서, 백엔드만 풀어선 화면에서 여전히 제출이 안 됐다. VERIFICATION_REQUIRED 상수로 같은 방식으로 감싸서 통일.재활성화는 두 플래그를 다시 true로 되돌리기만 하면 된다.
"머지가 됐다"와 "머지된 코드가 돌아간다"는 다른 이야기다. git의 3-way merge는 텍스트 레벨에서만 충돌을 판단하기 때문에, record 생성자 인자 개수 변경처럼 "같은 줄을 안 건드렸지만 의미적으로 깨지는" 변경은 실제로 컴파일/테스트를 돌려봐야만 잡힌다. 그리고 배포 환경 접근 권한이 막히는 상황은 언제든 생길 수 있으니, "환경변수 하나 바꾸면 되는 문제"도 그 권한이 없을 때를 대비한 우회로(로컬 실행, 기본값 기반 토글)를 항상 같이 설계해두는 게 낫다.