Repository navigation
[Feature]: add durable stream-stage timeline and failure attribution to request history #1217
Description
Area
Streaming / request history / observability
Current status
OpenCodex already has several pieces of correlation-safe request observability:
- every request has an OpenCodex
requestId; - request history is durably backed by
usage.jsonland exposed through the paginated/api/request-historyAPI; firstOutputMsrecords semantic TTFT;- failure rows can persist fields such as
errorCode,terminalStatus,closeReason, and redactedupstreamError; - durable
routeDecisiontraces explain why a provider/model/account route was selected; - the live request-log context can classify some terminals using
transportPhaseandterminalSource.
However, the durable request-history record still cannot reconstruct the end-to-end streaming timeline.
In particular, transportPhase and terminalSource are currently live request-log diagnostics rather than durable usage.jsonl fields, and they do not identify the actual failure side precisely enough.
This issue tracks the remaining durable streaming-stage observability.
Problem
When a Codex turn remains on “thinking” or ends with a stream-disconnected error, an operator should be able to determine from one request-history record whether the delay or failure occurred:
- before OpenCodex dispatched upstream;
- while waiting for upstream response headers;
- while reading the upstream response body/SSE stream;
- while transforming or relaying upstream data;
- while writing downstream;
- because the client cancelled;
- during terminal-event delivery.
Today several of these cases can collapse into the same terminal status.
For example:
terminalSource = synthetic
transportPhase = mid_stream
status = 502
does not prove that the provider caused the failure.
A synthetic terminal identifies who manufactured the terminal event, while a broad relay catch may cover both upstream reads and local relay/rewriting work.
Required durable timeline
Add a bounded, payload-free stream timeline to the canonical request-history row.
At minimum record elapsed milliseconds from request acceptance for:
upstreamDispatchMs
upstreamHeadersMs
upstreamFirstByteMs
upstreamFirstSemanticOutputMs
downstreamFirstWriteMs
upstreamEndMs
downstreamEndMs
Existing firstOutputMs may satisfy upstreamFirstSemanticOutputMs if the naming/relationship is explicit and backwards compatible.
All timings are best-effort observability only.
Failure to record a timing must never affect request delivery.
Failure attribution
Add stable bounded enums that identify where the terminal condition originated.
For example:
failureSide:
upstream
relay
downstream
client
local
failureStage:
pre_dispatch
upstream_wait_headers
upstream_read
relay_transform
downstream_write
client_cancel
terminal_delivery
Exact enum names may follow repository conventions, but the contract must distinguish these cases without parsing free-form error text.
transportPhase and terminalSource may remain for compatibility, but they must not be treated as substitutes for causal attribution.
Interpretation
The resulting record should support deterministic conclusions such as:
no upstreamDispatchMs
-> failure occurred before upstream dispatch
upstreamDispatchMs but no upstreamHeadersMs
-> provider/network failure before response headers
upstreamFirstByteMs but no downstreamFirstWriteMs
-> OpenCodex relay/transform path
downstreamFirstWriteMs then upstream_read failure
-> upstream/network mid-stream failure
upstreamEndMs but no downstreamEndMs
-> downstream relay/client path
Example
{
"requestId": "ocx-...",
"status": 502,
"durationMs": 61342,
"firstOutputMs": 9107,
"streamTimeline": {
"upstreamDispatchMs": 12,
"upstreamHeadersMs": 4410,
"upstreamFirstByteMs": 4421,
"downstreamFirstWriteMs": 4423,
"upstreamEndMs": 61340,
"downstreamEndMs": 61342
},
"failureSide": "upstream",
"failureStage": "upstream_read",
"transportPhase": "mid_stream",
"terminalSource": "synthetic"
}Persistence
The timeline and attribution must be part of the canonical durable request-history representation, not only the in-memory /api/logs ring.
They must therefore survive:
ocx stop
ocx start
and remain available from:
GET /api/request-history/:requestId
The request-history SQLite index remains a derived projection; usage.jsonl stays canonical unless the underlying storage contract is intentionally changed separately.
Protocol coverage
Cover both:
- native Responses passthrough;
- translated/provider-adapted streaming paths.
Instrumentation should be defined at shared transport/relay boundaries where possible rather than independently inventing incompatible timelines per adapter.
Non-streaming requests may omit stream-only fields.
Request correlation
The existing OpenCodex requestId remains the primary correlation key.
Where protocol-compatible and not already present, return it to clients as:
x-opencodex-request-id
so a client-visible failure can be matched to the durable request-history record without exposing internal state.
Privacy and bounds
The feature must not persist:
- authorization headers or credentials;
- prompts or request bodies;
- images;
- raw SSE payloads;
- account identifiers that are not already part of the approved history contract;
- resolved peer addresses;
- unbounded exception text;
- arbitrary stack traces.
Timeline fields are numeric timestamps/durations and failure attribution uses closed enums.
Existing redaction and bounded-metadata rules continue to apply.
Relationship to existing work
Already delivered and not part of this issue:
- durable request-history indexing and pagination;
firstOutputMsTTFT;- durable route-decision traces;
- existing terminal status/error diagnostics;
- existing live
transportPhase/terminalSourceclassification.
Related issue #919 established why terminalSource="synthetic" is insufficient for causal attribution: the synthetic event producer is not necessarily the underlying failure source.
#820 remains the broader architecture/performance tracker. This issue owns only correlation-safe streaming-stage observability.
Acceptance criteria
Durable timeline
- Canonical request history records upstream dispatch time.
- Canonical request history records upstream response-header time.
- Canonical request history records first upstream body/SSE receipt.
- Existing semantic TTFT is explicitly represented.
- Canonical request history records first successful downstream write.
- Canonical request history records upstream body end/failure time.
- Canonical request history records downstream end/cancel/failure time.
- Timeline fields survive proxy restart.
Attribution
- Stable bounded failure-side classification exists.
- Stable bounded failure-stage classification exists.
- Upstream header wait is distinguishable from upstream body-read failure.
- Upstream body-read failure is distinguishable from relay/transform failure.
- Relay/transform failure is distinguishable from downstream write failure.
- Client cancellation is distinguishable from upstream failure.
- Terminal-delivery failure is distinguishable from the underlying stream failure.
- Attribution does not depend on parsing arbitrary exception text.
Coverage
- Native Responses passthrough is covered.
- Translated provider streaming is covered.
- Eager and non-eager relay paths produce compatible stage semantics.
- Requests with no semantic model delta remain diagnosable.
- Non-streaming requests remain backwards compatible.
Safety
- Instrumentation is best-effort and cannot fail request delivery.
- No raw request or SSE payload is persisted.
- No credential material is persisted.
- No new unbounded text fields are introduced.
- Added metadata remains within existing request-history size/bounding expectations.
API
-
GET /api/request-history/:requestIdexposes the durable timeline and attribution. - Existing request-history rows without the new fields remain readable.
- Existing pagination/index rebuild behavior remains compatible.
- The OpenCodex request ID can be correlated back from the client response where protocol-compatible.
Activity
github-actions commentedon Aug 7, 2026
Automated translation bookkeeping — detected language: English.
Wibias commentedon Aug 8, 2026
Parent architecture/performance tracker: #820
This issue provides the correlation-safe stage observability needed to distinguish upstream, relay, downstream, cancellation, and runtime-pressure failures.
[-]Feature: Correlation-safe end-to-end streaming stage timeline[/-][+][Feature]: add durable stream-stage timeline and failure attribution to request history[/+]github-actions commentedon Aug 9, 2026
Maintainer decision respected
A maintainer has reopened this issue. The automated closure has been deactivated.
lidge-jun commentedon Aug 19, 2026
리뷰 · 우선순위 64 / 80
현재
dev의 요청 관측은 상관 ID와 TTFT까지는 있습니다.src/server/request-log.ts에firstOutputMs,transportPhase(pre_headers|mid_stream|terminal_sse),terminalSource(upstream|synthetic)가 있고,/api/request-history와usage.jsonl에는firstOutputMs가 내려갑니다. 그러나src/usage와src/routing/history에는transportPhase/terminalSource필드가 없습니다. 라이브 로그 진단이지 내구성 있는 히스토리 행이 아닙니다.이슈가 원하는 것은 한 행만 보고 실패 구간을 가르는 타임라인입니다.
upstreamDispatchMs,upstreamHeadersMs,upstreamFirstByteMs,upstreamFirstSemanticOutputMs,downstreamFirstWriteMs,upstreamEndMs,downstreamEndMs를 요청 수락 시각 기준 경과 ms로 남기자는 요청입니다. 지금terminalSource=synthetic+transportPhase=mid_stream+ 502는 업스트림 잘못인지, 로컬 릴레이/변환인지, 클라이언트 취소인지 증명하지 못합니다.네이티브 신뢰성 관점에서 이 공백은 증상 이슈(#314, #509)와 #820 메모리/동시성 프로그램의 관측 기반입니다. Windows/서비스에서 스트림이 thinking에 머물거나 끊길 때, 재현 없이 히스토리만으로 구간을 못 나누면 패치 우선순위를 잘못 잡습니다. 페이로드를 저장하라는 요청이 아니라 구간 시각만 남기라는 점이 위생적으로도 맞습니다.
구현 위치는 이미 있습니다.
request-log의 라이브 컨텍스트에 구간 시각을 더하고,usage/log.ts직렬화와 history indexer가 같은 필드를 선택적으로 보존하면 됩니다.firstOutputMs는upstreamFirstSemanticOutputMs와 관계를 명시하고 호환을 깨지 말아야 합니다. 타이밍 기록 실패가 요청 전달을 막으면 안 된다는 이슈 제약도 현재 로그 경로의 best-effort 성격과 맞습니다.해결방안은 observe-only 슬라이스입니다. 라이브 필드 확장 →
usage.jsonl선택 직렬화 → history API 노출 순으로 가고, 실패 귀속 라벨은terminalSource와 별도로 “누가 바이트를 잃었는지”를 적어야 합니다. 패치는 추측하지 않으며, 현재 트리에 위 구간 ms 필드는 없습니다.이 댓글은 grok-bot이 작성했습니다
fix(usage): clean up stream timeline normalization (lidge-jun#1217)
feat(usage): add durable stream timeline and failure attribution to r…
fix(usage): clean up stream timeline normalization (lidge-jun#1217)
fix(usage): enforce strict validation on transportPhase and terminalS…
devlog: WP8 execution and the program's closing reconciliation
58 remaining items
feat(usage): add durable stream timeline and failure attribution to r…
fix(usage): clean up stream timeline normalization (lidge-jun#1217)
fix(usage): enforce strict validation on transportPhase and terminalS…
fix(usage): populate stream timeline and attribution from streaming e…
fix(usage): preserve attempt-relative timeline on retry and set trans…
fix(relay): isolate inspected source attribution and gate semantic ti…
docs(structure): document stream timeline and failure attribution per…
devlog: WP8 execution and the program's closing reconciliation
feat(usage): add durable stream timeline and failure attribution to r…
fix(usage): clean up stream timeline normalization (lidge-jun#1217)
fix(usage): enforce strict validation on transportPhase and terminalS…
fix(usage): populate stream timeline and attribution from streaming e…
fix(usage): preserve attempt-relative timeline on retry and set trans…
fix(relay): isolate inspected source attribution and gate semantic ti…
docs(structure): document stream timeline and failure attribution per…