상태줄 요약을 켰더니 요청이 거부됐다: Codex 0.155.1 핫픽스가 되돌린 기본값
Codex 0.155.0은 터미널 상태줄에 모델의 추론 요약을 실시간으로 보여 주는 기능을 넣었습니다. 화면만 보면 작은 편의 기능입니다. 그런데 새 TUI 세션의 기본값도 함께 바뀌었습니다. 사용자가 아무 설정을 하지 않아도 model_reasoning_summary = "detailed"가 요청에 들어갔고, 이 필드를 지원하지 않는 모델 제공자는 요청 자체를 거부했습니다.
OpenAI는 0.155.0을 공개한 지 20시간 48분 21초 뒤 0.155.1을 냈습니다. 핫픽스의 핵심 코드는 한 줄입니다. 설정이 없을 때 쓰는 값을 Detailed에서 None으로 되돌렸습니다. 명시적으로 auto, concise, detailed를 고른 사용자의 설정은 그대로 존중합니다.
화면 기능이 요청 계약을 바꾼 경로
상태줄은 결과를 표시하는 UI지만, 실시간 요약을 받으려면 클라이언트가 모델 요청에서 요약을 요구해야 합니다. 0.155.0에 포함된 PR #43921은 새 embedded TUI thread의 기본 model_reasoning_summary를 detailed로 바꿨습니다. 동시에 진행 중인 요약을 상태줄에 남기고, 도구 호출 중에도 마지막 요약을 유지하며, 세션 재개와 스레드 전환 뒤에도 상태를 복원하는 코드와 테스트를 추가했습니다.
기능 자체의 흐름은 자연스럽습니다. 새 스레드가 설정을 읽고, 요청 override를 만들고, provider로 보내며, 돌아온 요약을 상태줄에 그립니다. 문제는 첫 단계에서 "설정 없음"을 "상세 요약 요청"으로 해석한 데 있었습니다.
그림 1. 상태줄 표시 기능의 기본값이 wire request 옵션까지 바꾸면서 미지원 provider 연결이 실패했다. 출처: github.com/openai/codex/pull/46467.
공식 핫픽스 설명은 reasoning summaries를 지원하지 않는 provider가 이 요청을 거부했다고 적습니다. 여기서 범위를 넓히면 안 됩니다. 모든 외부 provider가 실패했다는 뜻도 아니고, reasoning을 지원하는 모델이 반드시 detailed summary까지 지원한다는 뜻도 아닙니다. 실패 조건은 요청에 들어간 기능과 provider가 받는 계약이 맞지 않는 경우입니다.
한 줄 기본값이 만든 차이
두 태그의 app_server_session.rs를 직접 비교하면 변경 범위가 선명합니다. 0.155.0은 model_reasoning_summary가 비어 있을 때 ReasoningSummary::Detailed를 사용합니다. 0.155.1은 같은 위치에서 ReasoningSummary::None을 사용합니다. 코드 경로와 키 이름은 그대로고 fallback만 바뀌었습니다.
그림 2. 0.155.1은 요약 기능을 삭제하지 않고 설정 없음의 fallback만 detailed에서 none으로 복구했다. 출처: github.com/openai/codex/pull/46467/files.
이 차이는 "요약 기능 삭제"가 아닙니다. 핫픽스의 회귀 테스트는 네 경우를 나눕니다. 설정이 없으면 none, 명시적인 auto는 auto, concise는 concise, detailed는 detailed로 전달됩니다. concurrent_reasoning_summaries도 요약이 명시적으로 켜졌을 때만 함께 활성화됩니다. 공식 설정 참조 역시 model_reasoning_summary의 값으로 auto | concise | detailed | none을 구분하고, model_supports_reasoning_summaries를 별도 지원 여부 override로 설명합니다.
따라서 운영자가 0.155.0에서 문제를 피하려고 model_reasoning_summary = "none"을 넣었다면 그 설정은 여전히 유효합니다. 0.155.1에서는 설정을 아예 두지 않은 새 세션도 같은 안전한 기본값을 사용합니다. 반대로 상세 요약이 필요하고 provider가 지원한다면 detailed를 명시하면 됩니다.
릴리스 노트와 실제 배포를 따로 확인해야 하는 이유
PR이 병합됐다는 사실만으로 안정 버전에 들어갔다고 볼 수는 없습니다. 0.155.0은 GitHub의 원시 timestamp 기준 2026년 9월 17일 23:14:43 UTC, 한국 시각 18일 08:14:43에 공개됐습니다. 0.155.1은 18일 20:03:04 UTC, 한국 시각 19일 05:03:04에 공개됐습니다. 두 시각의 차이는 74,901초입니다.
그림 3. 두 stable release는 provider-native 공개 시각 기준 74,901초 간격이었다. 출처: github.com/openai/codex/releases/tag/rust-v0.155.1.
0.155.1 릴리스 노트는 버그 수정 항목을 PR #46467 하나로 연결합니다. 그 PR은 release/0.155에 병합된 focused backport이며, 변경 파일 2개, 추가 22줄, 삭제 5줄입니다. PR 작성자는 요약 기본값과 outbound Responses request를 다루는 집중 테스트 9개, 비무시 TUI 테스트 4,589개, Linux·macOS·Windows 릴리스 빌드 검사를 보고했습니다. 이 숫자는 upstream 검증 기록입니다. 이번 글에서 전체 Rust 테스트를 다시 실행했다고 주장하지는 않습니다.
왜 capability negotiation이 기본값보다 앞서야 하나
클라이언트가 여러 provider를 지원하면 "우리 기본 모델에서는 된다"는 판단으로 요청 옵션을 켜기 어렵습니다. 같은 이름의 설정이라도 provider가 필드를 무시할지, 거부할지, 다른 형식으로 받을지는 계약마다 다릅니다. UI에서 쓰고 싶은 데이터가 있다고 해서 wire request의 기본 필드까지 자동으로 늘리면, 표시 기능이 연결 실패로 바뀔 수 있습니다.
안전한 순서는 세 단계입니다. 첫째, provider와 선택한 모델이 기능을 지원하는지 확인합니다. 둘째, 사용자가 기능을 명시적으로 선택했는지 봅니다. 셋째, 두 조건이 맞을 때만 선택 필드를 요청에 넣습니다. 확인할 수 없다면 none처럼 최소 계약으로 시작하고, 지원이 증명된 경로에서만 늘리는 편이 낫습니다.
이 원칙은 reasoning summary에만 해당하지 않습니다. JSON schema, parallel tool calls, audio, image input, prompt caching처럼 provider별 차이가 있는 선택 기능도 같습니다. UI 기능 플래그와 API capability 플래그를 하나로 묶으면 이번과 비슷한 회귀가 다시 생깁니다.
재발을 막는 테스트 표
설정 파서의 반환값만 확인해서는 이 회귀를 잡기 어렵습니다. 실제로 중요한 것은 마지막 outbound request입니다. 설정이 없거나 none일 때 Responses 요청의 요약 값이 null로 남아 기능을 요구하지 않는지, detailed를 명시했을 때는 그 값이 들어가는지, 동시 전송 옵션이 요약을 끈 상태에서 살아나지 않는지를 같은 표에서 검사해야 합니다. PR #46467도 새 스레드 설정과 Responses 요청까지 함께 검증하도록 회귀 테스트를 바꿨습니다.
provider fixture도 두 부류가 필요합니다. 첫 fixture는 reasoning summary를 지원하고 명시값을 받아야 합니다. 둘째 fixture는 이 기능을 지원하지 않으며, 선택 필드가 들어오면 거부하도록 만들어야 합니다. 이렇게 해야 "기능이 동작한다"와 "기능을 쓰지 않는 연결도 계속 동작한다"를 동시에 지킬 수 있습니다. 미지원 경로가 단순히 필드를 무시하도록 만들면 실제 provider의 엄격한 검증을 재현하지 못합니다.
설정 계층도 빠뜨리기 쉽습니다. 사용자 전역 설정, 프로젝트 설정, 세션 override를 합친 최종값이 무엇인지 기록하고, 그 값이 wire request와 같아야 합니다. 화면에 표시된 토글이나 상태줄만 스냅샷으로 남기면 요청 직전의 변환에서 생긴 회귀는 보이지 않습니다.
운영자가 확인할 것
Codex 0.155.0에서 새 TUI 세션이 provider 오류로 시작되지 않는다면 먼저 0.155.1 이상으로 올리고, 사용자와 프로젝트의 config.toml을 확인하는 편이 좋습니다. model_reasoning_summary를 명시했다면 업그레이드 뒤에도 그 값이 유지됩니다. 문제를 재현할 때는 상태줄이 보였는지만 기록하지 말고 실제 provider, 선택 모델, 최종 유효 설정, outbound request에서 요약 옵션이 들어갔는지를 함께 남겨야 합니다.
도구 개발자라면 테스트 표를 기본값과 명시값으로 나누는 것이 중요합니다. 설정 없음, none, auto, concise, detailed를 각각 검증하고, 지원 provider와 미지원 provider를 따로 둬야 합니다. 기능을 켜는 테스트만 있으면 기본값 회귀를 놓치고, 오류만 막는 테스트만 있으면 사용자가 명시한 기능까지 꺼 버릴 수 있습니다.
이번 핫픽스는 큰 아키텍처 변경이 아닙니다. 한 줄 fallback을 되돌렸습니다. 하지만 그 한 줄이 보여 주는 경계는 큽니다. 화면에 더 많은 정보를 보여 주는 선택과 모든 provider 요청에 더 많은 필드를 보내는 선택은 같은 일이 아닙니다.



댓글
댓글 쓰기