Skip to content

fix(telemetry): a second export is a second plugin - #785

Merged
blafourcade merged 1 commit into
nextfrom
fix/a-second-export-is-a-second-plugin
Sep 6, 2026
Merged

fix(telemetry): a second export is a second plugin#785
blafourcade merged 1 commit into
nextfrom
fix/a-second-export-is-a-second-plugin

Conversation

@blafourcade

Copy link
Copy Markdown
Contributor

What

OpenCode loads every function-valued named export of a file in plugin/ as a plugin factory of its own. opencode-plugin.js exported journalCallFor beside AiddTelemetry as a spawn-free test seam; OpenCode called it with one argument, received null, and died reading .auth off it before any session started.

Installing this framework for OpenCode therefore made the tool it measures unusable.

Measured

Against opencode 1.14.20, in a freshly installed scratch project:

file in .opencode/plugin/ opencode run
export const Stub = async () => ({ event }) runs
+ export function helper(){ return null } TypeError: null is not an object (evaluating 'S.auth')
+ export const helper = { a: 1 } (not a function) runs
opencode-plugin.js as shipped dies at load

The second half

The same run exposed the rest of the fault. OpenCode publishes session.created on its own bus and never delivers it to a plugin's event hook, and opencode run is always such a session. So the journal never received a session_start, never created the run file the rest of the session appends to, and dropped the turn-end and every task declaration after it — while telemetryLocalRead declared the tool covered and aidd telemetry read, which reads only sessions the run journal knows, could never find one.

The first call a session produces now opens it, carrying the directory that call was already going to use. Never a new guess: for a session no session.created named, that is the plugin's own init-time directory, exactly what journalCallFor already handed turn-end and tool-used.

What is left is only what session.created alone could have said, and the README now says it: on a server serving more than one directory, a session it never announced is journalled under the plugin's own init-time directory rather than its own. The guard that pinned the old limit sentence now pins both the new fact and what it still costs.

Verified

End to end on a scratch repository with an isolated sink — the real sink was never written.

A real opencode run produced the first OpenCode journal this project has ever recorded:

{"type":"session_start","at":"2026-09-06T05:29:27Z","tool":"opencode","vendor_id":"ses_…"}
{"type":"task_declared","at":"2026-09-06T05:29:27Z","path":"aidd_docs/tasks/…/opencode.md"}
{"type":"turn_end","at":"2026-09-06T05:29:29Z"}

and aidd telemetry read answered OpenCode: read (3 new of 3).

The session_start came from the task declaration, not from turn_end — the case the second new test covers.

Gates

  • node --test "scripts/__tests__/**/*.test.js" — 380/380
  • cli vitest — 3501/3501, run twice: after the code change, and again after the doc changes, since the golden build matrix embeds the shipped plugin file
  • biome check on every changed file — clean
  • check-markdown-links.js — 0 broken in 797 files
  • CATALOG.md and docs/prompts-documentation.md regenerate unchanged

The six new tests were written first and were red for the reason they name before the change landed.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp

OpenCode loads every function-valued named export of a file in `plugin/` as
a plugin factory of its own. `opencode-plugin.js` exported `journalCallFor`
beside `AiddTelemetry` as a spawn-free test seam; OpenCode called it with one
argument, got `null`, and died reading `.auth` off it before any session
started. Measured against opencode 1.14.20 in a freshly installed project:

    export const Stub = async () => ({ event })          -> runs
    + export function helper(){ return null }            -> TypeError: null
                                     is not an object (evaluating 'S.auth')
    + export const helper = { a: 1 }                     -> runs

So installing this framework for OpenCode made the tool it measures
unusable. The seam now rides on the plugin function as a property, leaving
one export, and a test asserts that only `AiddTelemetry` is function-valued.

The same run showed the second half of the fault. OpenCode publishes
`session.created` on its own bus and never delivers it to a plugin's event
hook, and `opencode run` is always such a session, so the journal never
received a `session_start`, never created the run file the rest of the
session appends to, and dropped the `turn-end` and every task declaration
after it. `telemetryLocalRead` declared the tool covered while `aidd
telemetry read`, which reads only sessions the run journal knows, could
never find one. The first call a session produces now opens it, carrying the
directory that call was already going to use — never a new guess. What is
left is only what `session.created` alone could have said, and the README
says it: on a server serving more than one directory, a session it never
announced is journalled under the plugin's own init-time directory.

Verified end to end on a scratch repository, isolated sink: a real
`opencode run` wrote its first journal ever — `session_start`,
`task_declared`, `turn_end` — and `aidd telemetry read` answered
"OpenCode: read (3 new of 3)".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VWNxk63AGKkqE8HRqHLjGp
@blafourcade
blafourcade requested a review from a team as a code owner September 6, 2026 05:37
@blafourcade
blafourcade merged commit ba845f8 into next Sep 6, 2026
17 checks passed
@blafourcade
blafourcade deleted the fix/a-second-export-is-a-second-plugin branch September 6, 2026 06:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant