anomalyco / anomalyco/opencode
`openapi.json` no longer documents SSE payloads (`V2Event` / `SessionLogItem`) after the effect beta.107 regen
@neriousy is already working on this.
Since Aug 25, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Summary
I ran into this while refreshing the pinned openapi.json for a .NET SDK I'm building for opencode.
Since the effect 4.0.0-beta.101 → beta.107 bump, the generated packages/protocol/openapi.json no longer contains the actual payload schemas for the two SSE endpoints (v2.event.subscribe, v2.session.log).
Before the bump, the stream data field was represented as a JSON string with a contentSchema pointing back to the full payload union. After the first regen under beta.107, that became an opaque string and the whole event schema tree disappeared with it, 74 component schemas in that single regen.
The wire protocol itself hasn't changed, so nothing is broken at runtime. But anything consuming the OpenAPI document now loses the entire typed event surface.
I dug into the regression, traced it back to the effect upgrade, and verified that the current effect version can still generate everything needed to restore it. I also have a working restore patch and would be happy to send a PR.
Before / after
The break is pretty clean and sits between two adjacent commits.
At d9b81d2233 (2026-08-17, the effect bump itself, the document still carries the previous regen), the stream payload was still fully documented:
// components.schemas
"V2EventJsonString": {
"type": "string",
"contentSchema": { "$ref": "#/components/schemas/V2Event" },
"contentMediaType": "application/json"
},
"V2Event": { "anyOf": [ /* 84 event variants */ ] },
"V2Event.server.connected": { /* ... */ }
// ... full event model tree
Then one commit later, at aca42423d3 (chore: generate, the first regen under beta.107), the same part became:
"V2EventEncoded": {
"type": "string",
"contentMediaType": "application/json"
}
// V2Event, SessionLogItem and every event leaf schema: gone
components.schemas went from 314 to 240 entries in that one regen, and nothing in the document mentions server.connected, or any other event type anymore.
The same thing happened to SessionLogItemJsonString → SessionLogItemEncoded.
What changed
script/generate-openapi.ts itself didn't change between those commits, so I followed the behavior change into effect.
As far as I can tell, there are two changes interacting here:
-
beta.102, Effect-TS/effect#6424 — the SchemaRepresentation overhaul. OpenAPI/JSON Schema generation now projects the encoded side of a schema, and
contentSchemamoved from a structural field to an ordinary annotation (Annotations.Augment.contentSchema).HttpApiSchema.StreamSse's event schema encodesdataas a plain string, but nothing re-attaches the payload schema as that annotation, so the link disappears. -
beta.103, Effect-TS/effect#6781 — the OpenAPI generator stopped emitting component schemas that aren't reachable from a generated root. Once the
contentSchemalink is gone, the event union is no longer reachable from the OpenAPI root and the whole tree gets pruned. -
Also in beta.103, Effect-TS/effect#6782 renamed the fallback definitions from
*JsonStringto*Encoded. That part looks cosmetic, but it happened in the same window.
opencode's own TypeScript clients generate directly from the effect schemas, so this doesn't break anything in-repo. The OpenAPI document is basically the only surface that regressed.
Impact
For anything generating a client from openapi.json, the two streams are now effectively documented as string.
That means the regular OpenAPI surface no longer describes any of the actual event payloads.
The same document is also copied to packages/www/openapi.json, so the public version has the same gap.
I verified the schemas are still recoverable
I also wanted to make sure this wasn't simply a dead end caused by the newer effect representation.
I reproduced generation at the current v2 tip (8c126e98da, effect 4.0.0-rc.111), and all the information is still there:
-
Schema.toJsonSchemaDocument(OpenCodeEvent)still compiles the complete union, 138 definitions, root$ref: V2Event. -
The JSON Schema emitter still understands
contentSchema; it's still a first-class annotation key and gets emitted when present. -
If I merge those compiled definitions back into
components.schemasand re-attachcontentSchema: { "$ref": "#/components/schemas/V2Event" }onV2EventEncoded, the previous OpenAPI shape comes back.
I also compared the two generation paths. Of the 46 compiled schemas that already exist in the document, 45 are byte-identical, so the two pipelines still agree almost completely.
And importantly, this isn't restoring an old snapshot: the resulting union contains the current 85 event variants, including types added after the regression window such as session.viewed and worktree.*.
So the live protocol can still be fully documented with the current effect version.
Possible fix
The restore is fairly small: roughly a ~30-line post-processing step in script/generate-openapi.ts / openapi-stabilize.ts:
- compile the two stream payload schemas,
- merge their definitions into
components.schemas, - set
contentSchemaon the two*Encodedenvelopes.
I already have this working locally and I'm happy to open the PR.
import { Schema } from "effect"
import { OpenCodeEvent } from "../src/groups/event.js"
// after: const doc: any = stabilizeOpenApi(OpenApi.fromApi(ClientApi))
const compiled = Schema.toJsonSchemaDocument(OpenCodeEvent)
// draft-2020-12 refs -> OpenAPI component refs
const { schema, definitions } = JSON.parse(
JSON.stringify({ schema: compiled.schema, definitions: compiled.definitions })
.replaceAll("#/$defs/", "#/components/schemas/"),
)
const components = doc.components.schemas
for (const [name, s] of Object.entries(definitions)) {
components[name] ??= s // 45 of the 46 overlaps are byte-identical already
}
components["V2EventEncoded"] = { ...components["V2EventEncoded"], contentSchema: schema }
// same step for the session.log payload union -> "SessionLogItemEncoded"
Context
For context, I'm building Blind-Striker/opencode-sdk-dotnet, a typed .NET SDK for opencode.
It uses a fail-closed custom generator over opencode's OpenAPI dialect, tagged unions, x-effect-stream SSE framing metadata, cursor pagination, etc., and generates a fully typed client.
The OpenAPI document is intentionally the SDK's sole protocol input.
I had it pinned to a6a712a3 (2026-08-13), and when I tried to refresh that pin to the current v2 tip, the generator refused to ingest the new document because schemas it had previously used to type the streams (EventLogSynced, SessionCreated, …) were simply gone.
That's what led me down this rabbit hole.
External SDKs consuming openapi.json depend on it being a complete description of the protocol, so I figured this was worth reporting.
Happy to send the restore PR.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.