Skip to content

model set: --input-modalities accepts raw integers (1, 2, 4, 8) despite intended rejection #1674

Description

@Aaronontheweb

Summary

netclaw model set --input-modalities / --output-modalities accept raw integers for single-flag values, contrary to the intended contract stated in the code's own comment.

Repro

Verified against netclaw 0.25.0-alpha.onnx.7 (commit 5c7278f) in an isolated NETCLAW_HOME:

$ netclaw model set main local qwen3:30b --input-modalities 1
Set main model to local/qwen3:30b          # exit 0

Resulting definition — the integer is silently coerced to a named flag:

"local-qwen3-30b": {
  "Provider": "local",
  "ModelId": "qwen3:30b",
  "Provenance": "Manual",
  "InputModalities": "Text"
}

For contrast, these are correctly rejected:

$ netclaw model set main local qwen3:30b --input-modalities 3
Error: invalid modalities '3'. Use a comma-separated list of: Text, Image, Audio, Video (...)   # exit 1

$ netclaw model set main local qwen3:30b --input-modalities Bogus
Error: invalid modalities 'Bogus'. ...   # exit 1
Input Actual Expected
1, 2, 4, 8 accepted → Text/Image/Audio/Video rejected
3 (composite), 0, 99, Bogus rejected rejected

Cause

ModelCommand.cs:313-315 guards with !Enum.IsDefined(parsed). Enum.IsDefined matches declared member values, so single-flag integers (1, 2, 4, 8) are declared members of ModelModality and pass. Composite 3 is not a declared member, so it fails — which makes the rejection of 3 a coincidence of compositeness rather than the intended integer check.

The comment at ModelCommand.cs:311-312 asserts "a mistyped or scripted number is not silently coerced", which overstates the actual contract.

Why it matters

Enum.TryParse accepting numeric strings is the same class of bug that #1660 fixed for --verification-kind (where Enum.TryParse accepted "1" and could not parse header-secret). This is the surviving instance of that pattern.

The JSON schema is already stricter than the CLI — netclaw-config.v1.schema.json:876 uses ^(Text|Image|Audio|Video)(\s*,\s*(Text|Image|Audio|Video))*$, which is case-sensitive and integer-rejecting. The CLI is the outlier.

Impact is limited by normalization: values round-trip through input.ToString() (ModelEntryWriter.cs:480), so 1 lands on disk as canonical "Text" and does not corrupt config. This is a contract/validation bug, not a data-integrity bug.

Test gap

Set_InvalidOptions_ReturnErrorWithoutWriting (ModelCommandTests.cs:443) exercises only "3" — the one invalid integer that passes for the wrong reason. "1" is untested.

Suggested fix

Reject input that parses as an integer literal before calling Enum.TryParse, and add cases for 1/2/4/8 to the test above.

Docs impact

netclaw-dev/netclaw-website#83 asks docs to state that raw integers are rejected. Until this is fixed, that claim would be false, so the docs will document named flags only and stay silent on the integer path. Tracked in netclaw-dev/netclaw-website#86.

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