Skip to content

fix: tolerate null for optional strings in wire models (v0.25.4) - #64

Merged
rhiever merged 1 commit into
mainfrom
fix/nullable-wire-strings
Jul 30, 2026
Merged

fix: tolerate null for optional strings in wire models (v0.25.4)#64
rhiever merged 1 commit into
mainfrom
fix/nullable-wire-strings

Conversation

@rhiever

@rhiever rhiever commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

The bug

Clearing outcome on a single template breaks goodeye templates list and goodeye templates search entirely:

$ goodeye templates list
unexpected error: ValidationError: 1 validation error for TemplateList
items.1.outcome
  Input should be a valid string

20 fields in wire.py were typed as a bare str with an "" default. The default covers an absent key, but Pydantic still rejects an explicit null. Because validation happens on the list, one cleared row takes down the whole response, including rows that parsed fine.

The server is correct here; the API returns null for a cleared nullable column, and the MCP client reads it without complaint.

Blast radius

templates list and templates search fail outright for any account owning one template with a cleared outcome. The same latent bug exists on description, and on the skills-side models (WorkflowSummary, WorkflowDetail, WorkflowSearchItem).

The fix

Add a NullableStr alias (a BeforeValidator coercing None to "") and apply it to every optional string field that already defaulted to "".

  • Consumers keep a plain str, so call sites that format or concatenate the value stay safe (e.g. table.add_row(item.outcome, ...) in commands/templates.py).
  • Absent and present values behave exactly as before; only null changes meaning.
  • Non-string types are still rejected, so this does not become an anything-goes field.

This matches the module's stated intent that the wire models be "deliberately minimal and permissive so minor additive server changes do not break old CLI releases."

Tests

New tests/test_wire_nullable_strings.py, 11 cases: per-field null tolerance, the list-level case where one null row must not fail its siblings, search responses, unchanged behavior for absent/present values, and a guard that wrong types still raise.

Full suite: 1136 passed. ruff, ruff format, and pyright all pass.

Release

Version bumped 0.25.3 -> 0.25.4 (patch: no API or behavior change beyond accepting input that previously crashed).

🤖 Generated with Claude Code

Optional server columns come back as an explicit JSON null once a caller
clears them, but 20 wire fields were typed as a bare `str` with an ""
default. Pydantic rejects null for those, and because the failure happens
during list validation, one cleared row takes down the entire response:

    $ goodeye templates list
    ValidationError: 1 validation error for TemplateList
    items.1.outcome
      Input should be a valid string

Clearing `outcome` on a single template broke `templates list` and
`templates search` outright, including rows that parsed fine.

Add a `NullableStr` alias (BeforeValidator coercing None to "") and apply
it to every optional string field that already defaulted to "". Consumers
keep a plain `str`, so callers that format the value stay safe, and
non-string types are still rejected.

This matches the module's stated intent that the wire models be
"deliberately minimal and permissive so minor additive server changes do
not break old CLI releases."

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@rhiever
rhiever merged commit cf1370a into main Jul 30, 2026
1 check passed
@rhiever
rhiever deleted the fix/nullable-wire-strings branch July 30, 2026 21:55
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