Skip to content

structure() raises StructureHandlerNotFoundError for real InitializeResult with notebookDocumentSync (NotebookDocumentFilterWithCells.notebook union) #462

Open

Description

@vivainio

Versions: lsprotocol 2025.0.0, cattrs 26.1.0, attrs 26.1.0, Python 3.14.2

converter.structure(...) raises StructureHandlerNotFoundError when structuring a real InitializeResult whose capabilities.notebookDocumentSync.notebookSelector contains an entry with only cells (no notebook key) — i.e. the legal "cells-only" NotebookDocumentFilterWithCells shape. Captured verbatim from a real server (ty server, Astral's Python language server) but doesn't depend on it — reproduces with just lsprotocol + cattrs:

from lsprotocol import converters, types

conv = converters.get_converter()
raw = {
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
        "capabilities": {
            "notebookDocumentSync": {
                "notebookSelector": [{"cells": [{"language": "python"}]}]
            }
        }
    },
}
conv.structure(raw, types.InitializeResponse)

Traceback (innermost frame):

cattrs.errors.StructureHandlerNotFoundError: Unsupported type: str | lsprotocol.types.NotebookDocumentFilterNotebookType | lsprotocol.types.NotebookDocumentFilterScheme | lsprotocol.types.NotebookDocumentFilterPattern | None. Register a structure hook for it.
Structuring typing.Sequence[lsprotocol.types.NotebookDocumentFilterWithNotebook | lsprotocol.types.NotebookDocumentFilterWithCells] @ index 0

cattrs can't find/build a structure hook for NotebookDocumentFilterWithCells.notebook's declared union type even though the input omits that key entirely (so the field should just take its default). Every InitializeResponse a server advertises notebookDocumentSync capabilities in — regardless of whether the client cares about notebooks at all — currently fails to structure.

Activity

  1. vivainio commented on Aug 8, 2026

    @vivainio
    Author

    For anyone triaging: this looks related to #259 but isn't a duplicate. #259 was about disambiguating the outer union — which NotebookDocumentSyncOptions.notebookSelector[i] variant a given entry is (NotebookDocumentFilterWithNotebook vs NotebookDocumentFilterWithCells) — fixed via a custom dispatch hook in 2023, and that part does work correctly today: the traceback above shows cattrs correctly resolving to NotebookDocumentFilterWithCells.

    The failure here is one level deeper: once inside NotebookDocumentFilterWithCells, its optional notebook field has its own union type (str | NotebookDocumentFilterNotebookType | NotebookDocumentFilterScheme | NotebookDocumentFilterPattern | None), and that union has no structure hook registered at all — even though the field is simply absent from the input and should resolve to None. Different error class too: #259 raised ValueError: has no usable unique attributes from the disambiguator; this raises StructureHandlerNotFoundError for a field-level union with no hook.

    So the 2023 fix covered the outer selector-list case but the nested notebook field's union seems to have been missed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions