Skip to content

Model#initialize drops every false value — booleans are silently lost on requests and responses #800

Closed

Description

@lacma2011

Describe the problem

Auth0::Internal::Types::Model#initialize resolves each declared field with ||, so a legitimate false is treated as absent and never reaches @data. Since #to_h only emits keys present in @data, every boolean whose value is false silently disappears — on requests and responses alike.

Requests: users.update(id:, blocked: false) builds an empty PATCH body, and Auth0 rejects it with 400 Payload validation error: 'None of the valid schemas were met'. Unblocking a user is not possible through the SDK.

Responses: a user who genuinely is "email_verified": false deserializes to nil, indistinguishable from "the API did not return this field". This one is quiet — no exception, just a wrong value.

Reproduction

require "auth0/version"   # still needed on 6.2.0, see #786
require "auth0"

k = Auth0::Users::Types::UpdateUserRequestContent

k.new(id: "auth0|abc", blocked: false, email_verified: false).to_h
# => {"id" => "auth0|abc"}                       # both booleans dropped
k.new(id: "auth0|abc", blocked: true).to_h
# => {"id" => "auth0|abc", "blocked" => true}    # true survives

json = '{"user_id":"auth0|abc","email":"a@b.com","email_verified":false,"blocked":false}'
u = Auth0::Types::UpdateUserResponseContent.load(json)
u.email_verified  # => nil   (expected false)
u.blocked         # => nil   (expected false)
u.to_h            # => {"user_id" => "auth0|abc", "email" => "a@b.com"}

Users::Client#update does request_data.except("id"), so the first case sends PATCH /api/v2/users/auth0|abc with a literal {} body.

Expected behaviour

false round-trips like any other value: to_h includes the key and the reader returns false.

Cause

lib/auth0/internal/types/model.rb:

value = values.delete(field.api_name.to_sym) || values.delete(field.api_name) || values.delete(field_name)

field_value = value || (if field.literal?
                          field.value
                        elsif field.default
                          field.default
                        end)

Both || chains discard false. Presence has to be decided with Hash#key?, and the literal/default fallback should apply only when the value is genuinely nil:

key = [field.api_name.to_sym, field.api_name, field_name].find { |k| values.key?(k) }
value = key.nil? ? nil : values.delete(key)

field_value =
  if !value.nil?
    value
  elsif field.literal?
    field.value
  else
    field.default
  end

(Also note the elsif field.default guard is itself truthiness-based, so a field declared with default: false can never fall back to its default either.)

Because this lives in the shared base model it affects every boolean in the generated API surface — blocked, email_verified, verify_email, phone_verified, include_totals, and so on.

Environment

  • auth0 6.2.0 — lib/auth0/internal/types/model.rb is byte-identical in 6.0.0, 6.1.0 and 6.2.0 (md5 0908fa9ec6636316bc78688d3e7436b3), so all three are affected
  • Ruby 3.4

Activity

  1. kishore7snehil commented on Sep 7, 2026

    @kishore7snehil
    Contributor

    👋 @lacma2011, thanks for raising this.

    We have reproduced this. The root cause is in Model#initialize - it uses || chaining to resolve field values, which treats false as absent and drops it before it ever reaches @data. This affects any boolean field set to false, both on request models and deserialized responses. We are tracking this as a generator issue and will update here once it is addressed at the source.

  2. Nakanishi21 commented on Oct 1, 2026

    @Nakanishi21

    Hi, thanks for confirming this. I was looking at where the fix would come from and wanted to check my understanding.

    It looks like the generator side is already fixed: fern-api/fern#17671 (merged 2026-09-11, and it references this issue directly) changed Model#initialize to use key?/nil? checks instead of ||, and it shipped in the Ruby SDK generator 1.24.1 as "Preserve false values in model fields".
    https://github.com/fern-api/fern/blob/be87e073d331880133ce9b03fade22ec564e7e43/generators/ruby-v2/sdk/versions.yml#L114-L143
    (There is also a separate Fern issue, fern-api/fern#17594, which is still open but was never linked to that PR.)

    At the same time, this repo's .fernignore lists lib/auth0/internal/, and the regeneration PRs since then (#803 for v6.3.0 and #808 for v6.4.0) don't seem to touch anything under internal/. lib/auth0/internal/types/model.rb on master still has the || chain.

    Does that mean the generator fix won't reach model.rb through regeneration, and it would need to be ported by hand? Or is internal/ updated through some other path I'm not seeing?

    If a manual port is the way to go, I'm happy to open a PR for it.

  3. kishore7snehil commented on Oct 7, 2026

    @kishore7snehil
    Contributor

    @Nakanishi21 your reading is right. lib/auth0/internal/ is in .fernignore, so the generator fix never reaches model.rb through regeneration and it has to be ported by hand. That port is in #811.

    @lacma2011 @Nakanishi21 could you try it on your side and let us know whether false now comes through on both requests and responses? You can point your Gemfile at the branch with gem "auth0", github: "auth0/ruby-auth0", branch: "fix/preserve-false-model-fields". users.update(id:, blocked: false) and a response with "email_verified": false are the two cases from the report.

  4. lacma2011 commented on Oct 7, 2026

    @lacma2011
    Author

    Tested fix/preserve-false-model-fields (3bd5965) and it works on our side. UpdateUserRequestContent.new(id:, blocked: false, email_verified: false).to_h keeps both keys, and UpdateUserResponseContent.load with "email_verified": false, "blocked": false returns false for both readers and in to_h. End to end against a dev tenant, users.update(id:, blocked: false) now unblocks the user, so we've dropped our monkey patch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions