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_start — packages/opencode/src/session/prompt.ts:953
task_classified — packages/opencode/src/session/prompt.ts:1004
step is not session-scoped. It is declared inside loop():
let step = 0 — packages/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:
- Check whether the session already has prior assistant messages before emitting — a session that has already produced output is not starting.
- Keep a per-session "already emitted" claim in the telemetry module, similar to how other once-per-session state is tracked.
- 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.
Summary
session_startandtask_classifiedare 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, andtask_classifiedre-classifies intent on every turn instead of once from the opening message.Detail
Both events are emitted from a
if (step === 1)block inpackages/opencode/src/session/prompt.ts:939:session_start—packages/opencode/src/session/prompt.ts:953task_classified—packages/opencode/src/session/prompt.ts:1004stepis not session-scoped. It is declared insideloop():let step = 0—packages/opencode/src/session/prompt.ts:364step++—packages/opencode/src/session/prompt.ts:498and
loop()is invoked once per prompt, i.e. once per user turn. Sostep === 1means "first model step of this turn", and the block runs again for every subsequent message in the same session.Impact
session_startovercounts sessions. A session with N user turns emits Nsession_startevents, all carrying the samesession_id.session_startcounts is proportionally wrong. De-duplicating bysession_idat 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_classifiedreports the intent of whatever the user most recently typed, not the task the session was opened for. Itsconfidencedistribution is also skewed by short follow-up messages ("thanks", "yes", "try again") being classified as tasks.classifyTaskIntentregex 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 === 1block). Threesession_startevents are emitted with identicalsession_id.Suggested fix
Gate both events on something session-scoped rather than on
step. Options, roughly in order of preference:Note that
packages/opencode/src/session/prompt.ts:1088also usesif (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.