Skip to content

fix(tui): stop fleet readonly schema probe from auto-vivifying null enums - #5944

Merged
Hmbown merged 4 commits into
Hmbown:mainfrom
gaord:fix/fleet-bash-schema-null
Sep 6, 2026
Merged

fix(tui): stop fleet readonly schema probe from auto-vivifying null enums#5944
Hmbown merged 4 commits into
Hmbown:mainfrom
gaord:fix/fleet-bash-schema-null

Conversation

@gaord

@gaord gaord commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Fix project_readonly_evidence_schema: it probed for an action enum with IndexMut, which auto-vivifies missing keys as null. A read-only Fleet worker projecting a lowercase bash tool then carried properties.action = {"enum": null}, which strict OpenAI-compatible validators reject. Probe with non-mutating get_mut instead.

Testing

  • cargo fmt --all -- --check
  • cargo test -p codewhale-tui --lib fleet_readonly_reviewer_wire_catalog_carries_no_null_schema_fields

Note: main currently has 5 pre-existing nonminimal_bool clippy errors (tools/subagent, tui/ui/apply, tui/views/fleet_roster, tui/phase_strip), unrelated to this change.

No-Issue: community fix from a live Fleet 400 report; no tracking issue.

Maintainer follow-up (2026-09-06)

Rebased onto current main and applied the two review nits in a signed follow-up commit: the probe uses pointer_mut("/properties/action/enum") like tools/subagent, and the Run arm uses get_mut so it cannot write "properties": null. Changelog receipt added crediting @gaord.

Gate: cargo test -p codewhale-tui --lib -- tools::registry 51 passed / 0 failed; fmt, clippy -D warnings, and sync-changelog --check clean.

@gaord
gaord requested a review from Hmbown as a code owner September 6, 2026 08:14

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

@Hmbown Hmbown left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed at 8104a75b0. The bug is real, the fix is correct, and I reproduced both states locally. Approving in substance — two small reuse notes and a DCO trailer below.

What I verified

I checked the head out into a detached worktree, reverted only crates/tui/src/tools/registry.rs to its parent (8104a75b0^), and kept your new test. It fails exactly as you describe:

tool bash schema carries null at ["$.properties.action.enum"]:
{"additionalProperties":false,"properties":{"action":{"enum":null},"command":{...},...}}

Restoring the fix: 48 passed; 0 failed for tools::registry
(RUST_MIN_STACK=16777216 cargo test -p codewhale-tui --lib -- tools::registry).

Two things worth recording because they are not obvious from the diff:

  1. The auto-vivification is two levels deep, not one. schema["properties"]["action"] inserts Null, and then ["enum"] on that Null converts it into an object before inserting — which is why the artifact is {"enum": null} and not a bare null.
  2. Neither schema_sanitize::sanitize nor schema_canonicalize::canonicalize_schema (registry.rs:270-271) strips it downstream. The failure output above is post-sanitize, post-canonicalize. So the guard genuinely has to live at the probe — there is no net underneath it.

Nits

1. pointer_mut is already the house idiom for this exact probecrates/tui/src/tools/subagent/mod.rs:14869 and :14907:

let Some(actions) = tool
    .input_schema
    .pointer_mut("/properties/action/enum")
    .and_then(serde_json::Value::as_array_mut)
else { continue; };

and the comment right above it (mod.rs:14863-14867) describes this same bug, down to the lowercase-bash shape:

Indexing ["properties"]["action"]["enum"] mutably would fabricate an "action": {"enum": null} property on schemas that have no action discriminator (the lowercase bash command/timeout shape) — a phantom node that fails Moonshot MFJS validation. Only shape enums that already exist.

So this is the same known defect in a second location, and the established one-line fix collapses your three-step ladder to:

let Some(actions) = schema
    .pointer_mut("/properties/action/enum")
    .and_then(Value::as_array_mut)
else { return; };

Same fail-closed behavior, non-mutating, and it makes the two sites greppable as one pattern. Your long comment is worth keeping either way — it is better than the one in subagent.

2. The Run arm two lines above still auto-vivifiescrates/tui/src/tools/registry.rs:508:

if let Some(properties) = schema["properties"].as_object_mut() {

If a Run-family schema ever lands without a top-level properties, this writes "properties": null into the wire schema and then returns — the identical failure class one match arm earlier. It is latent today (Run always has properties), but since this PR is specifically about closing that hole, schema.get_mut("properties").and_then(Value::as_object_mut) there would close it for good. Your new test would not currently catch it, because Run is filtered out of the reviewer catalog unless verification == Bounded.

3. DCO8104a75b0 carries no Signed-off-by: trailer. The Check Signed-off-by job is advisory (.github/workflows/dco.yml exits 0 either way), so it is green, but CONTRIBUTING asks for it. git commit --amend -s && git push --force-with-lease when convenient.

Unrelated to this change: the Test (windows-latest) failure on this run does not touch tools::registry.

Nice catch, and thank you for the regression test that walks the whole projected catalog for nulls rather than asserting on bash alone — that generalizes to the next tool that grows an action enum.

gaord and others added 2 commits September 6, 2026 14:42
…nums

`project_readonly_evidence_schema` used `schema["properties"]["action"]["enum"]`
to probe for an action enum. serde_json's IndexMut auto-vivifies missing keys
by inserting Null, so schemas with no action property (e.g. lowercase `bash`)
ended up with `properties.action = {"enum": null}`. Strict OpenAI-compatible
validators then rejected the whole request with `Invalid schema for function
'bash': null is not of type "array"`, observed on Fleet read-only workers.

Probe with non-mutating `get_mut` instead, and add a regression test that
asserts a reviewer wire catalog carries no null schema fields.
…arm (Hmbown#5944 follow-up)

Maintainer follow-up on @gaord's fix, applying the two review nits:

- The read-only projection probes with
  schema.pointer_mut("/properties/action/enum"), the same idiom
  tools/subagent already uses for this exact bug, so the two sites read as
  one pattern.
- The Run arm two lines above still auto-vivified "properties" on a schema
  without one; it now uses get_mut and cannot write "properties": null.
- Rebased onto current main (the tests module had grown a neighbour) and
  added the changelog receipt crediting the author.

RUST_MIN_STACK=16777216 cargo test -p codewhale-tui --lib -- tools::registry:
51 passed; 0 failed. cargo fmt --check clean; cargo clippy -p codewhale-tui
--lib --all-targets -D warnings clean; sync-changelog --check clean.

No-Issue: community fix from a live Fleet 400 report; no tracking issue.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SJrzNAmppg4vt3LNJbaeri
Signed-off-by: CodeWhale Bot <bot@codewhale.net>
@Hmbown
Hmbown force-pushed the fix/fleet-bash-schema-null branch from 8104a75 to 7371e95 Compare September 6, 2026 22:01

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Devin Review

Comment thread CHANGELOG.md
Comment on lines +480 to +487
- Read-only Fleet workers no longer send `"action": {"enum": null}` in their
projected `bash` schema. The read-only projection probed the action enum
with a mutating index, which auto-vivified the key on schemas that have no
action property, and strict OpenAI-compatible validators then rejected the
whole request (`null is not of type "array"`). The probe is non-mutating
now, in both the read-only projection and the `Run` arm next to it, and a
regression test walks the whole projected catalog for nulls
(#5944, thanks @gaord).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Changelog hunks bypass merge workflow

CONTRIBUTING.md reserves both changelogs for merge-time updates on main. Remove these branch-local entries to avoid conflicts with other pull requests.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@Hmbown
Hmbown merged commit f974685 into Hmbown:main Sep 6, 2026
24 checks passed
Hmbown pushed a commit to goransh-walia/Codewhale that referenced this pull request Sep 7, 2026
…eps the endpoint's receipt (Hmbown#5926)

The founder's receipt: of the eight servers the footer called failed,
seven only needed a login. Three surfaces now keep the states apart.

/mcp lists the servers that need a login first, as their own Needs
login group above Needs attention, and opens with the cursor already on
the first such row, so the Enter the screen advertises runs
`/mcp login <server>` straight away. New ExtensionsGroupNeedsLogin key
in all 15 locale packs.

A token refresh that fails to parse the provider's answer keeps the
endpoint's receipt - status line, content type, and a 200-byte excerpt
with every credential-shaped value (access_token, refresh_token,
client_secret, id_token, bearer schemes) masked before the cut - instead
of rmcp's bare 'Failed to parse server response', so a provider outage
answering an HTML 502 reads differently from a parser defect. The
receipt rides a RecordingOAuthHttpClient that executes what rmcp's stock
client would and records the latest token-endpoint answer; only the
current refresh's answer may explain that refresh's failure. The remedy
wording itself landed earlier as Hmbown#5959.

A footer snapshot test pins 'MCP . 1 connected . 1 auth required .
1 failed' so an expired login never regresses into the failed count
(the chip landed with the boot-surface half of Hmbown#5926).

Gates on this head (rebased onto main f974685): cargo fmt --all
clean; cargo clippy --workspace --all-targets --all-features --locked
-D warnings clean; full suite cargo test -p codewhale-tui --lib --locked
11877 passed / 0 failed / 13 ignored (targeted module filters 71
passed, the eight new tests verified by name, localization 50 passed).
Earlier runs under the shared CI runner showed isolated load flakes
(deepseek translate, tmux clipboard, fleet concurrent manager loops)
that each pass in isolation and in the final clean run; one zai
compatibility-stream failure at the previous base was root-caused to
the tools-registry probe defect fixed on main by Hmbown#5944's follow-up.

Signed-off-by: CodeWhale Bot <bot@codewhale.net>
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.

2 participants