Uh oh!
There was an error while loading. Please reload this page.
Fix Variables API handling of non-string JSON values - #71018
Merged
kaxil merged 1 commit intoAug 11, 2026
Conversation
POST /api/v2/variables with a non-string JSON value (array, object, bool, null) silently stored the Python repr of the value, which cannot be read back with deserialize_json. PATCH with the same payload failed with a masked 500 because the raw value hit the ORM Fernet encryption step. VariableBody now JSON-encodes non-string values once at request validation, covering POST, PATCH and bulk consistently, matching the behaviour the bulk create path already had for dicts and lists. Variable.set_val also raises a clear TypeError instead of the cryptic "encoding without a string argument".
kaxil
marked this pull request as ready for review
August 3, 2026 20:37
kaxil
requested review from
XD-DENG, ashb, bugraoz93, choo121600, ephraimbuddy, henry3260, jason810496, pierrejeambrun, rawwar and shubhamraj-git
as code ownersAugust 3, 2026 20:37
jason810496
approved these changes
Aug 11, 2026
jason810496
left a comment
Member
There was a problem hiding this comment.
Thanks for the fix.
I wonder why not just set serialize_json=True to solve the issue in the first place. Then found out that the patch and bulk operation endpoint will interact with the ORM attribute so the serialize_json=True doesn't fit. Fixing at data model layer (current implementation) is better.
No other findings, LGTM.
Uh oh!
There was an error while loading. Please reload this page.
pierrejeambrun
left a comment
Member
There was a problem hiding this comment.
Nice, thanks!
Probably needs a backport btw.
jason810496
commented
Aug 13, 2026
Member
Manually backport in #71526. |
potiuk pushed a commit
to jason810496/airflow
that referenced
this pull request
Aug 13, 2026
…che#71018) POST /api/v2/variables with a non-string JSON value (array, object, bool, null) silently stored the Python repr of the value, which cannot be read back with deserialize_json. PATCH with the same payload failed with a masked 500 because the raw value hit the ORM Fernet encryption step. VariableBody now JSON-encodes non-string values once at request validation, covering POST, PATCH and bulk consistently, matching the behaviour the bulk create path already had for dicts and lists. Variable.set_val also raises a clear TypeError instead of the cryptic "encoding without a string argument". (cherry picked from commit 537feaf) Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com> (cherry picked from commit d83617b)
potiuk pushed a commit
that referenced
this pull request
Aug 13, 2026
) (#71526) POST /api/v2/variables with a non-string JSON value (array, object, bool, null) silently stored the Python repr of the value, which cannot be read back with deserialize_json. PATCH with the same payload failed with a masked 500 because the raw value hit the ORM Fernet encryption step. VariableBody now JSON-encodes non-string values once at request validation, covering POST, PATCH and bulk consistently, matching the behaviour the bulk create path already had for dicts and lists. Variable.set_val also raises a clear TypeError instead of the cryptic "encoding without a string argument". (cherry picked from commit 537feaf) (cherry picked from commit d83617b) Co-authored-by: Kaxil Naik <kaxilnaik@apache.org> Co-authored-by: Kaxil Naik <kaxilnaik@gmail.com>
dabla pushed a commit
to dabla/airflow
that referenced
this pull request
Aug 14, 2026
POST /api/v2/variables with a non-string JSON value (array, object, bool, null) silently stored the Python repr of the value, which cannot be read back with deserialize_json. PATCH with the same payload failed with a masked 500 because the raw value hit the ORM Fernet encryption step. VariableBody now JSON-encodes non-string values once at request validation, covering POST, PATCH and bulk consistently, matching the behaviour the bulk create path already had for dicts and lists. Variable.set_val also raises a clear TypeError instead of the cryptic "encoding without a string argument".
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 freeto 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.
Fixes#71010
Closes#71015
Problem
VariableBody.valueis typedJsonValue, so the Variables REST API accepts any JSON type, but everything downstream assumed a string:POST /api/v2/variableswith"value": ["a", "b"]returned 201 but stored the Python repr['a', 'b']. That is not valid JSON, so the variable silently breaks the nextVariable.get(key, deserialize_json=True).PATCH /api/v2/variables/{key}with the same payload failed with a masked 500: the raw list reached the Fernet encryption step, which raisedTypeError: encoding without a string argument."True"/"None".Solution
VariableBodynow JSON-encodes non-string values once, at request validation, so POST, PATCH and both bulk actions behave identically: strings are stored verbatim, everything else is stored as JSON (indent=2, byte-identical to the existing bulk-create format) and round-trips throughdeserialize_json=True. The now-redundantserialize_jsonspecial case in bulk create is removed, andVariable.set_valraises a clearTypeErrorpointing atserialize_json=Trueinstead of the cryptic encoding error."value": ["a", "b"]['a', 'b'], unreadable as JSON["a", "b"]"value": ["a", "b"]"value": true/null"True"/"None"true/null"value": nullnullString values, including JSON passed as a string, are stored byte-for-byte as before, and the OpenAPI schema is unchanged, so generated clients are unaffected.
Why coerce instead of rejecting with 422
Non-string values are an intentional part of the API contract since #49844: the UI Import Variables flow sends raw parsed JSON (ImportVariablesForm.tsx#L71), the docs describe bulk-uploading variables as a JSON file, and
airflow variables importapplies the identical encode-iff-not-a-string rule (variable_command.py#L165). Tighteningvalueback tostrwould 422 all of those.Notes
NaN. Rejecting them in the validator is not viable: the 422 response echoes the NaN input, which starlette'sJSONResponsecannot serialize, turning the rejection into a 500.JWTRefreshMiddlewaremasking the real traceback asRuntimeError: No response returned) is not addressed here; it also affects When trying to edit and save Airflow variables using the Web UI, the save operation fails with an HTTP 500 Internal Server Error. #68868 and API write endpoints return opaque 500 when the database rejects a payload #66889.