You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Bug]: session time.streamed is never persisted and step.streamed fires after the stream ends, not at first token #57178
time.streamed on assistant messages is never persisted (0 of 1,285 assistant messages in a live DB) and, even when it is, it is published after the whole stream ends — so it measures stream duration, never time-to-first-byte.
Evidence
Live DB: time.created and time.completed present on every assistant message; time.streamed absent from 100% of them.
Code path verified in v2.0.32 and HEAD: durable SessionEvent.Step.Streamed (session-event.ts:345) → published at packages/core/src/session/runner/step.ts:149 (inside the uninterruptible tail, after the stream completes) → projector.ts:699 → message-updater.ts:258-261 (draft.time.streamed = created) → optional schema field (session-message.ts:234). Bus projectors run serially and awaited (bus.ts:400-401); no projector error lines in the log.
Measured latency split on one session: loop → process 0.086–1.14 s; process → stream 1–3 ms; stream → first part3.0–16.1 s (provider TTFB; direct provider requests measured 2.7–4.1 s for minimal prompts).
Client impact: the "waiting for the first reply" timer reads this field, so it reports the entire stream duration as "no reply has started".
Proposed fix
Publish publisher.streamed() on the first published provider event inside Stream.runForEach (step.ts:108-121), exactly once; keep the existing end-of-stream call as a guarded fallback. The recorded-runner golden event order moves session.step.streamed to immediately after session.step.started.
Thanks for the detailed write-up. Before we go further, could you describe briefly, in your own words, what you did, what you expected and what you saw? Two specific questions:
How did you check the database? Please share the exact query or tool and which table you read. In v2, the assistant message is saved as JSON in the data column of the session message table, and the code in both v2.0.32 and dev writes time.streamed there when session.step.streamed is handled. Please also confirm that those 1,285 messages were created by v2 (opencode --version → 2.0.32) and not by a v1 install, whose messages don't have this field. Which provider/model did you use?
About the first-token timing: ending at the end of the response is intended. The schema documents time.streamed as "When the provider response body ended, before tool settlement", a test asserts that timing, and the TUI token-rate and debug metrics use streamed - created as the response duration (see fix(core): include provider wait in token throughput timing #47125). Publishing it at the first token would collapse that duration and inflate tok/s, which is the problem described in tui: token rate uses the final metadata burst instead of model-call duration #46883. A separate time-to-first-token value would be a feature request rather than a fix to this field.
You can reply here with the details, or edit the issue. If you edit it, please also leave a comment saying you've updated it, so we know you're done.
Related issues, ordered by likelihood of relevance:
We couldn't confirm the "never persisted" part. In v2.0.32 and dev the code writes time.streamed to the message data, so we'd need to know how the DB was queried and that the sessions came from v2.
If you can share those details, or want to suggest a separate time-to-first-token value as a feature, please comment here or open a new issue. Thanks for the report.
Closing is fair given that I missed the earlier question. Here is the exact check I used.
Tool: Python 3.13 sqlite3 in read-only URI mode against the live v2 database (...\.opencode-v2\data\opencode.db).
SELECTCOUNT(*) AS assistant,
SUM(json_extract(data, '$.time.streamed') IS NOT NULL) AS with_streamed
FROM message
WHERE json_extract(data, '$.role') ='assistant';
Current result: 190 assistant messages / 0 with time.streamed. The earlier database snapshot quoted in the report had 1,285 assistant messages and the same zero result. The stored v2 message JSON has time.created (and, when completed, time.completed), but no time.streamed key.
I am not disputing your point that recording time.streamed at the end of the response is intended. The remaining actionable part of the report is the session.step.streamed timing: it currently fires after the stream ends, so it cannot represent first provider event / TTFB. A narrower follow-up can cover that without changing time.streamed semantics. I have not reopened this issue or opened a replacement without a maintainer decision.
Reopened. The scope is now the separate session.step.streamed timing question: it should mark the first provider event, not stream end. The time.streamed end-of-stream semantics are not being changed, and the DB query/result is in the previous comment.
I tried to reopen this issue, but GitHub returns “Could not reopen the issue” for this contributor account (author association: CONTRIBUTOR; repository permissions: pull only). Could a maintainer reopen it, or confirm whether a narrowed replacement issue is preferred? The database evidence and the scoped session.step.streamed question are in the comment above.
Activity
opencode-agent commentedon Oct 19, 2026
Thanks for the detailed write-up. Before we go further, could you describe briefly, in your own words, what you did, what you expected and what you saw? Two specific questions:
datacolumn of the session message table, and the code in both v2.0.32 anddevwritestime.streamedthere whensession.step.streamedis handled. Please also confirm that those 1,285 messages were created by v2 (opencode --version→ 2.0.32) and not by a v1 install, whose messages don't have this field. Which provider/model did you use?time.streamedas "When the provider response body ended, before tool settlement", a test asserts that timing, and the TUI token-rate and debug metrics usestreamed - createdas the response duration (see fix(core): include provider wait in token throughput timing #47125). Publishing it at the first token would collapse that duration and inflate tok/s, which is the problem described in tui: token rate uses the final metadata burst instead of model-call duration #46883. A separate time-to-first-token value would be a feature request rather than a fix to this field.You can reply here with the details, or edit the issue. If you edit it, please also leave a comment saying you've updated it, so we know you're done.
Related issues, ordered by likelihood of relevance:
step.streamedto the first provider eventstreamed - createdspan is too short, which movingstreamedearlier would also causetime.streamedat the end of the response body for throughputopencode-agent commentedon Oct 22, 2026
We're closing this as not planned because we haven't heard back on the questions above.
To recap:
time.streamedat the end of the response is intended. The schema documents the field that way, and the token-rate and debug metrics depend on it (see fix(core): include provider wait in token throughput timing #47125 and tui: token rate uses the final metadata burst instead of model-call duration #46883).devthe code writestime.streamedto the message data, so we'd need to know how the DB was queried and that the sessions came from v2.If you can share those details, or want to suggest a separate time-to-first-token value as a feature, please comment here or open a new issue. Thanks for the report.
mradwankhalil commentedon Oct 22, 2026
Closing is fair given that I missed the earlier question. Here is the exact check I used.
Tool: Python 3.13
sqlite3in read-only URI mode against the live v2 database (...\.opencode-v2\data\opencode.db).Current result: 190 assistant messages / 0 with
time.streamed. The earlier database snapshot quoted in the report had 1,285 assistant messages and the same zero result. The stored v2 message JSON hastime.created(and, when completed,time.completed), but notime.streamedkey.I am not disputing your point that recording
time.streamedat the end of the response is intended. The remaining actionable part of the report is thesession.step.streamedtiming: it currently fires after the stream ends, so it cannot represent first provider event / TTFB. A narrower follow-up can cover that without changingtime.streamedsemantics. I have not reopened this issue or opened a replacement without a maintainer decision.mradwankhalil commentedon Oct 22, 2026
Reopened. The scope is now the separate
session.step.streamedtiming question: it should mark the first provider event, not stream end. Thetime.streamedend-of-stream semantics are not being changed, and the DB query/result is in the previous comment.mradwankhalil commentedon Oct 22, 2026
I tried to reopen this issue, but GitHub returns “Could not reopen the issue” for this contributor account (author association: CONTRIBUTOR; repository permissions: pull only). Could a maintainer reopen it, or confirm whether a narrowed replacement issue is preferred? The database evidence and the scoped
session.step.streamedquestion are in the comment above.