Skip to content

[Bug]: session time.streamed is never persisted and step.streamed fires after the stream ends, not at first token #57178

Closed as not planned

Description

@mradwankhalil

Summary

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 part 3.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.

Tests

session-runner-recorded.test.ts and session-prompt.test.ts: 44 pass / 0 fail; tsgo --noEmit -p packages/core clean.

Activity

  1. opencode-agent commented on Oct 19, 2026

    @opencode-agent
    Contributor

    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:

    1. 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?
    2. 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:

  2. opencode-agent commented on Oct 22, 2026

    @opencode-agent
    Contributor

    We're closing this as not planned because we haven't heard back on the questions above.

    To recap:

    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.

  3. mradwankhalil commented on Oct 22, 2026

    @mradwankhalil
    ContributorAuthor

    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).

    SELECT COUNT(*) 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.

  4. mradwankhalil commented on Oct 22, 2026

    @mradwankhalil
    ContributorAuthor

    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.

  5. mradwankhalil commented on Oct 22, 2026

    @mradwankhalil
    ContributorAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions