Summary
handleSessionDiff increments opencode.lines_of_code.count by fileDiff.additions/deletions on every session.diff event. But opencode publishes session.diff with the cumulative session diff (first snapshot → latest), not a per-event delta — so the counter accumulates cumulative totals and over-reports line counts on any session with more than one message.
Reproduction
- Install the plugin with
OPENCODE_ENABLE_TELEMETRY=1 against a local OTLP collector (SigNoz, Prometheus, anything).
- Start an opencode TUI session.
- Ask the model to edit a file across multiple turns. Example: edit
test.md six times in a row, ending with a state that differs from baseline by one line added and one line removed.
- Query the counter (e.g. in SigNoz ClickHouse):
SELECT value FROM signoz_metrics.distributed_samples_v4
WHERE metric_name='opencode.lines_of_code.count'
ORDER BY unix_milli
Expected vs. actual
For the scenario above with only +1/-1 net change, opencode's session.diff fires after each message with cumulative totals like:
| Event |
additions in cum diff |
counter .add() |
counter total |
Real session total |
| msg 1 |
1 |
+1 |
1 |
1 |
| msg 2 |
1 |
+1 |
2 |
1 |
| msg 3 |
1 |
+1 |
3 |
1 |
| msg 4 |
0 (revert) |
+0 |
3 |
0 |
| msg 5 |
1 |
+1 |
4 |
1 |
| msg 6 |
1 |
+1 |
5 |
1 |
The counter ends at 5 but the real net is 1. Users summing the counter on dashboards see inflated numbers that grow with message count.
Root cause
src/handlers/activity.ts#L7-L40 treats each session.diff event as carrying a delta, but the event actually carries the cumulative diff of the whole session so far. packages/opencode/src/session/summary.ts in upstream opencode confirms this — it passes the same cumulative diffs array into bus.publish(Session.Event.Diff, { sessionID, diff: diffs }) every time it recomputes the summary.
Proposed fix
Track the last-seen cumulative per session and emit only the positive delta to the counter. This also enables a parallel Gauge that reports the current cumulative total (for "what has this session produced so far" panels).
export function handleSessionDiff(e: EventSessionDiff, ctx: HandlerContext) {
let totalAdded = 0
let totalRemoved = 0
for (const d of e.properties.diff) {
totalAdded += d.additions
totalRemoved += d.deletions
}
const prev = ctx.sessionDiffTotals.get(e.properties.sessionID) ?? { additions: 0, deletions: 0 }
ctx.sessionDiffTotals.set(e.properties.sessionID, { additions: totalAdded, deletions: totalRemoved })
const delta = { added: totalAdded - prev.additions, removed: totalRemoved - prev.deletions }
if (delta.added > 0) ctx.instruments.linesCounter.add(delta.added, { /*...*/, type: "added" })
if (delta.removed > 0) ctx.instruments.linesCounter.add(delta.removed, { /*...*/, type: "removed" })
// Optional companion gauge for "current cumulative"
ctx.instruments.linesTotalGauge.record(totalAdded, { /*...*/, type: "added" })
ctx.instruments.linesTotalGauge.record(totalRemoved, { /*...*/, type: "removed" })
}
Cleanup on session.idle / session.error is symmetric to the existing sessionTotals map.
Backwards compatibility
This is a behaviour change for opencode.lines_of_code.count. Existing dashboards that sum() the counter will see smaller numbers (the correct numbers). Worth a BREAKING CHANGE: footer on the fix commit so release-please ticks a major version.
Known limitation
Even with this fix, the counter only captures churn that opencode actually publishes via session.diff. Rewrites that happen and then get reverted inside a single message don't generate events — opencode's summary recomputes against the session baseline, not step-by-step — so fine-grained "churn" would need to hook tool.execute.after instead. Not in scope for this bug.
Summary
handleSessionDiffincrementsopencode.lines_of_code.countbyfileDiff.additions/deletionson everysession.diffevent. But opencode publishessession.diffwith the cumulative session diff (first snapshot → latest), not a per-event delta — so the counter accumulates cumulative totals and over-reports line counts on any session with more than one message.Reproduction
OPENCODE_ENABLE_TELEMETRY=1against a local OTLP collector (SigNoz, Prometheus, anything).test.mdsix times in a row, ending with a state that differs from baseline by one line added and one line removed.Expected vs. actual
For the scenario above with only
+1/-1net change, opencode'ssession.difffires after each message with cumulative totals like:additionsin cum diff.add()The counter ends at 5 but the real net is 1. Users summing the counter on dashboards see inflated numbers that grow with message count.
Root cause
src/handlers/activity.ts#L7-L40treats eachsession.diffevent as carrying a delta, but the event actually carries the cumulative diff of the whole session so far.packages/opencode/src/session/summary.tsin upstream opencode confirms this — it passes the same cumulativediffsarray intobus.publish(Session.Event.Diff, { sessionID, diff: diffs })every time it recomputes the summary.Proposed fix
Track the last-seen cumulative per session and emit only the positive delta to the counter. This also enables a parallel Gauge that reports the current cumulative total (for "what has this session produced so far" panels).
Cleanup on
session.idle/session.erroris symmetric to the existingsessionTotalsmap.Backwards compatibility
This is a behaviour change for
opencode.lines_of_code.count. Existing dashboards thatsum()the counter will see smaller numbers (the correct numbers). Worth aBREAKING CHANGE:footer on the fix commit so release-please ticks a major version.Known limitation
Even with this fix, the counter only captures churn that opencode actually publishes via
session.diff. Rewrites that happen and then get reverted inside a single message don't generate events — opencode's summary recomputes against the session baseline, not step-by-step — so fine-grained "churn" would need to hooktool.execute.afterinstead. Not in scope for this bug.