Repository navigation
[Bug][Codex App] Encrypted subagent MESSAGE payload is surfaced as gAAAA ciphertext after adapter_eof #3021
Description
Client or integration
Codex App with delegated subagent messaging.
Area
OpenAI Responses adapter, encrypted tool output recovery, and subagent message delivery.
Summary
After a native OpenAI Responses turn ended with:
stream disconnected before completion: Incomplete response returned, reason: adapter_eof
a later message from the same delegated subagent was delivered into the parent conversation as a raw encrypted-looking payload beginning with gAAAA..., instead of the plaintext subagent result or a structured decryption error.
The parent message envelope was rendered normally (Message Type, Task name, Sender, and Payload), but the entire payload body was ciphertext.
The ciphertext is intentionally not included in this report. It may contain encrypted task output and should be treated as sensitive even when it is not immediately readable.
Reproduction
Observed sequence:
- Run a parent Codex App task through a local OpenCodex proxy using the native OpenAI Responses adapter.
- Spawn a delegated subagent.
- The parent turn encounters
adapter_eofbefore receiving a complete response. - Interrupt or follow up with the subagent while recovering the task.
- The parent later receives a subagent
MESSAGEevent whose payload is a longgAAAA...ciphertext value rather than plaintext.
This was observed once in a live task. The asynchronous timing makes it non-deterministic, so the exact request, thread, account, and ciphertext values are omitted.
Expected behavior
- Encrypted subagent or function output is decrypted only in the owning account/session context before delivery.
- If the required decryption context is unavailable after retry, fail closed with a structured error and correlation-safe diagnostics.
- Never forward the encrypted blob to the parent conversation or user-visible UI.
- Preserve enough non-sensitive diagnostics to distinguish account/session mismatch, missing decryption state, and ordinary stream truncation.
Actual behavior
The parent task received a syntactically valid subagent message envelope containing raw ciphertext as its visible payload. The intended subagent output was lost.
Impact
- User-visible task output is corrupted and cannot be acted on.
- An internal encrypted artifact crosses into the user-visible message surface.
- Retrying a long task can preserve the wrong account/session context and make recovery confusing.
- Users may accidentally paste the ciphertext into logs, issues, or support channels.
Environment
OpenCodex: 2.34.0
Codex CLI: 0.151.0-alpha.7.1
Client: Codex App
Provider adapter: openai-responses
Auth mode: forward
Codex account mode: pool
Local proxy: 127.0.0.1:10100
OS: macOS 26.6.1 (Build 25G76) on Apple Silicon (arm64)
The local OpenCodex health endpoint remained healthy during diagnosis. The service log contained other 502/429 records, but did not contain the literal strings adapter_eof or the surfaced ciphertext, so this report does not claim a proven log-level correlation.
Related behavior
#919 covers mid-stream OpenAI socket resets affecting account affinity. This report is distinct: encrypted subagent output itself reached the parent UI after an incomplete stream.
Security and redaction
- Ciphertext omitted.
- Tokens, API keys, account identifiers, request IDs, thread IDs, and timestamps omitted.
- No configuration file or raw log is attached.
Checks
- Searched existing issues for
adapter_eof,stream disconnected, and encrypted/decryption errors. - Removed secrets, ciphertext, account details, request credentials, and personal data.
- Clearly marked the reproduction frequency and current evidence boundary.
- Added operating system info
Activity
github-actions commentedon Aug 30, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
lidge-jun commentedon Aug 30, 2026
리뷰 · 우선순위 68 / 80
이 이슈가 말하는 구멍은 지금
devHEAD870a2adb6에도 그대로 열려 있다. 패키지 버전은2.37.0이다. 작성자가 본 설치본은2.34.0이다. 그 사이 #2850 과 보안 후속 #2851 이 올라왔다. 그 기차는 콤보/라우트 자식이 읽을 수 없는NEW_TASK암호문을 한 번 풀어 보거나, 못 풀면 닫는 길이다. 이 이슈가 그린 장면은 그 길이 아니다. 부모 Codex App 대화에 나중에 도착한 위임 서브에이전트MESSAGE의 Payload 가gAAAA로 시작하는 Fernet 암호문 그대로 보인 것이다.재현은 한 번이다. 네이티브 OpenAI Responses 턴이
stream disconnected before completion: Incomplete response returned, reason: adapter_eof로 끊긴 뒤, 같은 위임 서브에이전트가 보낸 메시지가 부모 화면에 봉투(Message Type, Task name, Sender, Payload)는 멀쩡하고 Payload 몸통만 암호문이었다. 작성자는 openai-responses, auth forward, Codex account mode pool, 로컬 프록시 127.0.0.1:10100, macOS 26.6.1 이다. 암호문 자체는 이슈에 없다. 의도적으로 빼 두었다.HEAD 에서
adapter_eof는 업스트림이 만든 코드가 아니다. src/bridge.ts 가 어댑터 스트림이 단말 이벤트 없이 끝나면response.incomplete에reason: adapter_eof를 붙여 합성한다. 잘린 스트림을 완료로 위장하지 않으려고 그렇게 한다. 그 합성 자체는 맞다. 문제는 그 다음이다. 잘린 턴 뒤에 도착한 서브에이전트MESSAGE를 부모가 읽을 수 있는 글이 아니라, 구조만 맞는 Fernet 토큰으로 보여 준 것이다.#2851 이 막아 둔 면은 입구다.
hasUnreadableEncryptedAgentTask는 요청input맨 끝agent_message만 본다. 라우팅 봉투 정규식AGENT_MESSAGE_ROUTING_ENVELOPE와 복구기의ROUTING_HEADER는NEW_TASK만 받는다.MESSAGE는 처음부터 복구 대상이 아니다. 복구 함수recoverEncryptedAgentTask는config.agentTaskRecovery.enabled === true일 때만 살아 있다. 기본값은 꺼져 있다. 그리고 최종 라우트가 정규 OpenAI forward 이면 복구를 건너뛴다. 작성자 환경이 바로 그 길이다. 네이티브 백엔드는 암호문을 풀 수 있다고 보고, 라우트 공급자에게만unreadable_encrypted_agent_task400 을 준다. 부모 화면에 나가는 SSE 를 검사해서 Fernet Payload 를 삼키는 칸은 없다.sanitize 도 이 장면을 안 닫는다.
sanitizeEncryptedContentInPlace는 진짜처럼 생긴 백엔드 암호문(looksLikeBackendCiphertext)을 바이트 그대로 둔다. 평문만input_text로 고친다. Fernet 토큰 검출기(structurallyValidFernetTokens)는gAAAA모양의 버전 바이트 0x80 토큰을 이미 알아본다. 그런데 그 검출 결과는 콤보 자식NEW_TASK입구에만 쓰인다. 부모에게 보이는MESSAGE출력에는 안 붙는다. 그래서 계정/세션이 어긋나거나 잘린 스트림 뒤에 복호 문맥이 없으면, 앱은 구조화된 에러 대신 암호문 Payload 를 그린다.이건 #919 와 다르다. #919 는 소켓 리셋 뒤 계정 친화도다. 이 이슈는 친화도가 깨진 다음 암호문이 부모 UI 로 새는 면이다. #2744 는 #2850/#2851 로 닫힌 콤보
NEW_TASK복구다. 중복으로 닫지 마라. 초안 PR #2496 은 평문 V2 협업 옵션이고, 열린 PR #2113 은 암호문 V2 를 신뢰 공급자에게 더 통과시키자는 쪽이다. #2113 을 이 구멍의 수리로 쓰면 더 넓어진다. types.ts/config.ts 분할과도 안 겹친다. 미리보기 배포는 계획에 없다. 이 구멍을 겨냥한 수리 PR 은 지금 없다.라인 src/bridge.ts 762 - 어댑터 스트림이 단말 없이 끝나면
response.incomplete/adapter_eof를 합성한다. 작성자가 본 끊김 문구의 출처다. 합성 자체는 맞다.라인 src/server/responses/encrypted-payload.ts 178 -
AGENT_MESSAGE_ROUTING_ENVELOPE가NEW_TASK만 잡는다.MESSAGE봉투는 복구/가독 검사 대상이 아니다.라인 src/server/responses/agent-task-recovery.ts 71 - 복구기
ROUTING_HEADER도NEW_TASK만 받는다. 타입 칸이NEW_TASK로 고정되어 있어MESSAGE는 봉투로 안 잡힌다.라인 src/server/responses/agent-task-recovery.ts 42 -
agentTaskRecoveryConfig는enabled === true가 아니면 null 이다. 기본 설치에서는 복구 기차가 출발하지 않는다.라인 src/server/responses/core.ts 2913 - 네이티브 fallback 이 암호문을 먹을 수 있다고 보고, 최종 라우트 선택 뒤에만 복구한다. 정규 OpenAI forward 면 이 칸을 건너뛴다.
라인 src/server/responses/core.ts 3047 - 읽을 수 없는 암호문 자식은 정규 OpenAI forward 가 아닐 때만 400 으로 닫는다. 작성자 환경(openai-responses + forward)은 여기로 안 떨어진다.
라인 src/server/responses/encrypted-payload.ts 295 -
sanitizeEncryptedContentInPlace가looksLikeBackendCiphertext이면 슬롯을 그대로 둔다. 부모 화면용 Fernet 차단이 아니다.경로 src/server/responses/encrypted-payload.ts structurallyValidFernetTokens -
gAAAAFernet 모양은 이미 검출한다. 그 결과를 부모MESSAGE출력 경로에 연결한 코드는 없다.메인테이너의 판단이 필요한 지점
MESSAGE암호문을 복구 기차에 넣을지, 검출만 하고 구조화 에러로 닫을지. 복구를MESSAGE로 넓히면 평문 오라클이 커진다.adapter_eof뒤에 복호 문맥을 깨는지. [Enhancement]: Mid-stream OpenAI socket resets can break Codex account affinity #919 친화도와 같은 PR 로 묶을지, 출력 차단만 따로 둘지너의 추천
열어 둔다. 라벨을 바꾸지 마라. #919 나 #2744 중복으로 닫지 마라. #2851 이 이 이슈를 고친 것으로 보지 마라. 그 기차는 꺼진 기본값의
NEW_TASK입구이고, 작성자 길은 네이티브 forward 라 복구를 건너뛴다. 고치는 자리는 부모에게 보이는 에이전트MESSAGE출력이다. 구조가 맞는 Fernet 이 Payload 로 나가려 하면unreadable_encrypted_agent_task같은 구조화 에러로 닫아라. 암호문 문자열을 화면에 그리지 마라.recoverEncryptedAgentTask를MESSAGE로 넓히지 마라. PR #2113 을 이 수리로 합치지 마라. types/config 분할에 안 깔리는 좁은 PR 로dev에 올려라.이 댓글은 grok-bot이 작성했습니다
docs(devlog): record #3021 moving from unsolvable to fixed
fix(responses): notice an unreadable MESSAGE reply, not just NEW_TASK (…
lidge-jun commentedon Sep 1, 2026
Landed on dev via #3116 at 5c0c131. Exact-head CI had 0 failures and 0 pending checks; an explicit security review confirmed that MESSAGE detection remains fail-closed and does not widen decryption. Closing this bug as fixed.
github-actions commentedon Sep 1, 2026
Automated translation bookkeeping — detected language: English.
docs(devlog): record the 2026-08-31 non-priority-70 bug triage round (#…
fix(responses): notice an unreadable MESSAGE reply, not just NEW_TASK (…
docs(devlog): record the 2026-08-31 non-priority-70 bug triage round (l…
fix(responses): notice an unreadable MESSAGE reply, not just NEW_TASK (…
docs(devlog): record the 2026-08-31 non-priority-70 bug triage round (l…