Strange behavior with OptionFlow in data and options config_entry

Hello,

I’m having a problem with the config_flow of my first integration.
I want to allow users to modify the integration options (username, password, pin_code, alarmpanel_code).
When the device is created, no problem, everything is created, the “static” part in config_entry.data`` and the “dynamic” part in config_entry.options``.

"data":{"serial_id":"11223344556677"}
"options":{"alarmpanel_code":null,"api_key":"adbcc682-1111-4444-2222-50e32262a17a","password":"MyPassword","pin_code":1111,"secret_key":"ZZZZZZZZPKeyV8kFj0x2tlZfVeAGk2-AAAAA_poqXM274","username":"[email protected]"}

On the other hand, when I want to modify an option (for example alarmpanel_code), updating config_entry.options works fine, but it also changes config_entry.data by putting what appear to be the values of config_entry.options before the option is modified (as you can see, alarmpanel_code in config_entry.data is null but changed in options.

"data":{"alarmpanel_code":null,"api_key":"adbcc682-1111-4444-2222-50e32262a17a","password":"MyPassword","pin_code":1111,"secret_key":"ZZZZZZZZPKeyV8kFj0x2tlZfVeAGk2-AAAAA_poqXM274","serial_id":"11223344556677","username":"[email protected]"}
"options":{"alarmpanel_code":123,"api_key":"adbcc682-1111-4444-2222-50e32262a17a","password":"MyPassword","pin_code":1111,"secret_key":"ZZZZZZZZPKeyV8kFj0x2tlZfVeAGk2-AAAAA_poqXM274","username":"[email protected]"}

At first I thought I’d filled in my variables incorrectly, but no, everything’s fine.

2025-02-27 23:56:15.612 DEBUG (MainThread) [custom_components.diagral.config_flow] Submitting data entry to HA : {'serial_id': '11223344556677'}
2025-02-27 23:56:15.612 DEBUG (MainThread) [custom_components.diagral.config_flow] Submitting options entry to HA : {'username': '[email protected]', 'password': 'MyPassword', 'pin_code': 1111, 'api_key': 'adbcc682-1111-4444-2222-50e32262a17a', 'secret_key': 'ZZZZZZZZPKeyV8kFj0x2tlZfVeAGk2-AAAAA_poqXM274', 'alarmpanel_code': 123}

The part of my code that updates the options is as follows:

if user_input is not None:
            # Check if the user has changed the alarm panel code
            if (
                user_input[CONF_ALARMPANEL_CODE] is not None
                and user_input[CONF_ALARMPANEL_CODE]
                != self.options[CONF_ALARMPANEL_CODE]
            ):
                # Check if the user has entered a valid alarm panel code
                if not is_valid_pin(user_input[CONF_ALARMPANEL_CODE]):
                    errors[CONF_ALARMPANEL_CODE] = "invalid_alarmpanel_code"
                else:
                    self._alarmpanel_code_changed = True
                    self.options[CONF_ALARMPANEL_CODE] = user_input[
                        CONF_ALARMPANEL_CODE
                    ]

            if (
                self._username_changed
                or self._password_changed
                or self._pincode_changed
                or self._alarmpanel_code_changed
            ):
                _LOGGER.debug(
                    "Submitting data entry to HA : %s", self.config_entry.data
                )
                _LOGGER.debug("Submitting options entry to HA : %s", self.options)
                return self.async_create_entry(title="", data=self.options)

            _LOGGER.debug("No configuration changes detected, skipping update.")
            return self.async_abort(reason="no_changes")

I don’t understand where this is coming from. Why are keys and values from config_entry.options added to config_entry.data when updating options?
I’ve already tested a lot of things. Dived deep into Github to see how others were doing, but nothing worked.

Thanks in advance for your assistance on this strange behavior.

PS : I working on dev branch (version from 3 days ago)

Do you want to post a link to your whole code. Its much easier to help if we can see everything that is happening in your code.

1 Like

Hello @msp1974,

Yes can find the full code here : GitHub - mguyard/hass-diagral at dev
I confirm that each time data is populated with old options content.

If i change twice my option alarmpanel_code, each time, my data.alarmpanel_code is the N-1 version and options.alarmpanel_code the N version.

Very strange as my understanding is that data is more for all data that are not updatable by user and options for the others. So during my config_flow creation, i split my infos between data and options : hass-diagral/custom_components/diagral/config_flow.py at 55fa23e0bd8bc420b721908f808a69c64d0e8461 · mguyard/hass-diagral · GitHub

As it’s my first integration, it’s possible that my understanding isn’t the right.

For now all works as my init.py use options values (hass-diagral/custom_components/diagral/__init__.py at 55fa23e0bd8bc420b721908f808a69c64d0e8461 · mguyard/hass-diagral · GitHub) but double infos is not something very clean :frowning:

Ok. Firstly, thanks for posting your code. It really helps see what you are doing.

From a brief look on my phone, i cannot see exactly what is causing this. However, i think you are making it overly complicated for yourself and it maybe better to focus on simplification (which will likely fix the problem).

So, i think there is a better way for you to handle what you are trying to do. From the fact you require your users to complete the 3 forms on setup, all info should be stored in data and accessed from there.

There is a relatively new method to update setup data (as opposed to options data) called reconfigure which will allow you to call your original forms and update the data.

This is slightly different to the configure option shown in the UI as it shows a reconfigure option in the 3 dot overflow menu.

If you dont like that (and some dont as it is more hidden), there is a trick to update the config data from within an options flow which i will post you an example of shortly.

1 Like

I’ll give you a link to an options flow that updates confog data. This points you to the line that actually handles this but you can also then see the code around it.

And here is an example reconfigure flow.

1 Like

Hello @msp1974,

Thank you for taking the time to look at my code.
Following your recommendations, I ran some new tests and identified the problem:

On this line, every time a configuration is modified, I trigger a :

This function uses async_update_entry to update my configuration by pushing options into my data:

This was an old code used in my tests.

So I changed my way of doing things, using async_step_reconfigure to allow the user to change the credentials and options for the optional configuration.
Now everything works as I expected.

Thank you very much

1 Like