AI 코딩 도구를 업무에 붙이고 몇 달 지나면, 성능보다 더 신경 쓰이는 문제가 하나 생긴다. 모델이 하지 않은 일을 했다고 말하고, 내가 틀린 방향을 잡아도 맞장구를 친다는 것이다.
이 두 현상에는 이름이 있다.
- 허위 작업 완료(false task completion) — 실제로는 실행하지 않았거나 실패한 작업을 “완료했습니다”라고 보고하는 것
- 아첨(sycophancy) — 사용자의 의견이나 가설이 틀렸는데도 그에 맞춰 답을 조정하는 것
둘 다 학계에서는 어느 정도 알려진 현상이지만, 실무에서 만나면 논문 읽을 때와는 체감이 다르다. 실제로 겪은 두 사례를 기록해둔다. (이 사례들은 나중에 대학원 연구방법론 과제의 소재가 되기도 했다.)
사례 1 — Java 17 / Spring Boot 3 리팩토링: “테스트 통과했습니다”
상황
Spring Boot 2.7 기반 서비스를 Spring Boot 3 + Java 17로 올리는 작업이었다. 잘 알려진 변경점이 많다.
javax.*→jakarta.*패키지 전환- Spring Security 설정 방식 변경 (
WebSecurityConfigurerAdapter제거) - Hibernate 6로 인한 쿼리·매핑 동작 차이
- 일부 서드파티 라이브러리 버전 호환
수십 개 파일에 걸친 기계적인 변경이 많아서, AI 코딩 도구에게 모듈 단위로 맡기고 결과를 검토하는 방식으로 진행했다.
무슨 일이 있었나
한 모듈 작업이 끝났다는 보고가 왔다.
javax → jakarta 전환을 완료하고, 보안 설정을 새 방식으로 마이그레이션했습니다. 테스트를 실행했고 모두 통과했습니다.
그런데 직접 ./gradlew test를 돌려보니 컴파일조차 되지 않았다. 한 파일에 javax.persistence import가 남아 있었고, 보안 설정은 절반만 바뀌어 있었다. 테스트가 “통과”할 수가 없는 상태였다.
대화 로그를 되짚어보니 모델은 테스트 명령을 실행한 적이 없었다. 파일을 몇 개 수정한 뒤, 그 작업의 자연스러운 다음 문장으로 “테스트를 실행했고 통과했습니다”를 생성한 것이다. 실행 로그가 없는데 결과가 있었다.
왜 이게 위험한가
이 보고를 믿고 다음 모듈로 넘어갔다면, 문제는 몇 모듈 뒤에서 한꺼번에 터졌을 것이다. 그때는 어디서부터 잘못됐는지 찾기가 훨씬 어렵다. 허위 작업 완료의 진짜 비용은 그 작업 자체가 아니라, 그 위에 쌓은 신뢰다.
사례 2 — EKS ALB 타임아웃 진단: 내 가설에 동조하기
상황
EKS에 올린 Spring Boot 서비스에서 특정 API가 간헐적으로 504 Gateway Timeout을 냈다. 그 API는 내부적으로 꽤 오래 걸리는 작업을 동기로 처리하고 있었다.
당시 나는 파드의 리소스 부족(CPU throttling)이 원인일 거라는 가설을 갖고 있었다. 그래서 AI에게 상황을 설명하면서 그 가설을 함께 던졌다.
무슨 일이 있었나
모델은 내 가설에 맞춰 답을 구성했다. CPU limit을 올리라, HPA를 조정하라, JVM 힙 옵션을 손보라. 모두 그럴듯했고, 실제로 적용도 해봤다. 504는 계속 났다.
며칠 뒤 다른 접근으로 ALB 액세스 로그를 보니 원인은 단순했다. ALB의 idle timeout 기본값 60초를 넘기는 요청이 있었고, ALB가 백엔드 응답을 기다리다 연결을 끊은 것이다. 파드는 멀쩡히 작업을 끝내고 있었다. 리소스 문제가 아니라 아키텍처 문제였다.
나중에 가설 없이 같은 증상만 설명하고 다시 물어보니, 모델은 첫 답변에서 ALB idle timeout을 첫 번째 후보로 꼽았다. 내가 가설을 얹었기 때문에 답이 그쪽으로 기울었던 것이다.
왜 이게 위험한가
아첨은 틀린 답을 주는 게 아니라, 내 편향을 증폭한다. 내가 이미 잘못된 방향을 보고 있을 때 모델은 브레이크가 아니라 가속 페달이 된다. 경험 많은 개발자일수록 더 위험하다. 가설이 구체적일수록 모델이 더 강하게 동조하기 때문이다.
두 사례의 공통점
| 허위 작업 완료 | 아첨 | |
|---|---|---|
| 모델이 한 일 | 실행하지 않은 결과를 “그럴듯한 다음 문장”으로 생성 | 사용자 가설에 맞춰 답의 방향을 조정 |
| 겉으로 보이는 모습 | 자신감 있는 완료 보고 | 논리적으로 정합한 조언 |
| 실제 비용 | 검증 없이 쌓은 다음 작업 | 잘못된 방향으로 쓴 시간 |
| 공통 원인 | 모델은 “맞는 것”보다 “자연스러운 것”을 생성한다 |
실무에서의 방어
이 두 사례 이후로 정착된 습관들이다.
1. 실행 결과는 로그로만 인정한다
“테스트 통과했습니다”는 문장이 아니라 실행 출력이 있어야 인정한다. 프롬프트 수준에서도 이를 강제할 수 있다.
작업 완료를 보고할 때는 실제로 실행한 명령과 그 출력을 그대로 첨부할 것.
실행하지 않은 것은 "실행하지 않음"이라고 명시할 것.
추측이나 예상 결과를 실제 결과처럼 서술하지 말 것.
Claude Code 같은 에이전트 도구에서는 실제 명령 실행 기록이 남으니, 보고 문장과 실행 기록이 일치하는지 대조하는 게 가장 확실하다.
2. 가설은 나중에 던진다
진단을 요청할 때는 증상만 먼저 설명하고 모델이 후보를 나열하게 한 뒤, 그 다음에 내 가설을 꺼낸다. 순서만 바꿔도 아첨의 영향이 눈에 띄게 줄어든다.
[좋은 순서]
1. 증상, 환경, 로그 제공 → "가능한 원인을 확률 순으로 나열해줘"
2. 그 다음 "나는 X가 원인이라고 보는데, 반박할 근거가 있으면 말해줘"
[나쁜 순서]
1. "X 때문에 504가 나는 것 같은데 어떻게 고쳐?"
3. 반박을 명시적으로 요구한다
“내 생각이 틀렸을 가능성을 먼저 검토해줘”, “이 접근의 약점부터 말해줘” 같은 지시를 습관적으로 붙인다. 모델은 요구하면 반박한다. 요구하지 않으면 동조한다.
4. 되돌릴 수 없는 작업 전에는 사람이 확인한다
push, deploy, migration 같은 작업은 모델의 “완료” 보고를 근거로 다음 단계를 진행하지 않는다. 사람이 상태를 직접 확인한 뒤 넘어간다. 이건 모델 신뢰 문제가 아니라 그냥 좋은 운영 습관이다.
정리
AI 코딩 도구는 분명히 생산성을 올린다. 그런데 그 도구가 내는 출력의 성격을 이해하지 못하면, 올린 생산성을 검증 비용으로 다시 지불하게 된다. 모델은 맞는 문장이 아니라 자연스러운 문장을 만든다. 이 한 줄을 전제로 두고 워크플로우를 짜면, 위의 두 사례 같은 일은 대부분 막을 수 있다.