fix(label): reject a mistyped label body instead of silently clearing (#1821) - #1822
fix(label): reject a mistyped label body instead of silently clearing (#1821)#1822dolho wants to merge 2 commits into
Conversation
vybe
left a comment
There was a problem hiding this comment.
This PR requires the following change before merge:
- Update
tests/unit/test_1640_display_label_at_creation.py::test_label_update_shares_policyto the new contract. Line 95 assertsAgentLabelUpdate().label is None— the empty-body-means-clear behavior this PR deliberately outlaws — so theregression diffjob fails deterministically across all 3 seeds (4978 pass / 1 failon head vs4972 / 0on base).
The fix itself is right (the #1821 silent-clear on an unknown field name is a real bug and extra="forbid" + required-but-nullable is the correct shape), but it changes a contract that an existing test pins, and only the new test file was updated. Merging as-is would land a permanently red test on dev and pollute every future PR's regression-diff base.
Please update the old test to the new contract (empty body → ValidationError) and request re-review.
|
Resolve by running |
|
Fixed in 096eee3 — thanks, that was a clean catch.
Full unit suite is running; I'll post the count when it lands. On the nightly bot's merge-conflict note above: stale. Re-review please. |
|
Full unit suite on the branch after 096eee3:
|
…#1821) `AgentLabelUpdate` ignored unknown fields and defaulted `label` to None — and None means "clear the label". So any body the server did not recognise was indistinguishable from a deliberate clear: PUT {"display_label": "Typo Name"} -> 200, label wiped That mistake is easy to make: `display_label` is the DB column, `display_name` is the response field, and only `label` is accepted. - `extra="forbid"` so an unknown field 422s instead of destroying data. - `label` is now required-but-nullable, so an empty `{}` 422s too — it was the same ambiguity, and nothing documented it as a way to clear. Clearing stays fully supported and unchanged in meaning; it just has to be said explicitly (`{"label": null}` or a blank string). The frontend already sends `{ label }` on every call (stores/agents.js:393) and no MCP tool sets labels, so no caller is affected. Related to #1821 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review catch (vybe): test_1640_display_label_at_creation::test_label_update_shares_policy line 95 asserted `AgentLabelUpdate().label is None` — the empty-body-means-clear behaviour this PR deliberately outlaws. Only the new test file was updated, so the regression-diff job failed deterministically across all 3 seeds (4978/1 on head vs 4972/0 on base). Left as-is it would have landed a permanently red test on dev and polluted every later PR's base. Replace that line with the explicit-clear case it was standing in for (`label=None`), and split the now-rejected inputs into their own test: empty body and the misnamed `display_label` field — the actual #1821 trigger, which had no coverage in this file at all. Related to #1821
096eee3 to
2435e58
Compare
|
Review item was already fixed in The item. def test_label_update_requires_the_label_field(M):
with pytest.raises(Exception):
M.AgentLabelUpdate() # empty body
with pytest.raises(Exception):
M.AgentLabelUpdate(display_label="Typo Name") # the actual #1821 triggerWorth noting the second case had no coverage in that file at all — the misnamed-field trigger was only tested in the new file, so the contract's own test module didn't pin the bug it exists to describe. Checked whether one site was all of them, rather than just doing the named fix. Every Frontend compatibility re-verified rather than re-asserted. The PR body claims the UI always sends Verification after the rebase (47 commits of Re-requesting review. |
obasilakis
left a comment
There was a problem hiding this comment.
Re-validated after the requested change landed.
vybe's single blocking item is addressed by 2435e58: test_1640_display_label_at_creation.py::test_label_update_shares_policy line 95 no longer asserts AgentLabelUpdate().label is None — it now asserts the explicit-clear case (label=None) it was standing in for, and the two newly-rejected inputs (empty body, the misnamed display_label field) moved into their own named test. The regression diff job is green across all 3 seeds: base 5735 pass / 1 fail vs head 5743 / 1 — the same single pre-existing failure on both sides (test_1069_voip_call_path_param, a FastAPI get_flat_dependant ImportError already red on dev), so no new failures and +8 net passing tests. All 18 checks green.
Contract change verified safe. Every caller sends an explicit {label: <string|null>}: the only writer is stores/agents.js:391 setAgentLabel, reached solely from AgentDetail.vue:629 via AgentHeader.vue:725 emit('set-label', trimmed || null) — the clear path coerces '' to an explicit null, never undefined, so it never serializes to {}. No MCP tool, CLI command, script, agent-server path, or enterprise-submodule caller touches the endpoint, and nothing constructs AgentLabelUpdate programmatically outside tests. Docs already prescribed the strict form — requirements/core-agent.md:60 specifies {label: string|null} and managing-agents.md:65 documents clearing as {"label": null} or a blank string — so this brings the code into conformance rather than changing what was documented. Both fixes match #1821's own suggested remedies.
Approving; not dismissing the prior review.
|
@vybe the blocking item is addressed in |
What
PUT /api/agents/{name}/labelno longer returns 200 and silently wipes the label when the body uses a field name it doesn't recognise.Why
label=Nonemeans "clear", and the model ignored unknown fields withlabeldefaulting toNone— so a malformed body and a deliberate clear were the same request. I hit this by accident while testing the feature, sendingdisplay_label(the DB column name):The naming makes it easy: the column is
display_label, the response field isdisplay_name, onlylabelis accepted.Change
model_config = ConfigDict(extra="forbid")— an unknown field 422s.labelbecomes required-but-nullable —{}also 422s, since it carried the same ambiguity and was never a documented way to clear.Clearing is unchanged in meaning and still supported; it just has to be explicit.
Compatibility
{ label }on every call (stores/agents.js:393), including when clearing.PUT {}now 422s where it used to clear. That path was indistinguishable from a typo, which is the bug.Verification
Unit —
tests/unit/test_1821_label_update_strictness.py, 7 cases: unknown field rejected, empty body rejected, explicit null still clears, blank string still clears, setting works, the ent#181 normalizer still runs, and control characters are still rejected (strictness must not bypass the existing validator).One test of mine was wrong on the first pass — I asserted the normalizer collapses internal whitespace; it only trims and NFC-normalizes. Corrected to the real behaviour rather than changing the code to match my assumption.
Live, reproducing the issue exactly:
Instance reverted to stock afterwards.
Related to #1821
🤖 Generated with Claude Code