Repository navigation
Vision sidecar silently truncates valid captions at the 64 KiB SSE limit #4727
Description
Client or integration
Codex App
Area
Streaming
Summary
A normal vision-sidecar response is silently cut off before its terminal response.completed event because parseSidecarSSE limits total raw SSE bytes to 64 KiB. describeImage accepts the partial deltas as a successful description, and the text-only target model receives an incomplete caption without any truncation indication. In the observed session this led the target model to crop a screenshot into three parts and retry reading it.
Expected: ordinary completed captions should survive SSE framing overhead while parsing remains bounded. When a safety limit really is reached, the vision path should explicitly identify incomplete output rather than cache/render it as a complete successful description.
This is distinct from DESC_MAX_CHARS = 2000: the captured complete caption was only 1488 characters, but the parser returned 725. The raw-stream limit was introduced in #1376. Existing configuration-parity issue #1201 does not cover this data-loss behavior.
Reproduction
Deterministic offline reproduction (no credentials, images, or network calls required): save the following as repro-sidecar.ts at the repository root and run bun repro-sidecar.ts. Tested against the installed 2.56.0 parser.
import { parseSidecarSSE } from "./src/web-search/parse";
const text = "Readable screenshot text. ".repeat(60);
const frame = (data: unknown) => `data: ${JSON.stringify(data)}\n\n`;
let wire = "";
for (let i = 0; i < text.length; i += 3) {
wire += frame({type: "response.output_text.delta", item_id: "msg_synthetic", output_index: 0, content_index: 0, sequence_number: i / 3, delta: text.slice(i, i + 3)});
}
wire += frame({type: "response.completed", response: {status: "completed", output: [{type: "message", content: [{type: "output_text", text}]}]}});
const result = await parseSidecarSSE(new Response(wire));
console.log({wireBytes: Buffer.byteLength(wire), expectedChars: text.length, actualChars: result.text.length, error: result.error ?? null});Actual: 75436 wire bytes, expected 1560 characters, actual 1386 characters, error null.
Original observation: enable visionSidecar with gpt-5.6-luna and route an image-bearing turn to a text-only deepseek-v4.1-flash model. A tall screenshot containing readable text triggered the problem. Capturing the complete upstream response separately and replaying the same bytes twice through the installed parser reproduced exactly the same 725-character partial result each time. Live model output varies; the synthetic reproduction above is deterministic.
Version
@bitkyc08/opencodex 2.56.0
Operating system
macOS 27.0 (26A5425a), arm64
Provider and model
Vision: OpenAI Responses / gpt-5.6-luna (low); target: OpenCode Go / deepseek-v4.1-flash
Logs or error output
Synthetic reproduction:
wireBytes=75436 expectedChars=1560 actualChars=1386 error=null
Aggregate metrics from the separately captured live response:
wireBytes=138693
response.output_text.delta events=467
terminal event=response.completed; status=completed
complete description chars=1488
parseSidecarSSE result chars=725; error=null
replay 1 chars=725; replay 2 chars=725
2000-character rendering clamp did not activateScreenshots and supporting files
The credential-free synthetic reproduction above replaces the original screenshot and captured stream, which contain private conversation content. No private image or raw response is attached.
Relevant code: src/web-search/parse.ts (MAX_SIDECAR_RESPONSE_BYTES and parseSidecarSSE), src/vision/describe.ts (accepts partial parsed text), src/vision/index.ts (renders/caches it).
Redacted configuration
{
"visionSidecar": {
"enabled": true,
"model": "gpt-5.6-luna",
"reasoning": "low",
"maxDescriptionsPerTurn": 8,
"timeoutMs": 45000
}
}Checks
- I searched existing issues and documentation.
- I removed secrets, tokens, account details, request credentials, and personal data.
Activity
lidge-jun commentedon Sep 15, 2026
리뷰 · 우선순위 76 / 80
이슈 #4727은 vision sidecar가 SSE를 읽을 때 원문 바이트 상한 64 KiB에 걸리면, 완성된 캡션인데도 중간에서 잘린 텍스트를 성공 설명으로 받아들이는 문제를 보고한다. 제보자 재현은 자격증명·이미지·네트워크 없이
parseSidecarSSE만 돌린다. 짧은 델타를 많이 보내면 와이어는 약 75 KiB인데 파서가 에러 없이 일부 글자만 돌려준다. 실제 세션에서도 완성 캡션 1488자인데 파서 결과가 725자로 고정 재현됐고, 텍스트 전용 타깃 모델이 잘린 설명을 보고 스크린샷을 잘라 재시도하는 부작용까지 났다.지금
devHEAD(45cfb04e9, package 2.57.0)에서 경로는 이렇다.src/web-search/parse.ts36행MAX_SIDECAR_RESPONSE_BYTES = 64 * 1024.parseSidecarSSE(약 213행)는 청크를 받을 때마다 남은 바이트만 받아 디코드하고, 283–288행에서 한도에 닿으면 리더를 취소한 뒤 break한다. 주석은 완전한 이벤트는 남기고 미완성 줄은 버린다고 하지만, 반환부(약 295–314행)는response.completed/ done / delta 중 있는 텍스트를 고르고acc.error가 있을 때만 error를 붙인다. 바이트 한도 자체는 error로 남지 않는다. 그래서 한도 앞에서 모은 delta만으로 부분 텍스트가 정상처럼 나간다.src/vision/describe.ts약 121–127행은parseSidecarSSE결과를 받고, 텍스트가 비어 있고parsed.error가 있을 때만 실패로 돌린다. 부분 텍스트에 에러가 없으면 성공으로 반환한다.src/vision/index.ts49행DESC_MAX_CHARS = 2000과 약 347행successfulText캐시 경로도outcome.error가 없으면 그 텍스트를 완성 설명으로 캐시·렌더한다. 이슈가 말하듯 이 버그는 2000자 표시 클램프와 다르다. 완성본이 1488자여도 SSE 프레임 오버헤드 때문에 원문 64 KiB 한도에 먼저 걸린다. #1376에서 넣은 안전 한도가, 평범한 완료 캡션까지 조용히 자르는 쪽으로 쓰이고 있다.참고로 Anthropic vision 경로(
src/vision/anthropic-describe.ts)는 같은 64 KiB 한도를 쓰지만, 스트림 이상 종료 시 부분 텍스트를 버리고 error outcome을 돌려 불완전 설명을 캐시하지 않는다는 주석(review F1)이 있다. 공유parseSidecarSSE(OpenAI Responses sidecar / web-search와 공용)만 한도 hit를 침묵 성공으로 남긴다. 설정 패리티 이슈 #1201과도 별개다. 타입/config 분할에 무효화될 자리 아니다. 데이터 손실이 조용해서 우선순위는 높다.라인 36 (
src/web-search/parse.tsMAX_SIDECAR_RESPONSE_BYTES) - 원문 SSE 64 KiB 상한이 프레임 오버헤드를 포함한 값이라, 짧은 완료 캡션도 잘릴 수 있다라인 283–288 (
parseSidecarSSE) - 한도 도달 시 리더만 끊고acc.error를 세우지 않는다라인 295–314 (
parseSidecarSSE반환) - 부분 delta를 성공 텍스트로 돌려 truncation 표시가 없다라인 121–127 (
src/vision/describe.ts) - 부분 텍스트와 에러 없음을 성공 설명으로 수용한다라인 347–352 (
src/vision/index.ts) -outcome.error가 없으면 잘린 텍스트를 캐시·렌더한다DESC_MAX_CHARS(index.ts 49) - 표시 클램프와 별개이며, 이번 손실의 원인이 아니다메인테이너의 판단이 필요한 지점
parseSidecarSSE로 둘지(web-search에도 영향), visiondescribe.ts에서만 불완전을 거절할지너의 추천
이슈는 유효하고 재현이 결정적이다. 첫째,
parseSidecarSSE가 바이트 한도로 끊으면 error 또는 incomplete 표시를 반드시 세운다. 둘째, vision은 그 경우 부분 텍스트를 성공·캐시하지 않는다. 셋째, 필요하면 한도를 현실적인 완료 캡션과 SSE 오버헤드에 맞게 올리되 상한은 남긴다. 제보자 synthetic 스크립트를 테스트로 고정하면 좋다. #1201과 묶지 말고 이 데이터 손실만 좁게 고치는 PR을 받는 쪽을 권한다. close-don't-rebase 해당 없음.이 댓글은 grok-bot이 작성했습니다
Merge pull request #4752 from lidge-jun/codex/pw1-vision-sidecar-trun…
lidge-jun commentedon Sep 16, 2026
Landed via #4752 at 112d2ea
fix(vision): fail closed on truncated sidecar captions (lidge-jun#4727)…
fix(vision): fail closed on truncated sidecar captions (lidge-jun#4727)…
Merge pull request lidge-jun#4752 from lidge-jun/codex/pw1-vision-sid…