fix: a refusal reaches the practitioner in the API's own words - #126
Merged
ShocOne merged 1 commit intoAug 26, 2026
Merged
Conversation
kiota deserializes an error body into the properties the document declared and never copies any of them onto the embedded ApiError, whose Error() then answers one constant however much the API said. Every refusal a generated provider reported read "error status code received from the API", which names no field, no value and no condition. The status alone does not separate them. Against one pilot a single 400 stood for a name collision, a missing field, an unreachable URL, an absent discriminator, an empty relation and an opaque validation failure; the message separates five of the six. The kiota extractor now reads what the API sent: the problem document's summary and explanation joined when both are present and distinct, then the single property that carried a sentence, then the listed complaints among the properties the document did not declare. The vocabulary is the audit's own refusal reader, so both stages read a refusal the same way rather than inventing a second spelling for it. This brings the dialect level with the other one: the openapi-generator extractor already answered with the raw response body. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ShocOne
deleted the
fix/a-refusal-reaches-the-practitioner-in-the-apis-words
branch
August 26, 2026 15:07
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The defect
kiota deserializes an error body into the properties the document declared, then throws them away:
Nothing populates
Messagefrom thedetail,titleorstatusthe generated type just deserialized. So that literal — the SDK's fallback for an unset field — was the detail of every refusal a generated provider reported. The words were in memory on the error's own getters, and the extractor read onlyError().Why the status is not enough on its own
Against the ThousandEyes pilot, a single
HTTP 400stood for six unrelated causes:account_groupAn Account Group with that name already exists.roleError in field roleName : must not be null.streamURL '…' is not reachable due to: unknown hostoperations_webhookmissing type id property 'type'tests_ftp_serverError in field agentSet : No agents selected.endpoint_labelRequest validation failedA collision that should have been a 409, a missing field, a semantic reachability check, an absent discriminator, an empty relation, and one opaque even in prose. The status separates none of them; the message separates five of six.
The fix
The kiota extractor reads what the API sent: the problem document's summary and explanation joined when both are present and distinct, then the single property that carried a sentence, then the listed complaints among the properties the document did not declare (
GetAdditionalData, which kiota keeps).The key vocabulary is the audit's own
refusalMessage(internal/audit/run/adjust.go) — listed complaints first, thendetail,message,error_description,errorMessage,error,title— so the audit and the provider read a refusal the same way instead of inventing a second spelling.This brings the dialect level with its sibling:
extract_openapigenerator.go.tmplalready answered with the raw response body. kiota was the odd one out.Measured, against a generated tree
thousandeyes_role, before:after:
That message names the fix. The old one named nothing.
Contract
Provider-core change, so it is a contract change under semver — every generated provider's error detail changes shape.
Verification
make check(90.7% total,emit91.4%) andgolangci-lint run(0 issues). The pilot regenerates,postcheckpasses, and the message above is from the live acceptance run, not from grepping this repo.Related
My #122 dropped error-response content on 27 DELETE operations to fix the
Acceptheader, which set theirerrorMappingtonil— those deletes now yield an untypedApiErrorand gain nothing from this change.The cost is narrower than it first looks. A 404 on delete never reaches the message at all:
HandleDeleteErrorreturns early on it, because the object being gone is what the delete wanted. Retryable statuses are outwaited byIsRetryableDelete. What is left is a delete refused with a status that is neither — a 403, or a genuine 400/409 — where the practitioner now sees kiota'sno error factory is registered for this code: Ninstead of the API's sentence.Observed exactly once on the pilot, on
account_group's delete, and that case has since resolved on its own: the objects holding the name were removed and the delete now answers 204. So the residual cost is real but currently unexercised. Worth revisiting separately if a delete refusal ever needs to explain itself.