OpenQodex 0.11.0 실험: AI 리뷰가 미완료여도 Git push가 통과할 수 있는 이유
Git pre-push Hook에 AI 코드 리뷰를 넣으면 깔끔해 보인다. 코드를 원격 저장소로 보내기 전에 검사하고, 리뷰가 없거나 중대한 문제가 발견되면 push를 막는 방식이다. 나 역시 처음에는 그렇게 기대했다.
OpenQodex 0.11.0을 버려도 되는 Git 테스트 저장소에서 여러 차례 실행해 봤다. ShellCheck는 일부러 만든 Shell 스크립트의 문제를 실제로 찾아냈다. 설정에 따라 Git Hook이 리뷰하지 않은 push를 거부하는 것도 확인했다. 하지만 독립 AI Reviewer가 끝까지 실행되지 못했을 때는, Hook이 반드시 push를 거부하는 것은 아니었다.
이 결과가 OpenQodex의 AI 리뷰 품질이 나쁘다는 뜻은 아니다. AI Reviewer의 전체 리뷰는 검증에 실패했고, 제대로 끝나지 않았다. 중요한 것은 정적 스캔, 독립 AI Reviewer, 기존 리뷰 영수증(Receipt)을 조회하는 Git push 게이트가 서로 다른 단계라는 점이다. Hook을 설치했다고 세 단계가 자동으로 완료되는 것은 아니다.
ShellCheck가 버그를 찾았다. 실제로 실행됐을 때만
먼저 폐기 가능한 Git 저장소에 같은 Shell 스크립트의 두 버전을 준비했다. 기준 버전에는 변수를 큰따옴표로 감쌌고, 수정 버전에서는 그 따옴표를 의도적으로 제거했다. ShellCheck 규칙 SC2086을 발생시키기 위한 합성 샘플이다.
이 테스트는 OpenQodex 0.11.0의 스캐너만 실행했고 AI 모델은 호출하지 않았다. 핵심 인자는 다음과 같다.
openqodex scan --uncommitted --only shellcheck --block-on-severity nitpick
읽기 쉽게 --cwd, --report-dir, 검증된 패키지의 고정 실행 경로는 생략했다. 위 한 줄이 실험에서 사용한 정확한 원본 명령이라는 뜻은 아니다. 재현하려면 별도의 Git 저장소에 실제 diff를 만든 다음, 버전을 고정한 CLI로 실행해야 한다. 자세한 명령 의미는 해당 커밋의 공식 문서를 참고하면 된다.
같은 인위적 변경이지만 ShellCheck를 사용할 수 있느냐에 따라 결과가 달랐다.
| 실행 조건 | ShellCheck 상태 | 최종 탐지 수 | 스캔 종료 코드 |
|---|---|---|---|
| 오프라인, 설치 금지 | not_installed | 0 | 0 |
| 설치 및 실행 허용 | ran | 1 (SC2086) | 1 |
2026년 10월 10일의 고정 버전 실험에서 얻은 민감 정보를 제거한 결과다. 온라인 실행에서는 rawCount=1과 keptCount=1이 기록됐고 최종 보고서에도 SC2086이 있었다. 반대로 오프라인 실행에서는 ShellCheck 자체를 설치하지 못해 검사도 하지 않았다. 이 경우 0 findings를 보고 “문제가 없다”고 판단해서는 안 된다.
한 가지 더 조심해야 한다. OpenQodex의 이 심각도 매핑에서 SC2086은 nitpick으로 분류되지만, review.severity_threshold의 기본값은 minor다. 기본 표시 결과만 보면 실제 탐지가 없었던 것처럼 보일 수 있다. 앞선 확인 과정에서 바로 이 필터링을 겪어서, 실험에서는 --block-on-severity nitpick을 명시해 의도적으로 넣은 오류를 보고서에 남겼다.
그래서 이제 단순히 종료 코드가 0인지부터 보지 않는다. 스캐너가 실제로 실행됐는지, 원래 탐지한 항목이 심각도와 변경 라인 필터를 거친 뒤 얼마나 남았는지를 먼저 확인한다. 도구가 설치되지 않았는데도 0으로 끝날 수 있다는 사실이 중요하다.
pre-push Hook이 매번 AI 리뷰를 자동 실행하지는 않는다
다음은 Git을 실제로 사용한 테스트다. 원격 저장소 대신 로컬 bare 리포지토리를 연결했다. 프로덕션 저장소와 호스팅 서비스의 실제 remote는 건드리지 않았다.
OpenQodex의 진짜 pre-push Hook을 설치한 뒤 리뷰 기록이 없는 변경을 커밋했다. 기본 설정에서 Hook은 미리 리뷰하지 않았다는 경고를 출력했지만, Git push는 성공했고 로컬 bare remote의 분기도 업데이트됐다.
설정을 바꿨다.
version: 1
review:
block_on_severity: major
새로운 미리뷰 커밋을 다시 push하자 이번에는 거부됐다. 테스트는 bare remote의 main이 이전 커밋을 그대로 가리키는지도 확인했다. 종료 코드 1만 반환하는 가짜 Hook으로 만든 결과가 아니라, 실제 설치된 OpenQodex Git Hook을 거친 결과다.
| 합성 push 조건 | 실제로 확인한 결과 |
|---|---|
| 기본 설정, 리뷰 Receipt 없음 | 경고 출력 후 push 허용 |
block_on_severity: major, 여전히 미리뷰 | push 거부, 원격 HEAD 변화 없음 |
이것은 리뷰 기록이 없는 변경의 처리 방식을 검증한 것이다. 독립 Codex Reviewer를 실행하지 않았고, 성공적인 AI 리뷰에 대한 유효 Receipt도 발급되지 않았다.
고정 버전의 Hook 소스를 보면 역할이 더 분명하다. pre-push는 Git이 보내려는 변경 범위를 읽고, 사용자 OpenQodex HOME에서 해당 변경과 일치하는 Receipt를 찾은 다음, 그 결과로 통과 여부를 판단한다. git push를 입력할 때마다 스캐너를 다시 실행하거나 별도의 AI 모델을 자동 시작하지 않는다.
그러니 “scan이 끝났다”, “review가 완료됐다”, “hook이 push를 허용했다”는 각각 따로 검증해야 할 사실이다. 하나만 확인했다고 AI 코드 리뷰까지 완료했다고 할 수는 없다.
Reviewer가 실패했는데 왜 push가 통과할 수 있을까?
세 번째 단계에서 까다로운 상황이 생겼다. Mac에서 독립 Codex Reviewer를 실행했을 때 하위 프로세스는 요청을 시작했지만 HTTP 401 응답을 받았다. OpenQodex는 이를 incomplete로 기록했고 유효한 리뷰 완료 Receipt를 얻지 못했다. 따라서 독립 모델의 버그 탐지 정확도나 정상·오래된 Receipt를 처리하는 전체 흐름을 평가할 수 없다.
해당 Mac 테스트 요약에는 미리뷰 push가 거부된 후 incomplete 리뷰를 거친 다른 push가 종료 코드 0으로 성공하고 원격 분기를 추가했다는 관찰도 있었다. 하지만 두 push의 대상 ref가 동일하지 않았고, 원본 Mac 보고서 파일도 독립 검증 가능한 증거 패키지에 포함되지 않았다. 그러므로 이 순서만으로 “리뷰 실패 때문에 push가 성공했다”는 인과관계를 실험적으로 증명했다고 말하지 않는다.
대신 OpenQodex 0.11.0의 고정된 소스 코드에서 확인 가능한 동작이 있다.
push-gate.ts에서 checkPush()는 현재 변경과 일치하는 kind: "incomplete" Receipt를 발견하면 abstain을 반환한다. 스스로 push를 막지 않는다는 의미다. review.block_on_severity: major로 설정해도 이 분기는 거부로 바뀌지 않는다.
또한 hook.ts가 생성하는 Git Hook은 하위 명령의 종료 코드가 1일 때만 push를 거부한다. 다른 도구 오류나 하위 프로세스를 시작하지 못하는 경우에는 바깥쪽 셸 스크립트가 0으로 종료한다. 리뷰 도구 장애가 개발자의 Git 작업을 통째로 멈추지 않도록 한 설계다. 그러나 그 말은 심각도 임계값을 설정하는 것과 성공적인 리뷰 완료를 필수 조건으로 만드는 것이 다르다는 뜻이기도 하다.
이것을 임의의 운영 환경 보안 우회를 검증한 결과로 과장할 필요는 없다. 도구 제작자는 리뷰 서비스가 고장 나도 개발을 이어 가도록 선택할 수 있다. 반면 어떤 팀은 리뷰 실패 시 반드시 막기를 원한다. 위험한 것은 앞의 정책을 뒤의 정책으로 착각하는 경우다.
리뷰 완료를 반드시 요구한다면
현재로서는 OpenQodex를 개발자 피드백 도구로 보되, 단 하나의 강제 병합 게이트로 삼지는 않겠다. 스캐너는 정확한 규칙과 파일 위치를 알려주고 Hook은 미리뷰 상태를 경고한다. 하지만 병합 허용 여부는 별도의 관측·검증 가능한 CI 검사에서 판단하도록 하고 싶다. 적어도 아래 조건은 나눠서 확인해야 한다.
- 필요한 스캐너가 실제 실행됐는지, 아니면
not_installed, 생략, 실행 오류인지. - 독립 AI Reviewer가 완전히 끝났는지,
unavailable또는incomplete상태인지. - Receipt가 현재 commit / diff 및 현재 심각도 정책과 맞는지.
- CI나 리뷰 서비스가 실패할 때도 보호 브랜치가 관련 required check의 성공을 반드시 요구하는지.
이것은 운영 구성에 대한 권고이며, 이번 실험에서 GitHub 브랜치 보호까지 연동해 통과를 확인한 해결책은 아니다. 모든 병합에서 독립 리뷰 완료를 필수로 하는 팀이라면 CI 게이트 자체가 fail-closed로 동작하는지 추가 검증해야 한다. 로컬 pre-push 설정만으로 그 보장은 성립하지 않는다.
개인 저장소에서는 매 push마다 모델을 호출하는 것이 꼭 합리적이지 않을 수도 있다. 필요한 때 review를 돌리고, 정적 스캔을 빠른 피드백으로 활용하며, 중요한 브랜치를 실제 테스트한 CI 게이트로 보호하는 편이 비용과 작업 흐름에 맞을 수 있다. 전제는 “Hook이 설치되어 있다”를 “이 코드가 독립 AI 리뷰를 통과했다”로 해석하지 않는 것이다.
이번에 확인된 범위는 좁다. OpenQodex 0.11.0은 합성 Shell 변경에서 SC2086을 발견했다. 별도의 bare Git remote에서는 기본 경고 후 허용과 설정 변경 후 미리뷰 push 거부가 관찰됐다. 고정 소스에는 incomplete Receipt와 특정 Hook 오류를 막지 않는 경로도 있다. 독립 모델 리뷰는 성공적으로 끝나지 않았다. 모델 품질, 오탐률, 지연 시간, 유효 Receipt의 전체 왕복 검증에 관한 신뢰할 수 있는 결론은 없다.
그 빈틈을 남겨 둔 채 OpenQodex 로컬 Hook을 프로덕션 코드의 유일한 강제 AI 리뷰 게이트로 사용하지는 않겠다. 스캔과 리뷰를 시작하는 진입점으로는 충분히 유용하다. 다만 push 허용 여부를 맡기기 전에, 리뷰가 끝나지 않을 때 누가 확실히 막는지부터 확인해야 한다.