Skip to content

OpenAPI spec types session.next.* event timestamp as a number, but the server sends an ISO 8601 string #28847

@faern

Description

@faern

Description

In GET /doc (OpenCode 1.15.7) every session.next.* (EventV2) event schema declares its timestamp field as {"type": "number"}. The server actually sends timestamp as an ISO 8601 datetime string.

For example, EventSessionNextAgentSwitched's inner properties schema:

{
  "type": "object",
  "properties": {
    "timestamp": { "type": "number" },
    "sessionID": { "type": "string", "pattern": "^ses" },
    "agent":     { "type": "string" }
  },
  "required": ["timestamp", "sessionID", "agent"],
  "additionalProperties": false
}

But opencode serve 1.15.7 emits this frame on GET /event (and GET /global/event):

{
  "id": "evt_e4fd04f94001sMpdNAQBQMEwke",
  "type": "session.next.agent.switched",
  "properties": {
    "sessionID": "ses_1b02fb8a3ffeI2yMKTwQF5dXBh",
    "timestamp": "2026-05-22T13:11:52.466Z",
    "agent": "build"
  }
}

timestamp is declared a number but sent as a string.

This affects all 26 EventSessionNext* schemas - timestamp is the shared Base field of every EventV2 event (session.next.agent.switched, session.next.model.switched, session.next.prompted, etc.).

Net effect: a client generated from or validating against openapi.json cannot decode any session.next.* event. At least session.next.agent.switched and session.next.model.switched fire on every prompt, so a strict typed client treats the first one as a fatal decode error and the whole event stream dies on the first prompt.

Likely correct behavior

number is almost certainly the intended type - the fix belongs on the server, not the spec:

  • The field's schema is V2Schema.DateTimeUtcFromMillis (packages/core/src/v2-schema.ts); its encode step is DateTime.toEpochMillis - the wire form is meant to be epoch milliseconds.
  • Every other timestamp in the API is epoch milliseconds (Session.time.created, etc.); session.next.* timestamp is the only one sent as a string.

The ISO string looks like a serialization slip: the SSE path JSON.stringifys the event - so an Effect DateTime renders via toJSON() as an ISO string - instead of encoding through the schema, which would yield the epoch-millis number. Emitting timestamp as epoch milliseconds would make it match both its declared schema and the rest of the API.

Plugins

No response

OpenCode version

1.15.7

Steps to reproduce

  1. opencode serve
  2. Subscribe to GET /event (or GET /global/event).
  3. Prompt any session.
  4. The emitted session.next.* frames carry timestamp as a string, matching no EventSessionNext* schema in GET /doc, which requires a number.

Spec side, in one command:

jq '[.components.schemas | to_entries[]
     | select(.key | startswith("EventSessionNext"))
     | .value.properties.properties.properties.timestamp] | unique' openapi.json

-> [{"type":"number"}] for all 26 event types.

Screenshot and/or share link

No response

Operating System

No response

Terminal

No response

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions