Skip to content

session_start and task_classified telemetry fire once per user turn, not once per session #1047

Description

@sahrizvi

Summary

session_start and task_classified are emitted once per user turn, not once per session. Session counts in telemetry are therefore inflated by roughly the average number of turns per session, and task_classified re-classifies intent on every turn instead of once from the opening message.

Detail

Both events are emitted from a if (step === 1) block in packages/opencode/src/session/prompt.ts:939:

  • session_startpackages/opencode/src/session/prompt.ts:953
  • task_classifiedpackages/opencode/src/session/prompt.ts:1004

step is not session-scoped. It is declared inside loop():

  • let step = 0packages/opencode/src/session/prompt.ts:364
  • step++packages/opencode/src/session/prompt.ts:498

and loop() is invoked once per prompt, i.e. once per user turn. So step === 1 means "first model step of this turn", and the block runs again for every subsequent message in the same session.

Impact

  • session_start overcounts sessions. A session with N user turns emits N session_start events, all carrying the same session_id.
  • Any funnel or retention metric built on session_start counts is proportionally wrong. De-duplicating by session_id at query time recovers the true count, so historical data is salvageable, but no current dashboard is doing that unless it was written with this in mind.
  • task_classified reports the intent of whatever the user most recently typed, not the task the session was opened for. Its confidence distribution is also skewed by short follow-up messages ("thanks", "yes", "try again") being classified as tasks.
  • Cost: an extra pair of tracked events per turn, plus the classifyTaskIntent regex sweep over up to 2000 characters per turn.

Reproduction

Start a session, send three messages, and inspect the emitted telemetry (or set a breakpoint on the step === 1 block). Three session_start events are emitted with identical session_id.

Suggested fix

Gate both events on something session-scoped rather than on step. Options, roughly in order of preference:

  1. Check whether the session already has prior assistant messages before emitting — a session that has already produced output is not starting.
  2. Keep a per-session "already emitted" claim in the telemetry module, similar to how other once-per-session state is tracked.
  3. Emit at session creation instead of in the prompt loop, which is where the event name says it belongs.

Note that packages/opencode/src/session/prompt.ts:1088 also uses if (step === 1), but that one is intentionally per-loop() (it traces the system prompt once per loop invocation, and the comment there says so). Only the telemetry block at line 939 is affected.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions