Skip to content

nullable enums reach Gemini via OpenCode as the string "null" #369

Closed

Description

@ViperJuice

Found by the 3.0.0 pre-release host smoke test (#301).

Since #300 (piece A of #236), optional enum arguments advertise null as an enum member, e.g. refresh.source: {"enum": ["claude_config","custom",null], "type": ["string","null"]}. Four enums are affected on main: catalog_search.filters.risk_max, refresh.source, set_startup_policy.source, sync_environment.platform.

OpenCode 1.18.34's Google provider rewrites schemas for Gemini. Every type: [X,"null"] becomes anyOf: [{"type": X}] (null dropped), and null inside an enum becomes the string "null" (captured request: "enum": ["claude_config","custom","null"]). A Gemini model can then send source: "null", which pmcp would likely refuse. Whether the real Gemini API accepts the rewritten payload was not verified (no Gemini key on the test host).

Options

Leave null out of enum and rely on the type array (null is still accepted through the type), or emit anyOf: [{"enum":[...]},{"type":"null"}]. Check against each host family's schema conversion, and add a schema test that no enum contains null. This is likely a small fix; it could go in a 3.0.x.

Activity

  1. ViperJuice commented on Oct 5, 2026

    @ViperJuice
    CollaboratorAuthor

    The plan is merged as b9bf9d3 (#370). All four review seats agreed on rev 2.

    It covers:

    • Every nullable argument is advertised as anyOf: [X, {"type": "null"}]. null never appears in an enum, const, default or type array.
    • gate_error_for keeps the 3.0 gate's messages, paths and audit fields identical. A re-fuzz of about 115k multi-error calls found 0 differences.
    • OpenCode's Google path now sends {type: X, nullable: true} and no "null" strings, verified against the real 1.18.34 binary.

    Next is the implementation PR, built from the proven spike. It also takes the two review nits: the fallback will cover the final ranking, and the CHANGELOG clause order will be fixed. The aim is to land it before the 3.0.0 tag.

  2. ViperJuice commented on Oct 5, 2026

    @ViperJuice
    CollaboratorAuthor

    Fixed in 23edd92 (#371), which lands before the 3.0.0 tag.

    All 42 nullable arguments are now advertised as anyOf: [X, {"type": "null"}], and null no longer appears in any enum, const, default or type array. OpenCode's Google provider now sends {type: X, nullable: true}, with no "null" strings.

    The gate's behaviour is unchanged: it accepts and refuses the same calls and reports the same message, keyword and path as 3.0. A fuzz of about 115k calls found 0 differences. Enum messages no longer list None.

    Still unverified: live acceptance by the native Gemini API. agy can't be tested because of #368, which rejects dotted tool names.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions