Summary
work_items.retrieve() (and any code path returning a WorkItemDetail) raises a pydantic.ValidationError for any work item that has labels attached, when labels is not explicitly expanded.
Repro
Call the retrieve endpoint for a work item that has one or more labels, without expand=labels:
client.work_items.retrieve(
workspace_slug=workspace_slug,
project_id=project_id,
work_item_id=work_item_id,
)
Result: pydantic.ValidationError — Input should be a valid dictionary or instance of Label — deterministic, not intermittent, for every work item that has labels.
Root cause
The Plane API returns labels as an array of label ID strings when the field isn't expanded (the normal/default response shape). The WorkItemDetail model types labels as list[Label] unconditionally, so pydantic rejects the plain ID strings instead of accepting either shape.
Expected behavior
labels should accept both shapes depending on whether the caller expanded it — e.g. list[Label] | list[str], or the model should coerce string IDs into a minimal Label reference, consistent with how the API actually behaves when expand isn't requested.
Environment
plane-sdk==0.2.19 (PyPI)
- Found via
makeplane/plane-mcp-server (retrieve_work_item / retrieve_work_item_by_identifier tools), but the bug is in the SDK's model, not the MCP server.
- Server tested against: self-hosted Plane Community 1.3.1.
Workaround in use
Using list_work_items (which doesn't hit this validation path) instead of retrieve_work_item*, and filtering client-side.
Summary
work_items.retrieve()(and any code path returning aWorkItemDetail) raises apydantic.ValidationErrorfor any work item that has labels attached, whenlabelsis not explicitly expanded.Repro
Call the retrieve endpoint for a work item that has one or more labels, without
expand=labels:Result:
pydantic.ValidationError—Input should be a valid dictionary or instance of Label— deterministic, not intermittent, for every work item that has labels.Root cause
The Plane API returns
labelsas an array of label ID strings when the field isn't expanded (the normal/default response shape). TheWorkItemDetailmodel typeslabelsaslist[Label]unconditionally, so pydantic rejects the plain ID strings instead of accepting either shape.Expected behavior
labelsshould accept both shapes depending on whether the caller expanded it — e.g.list[Label] | list[str], or the model should coerce string IDs into a minimalLabelreference, consistent with how the API actually behaves whenexpandisn't requested.Environment
plane-sdk==0.2.19(PyPI)makeplane/plane-mcp-server(retrieve_work_item/retrieve_work_item_by_identifiertools), but the bug is in the SDK's model, not the MCP server.Workaround in use
Using
list_work_items(which doesn't hit this validation path) instead ofretrieve_work_item*, and filtering client-side.