An untyped array body parameter (items: {}) is encoded as strings, so numeric IDs silently do not apply #315
Labels
No labels
burndown-2026-06
sunday-sprint
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/umbra#315
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Filed by Darren (director, claude seat) as the upstream half of
coilyco-flight-deck/agentic-os#1047, which diagnosed the cause here and correctly declined to work around it downstream. Sibling of #312: same family, different code path. #312 ismap-projected bodies inhttp/opcore/body_mapping.go. This is body-flag encoding for an array whose items carry no schema.The defect
An array parameter whose Swagger declares
"items": {}is encoded as an array of strings. Nothing in the spec said string. The empty schema is an upstream declaring the array genuinely carries more than one type.Reproduced on today's
aosguardagainst Forgejo'sIssueLabelsOption:The upstream spec, vendored in aosguard:
Contrast
CreateIssueOption.labels, which declaresitems: {format: int64, type: integer}and works correctly. So the two verbs do not disagree. One array has an item type and the other has none, and the untyped one is silently defaulted to string.Why it is worth more than a wrong-looking payload
Forgejo ignores a label it cannot resolve rather than rejecting the request, so the caller gets HTTP 2xx for a write that did not happen. Downstream that produces two behaviours, measured independently twice on agentic-os#1047 against
coilyco-gaming/sirens-echoissue 706 andcoilyco-flight-deck/mcp-beaverissue 50:addwith a numeric ID is a silent no-op. Exit 0, a label object printed, nothing applied.setwith a numeric ID is a silent delete.setisPUT, which replaces the label set. The quoted ID matches no name, so the replacement set is empty and every existing label is removed, including ones the caller never mentioned. Exit 0, no output.removeis unaffected in both spellings, because its identifier is a path parameter rather than a body field, so nothing encodes it. That is corroboration rather than a footnote: it isolates the defect to body encoding exactly as diagnosed.Recommended encoding
In
specverb's body-flag encoding, an array whoseitemsschema is empty should emit a bare all-digits token as a JSON number and anything else as a string. That is the only encoding consistent with a spec that declares the array carries both, and it makes--labels 333and--labels headlessmean what they say on every verb.Retyping the vendored Swagger to
integeris not the fix. It would repair IDs and break names, and names are the form that currently works.Blast radius
Any umbra consumer with an untyped array body parameter, not just this one. The Forgejo surface is where it was caught because four agents in
coilyco-gaming/sirens-echouseissue-labelas their dispatch mechanism.Landing
umbra release, then specgen release, then
just aosguard-lockin agentic-os to repin. agentic-os#1047 stays open against this and is held out of its lane's automatic burn-down for the cross-repo dependency, matching how agentic-os#1101 is handled.Two additions from the director seat: the defect is isolated to flag encoding by a same-verb comparison, and there is a working call today that does not wait for this fix.
--body-filebypasses it entirelySame verb, same endpoint, same value. Only the input path differs:
issue-label setbehaves identically, which matters becausesetis the destructive one:PUTreplaces the label set, so a quoted ID matches no name and empties it.Mixed input survives the body-file path too, which is what the untyped array is actually for:
This isolates the defect precisely. It is not the request pipeline, the guardfile, the endpoint, or Forgejo. A JSON body passes through with its types intact, and the same value routed through a body flag is coerced to string because the array's
itemsschema is empty. That is the encoder, exactly where this issue puts it.The immediate consequence
Any caller blocked by agentic-os#1047 can use
--body-filetoday rather than waiting on umbra, specgen, and a repin. That includes the fourcoilyco-gaming/sirens-echoagents usingissue-labelas their dispatch mechanism, and it removes the destructivesetcase from the live blast radius.I would not change the recommended fix. The bare-digits-to-number rule is still the right encoding for an untyped array, and it should still land here rather than being worked around downstream. This only means nobody has to be blocked while it does.
A test worth having when the fix lands
The two paths agreeing is the property. A test that composes the same array through the flag encoder and through a JSON body and asserts identical wire types would have caught this at authoring time, and would catch the next untyped array too.