- Newest
- Most votes
- Most comments
The root cause is likely a race condition between the IoT Core MQTT subscription and the IPC Shadow Manager.
While the previous answer correctly identifies the 409 Conflict, it misses the specific reason why this is happening in your code. You are currently mixing two different communication paths:
- Subscription: You are using
SubscribeToIoTCoreRequest. This talks directly to the MQTT broker (cloud/proxy). - Update: You are using
UpdateThingShadowRequest. This is an IPC call to the local Greengrass Shadow Manager.
Why the "false to true" update fails:
When you update the shadow in the cloud, the Shadow Manager and your Python script (via MQTT sub) receive the message at almost the same time. If your script processes the delta and sends an IPC UpdateThingShadow report while the Shadow Manager is still busy committing the cloud update to its local database, a version mismatch (409) occurs.
How to fix it: Stop using the IoT Core MQTT topic subscription. Instead, use the dedicated Greengrass IPC event stream. This ensures your component stays in sync with the Shadow Manager’s local state before you attempt an update.
Change your subscription logic:
Instead of SubscribeToIoTCoreRequest, use SubscribeToNamedShadowUpdateEventsRequest.
from awsiot.greengrasscoreipc.model import SubscribeToNamedShadowUpdateEventsRequest def _subscribe_to_shadow_updates(self): # Use the Shadow Manager IPC event stream instead of raw MQTT try: req = SubscribeToNamedShadowUpdateEventsRequest() req.thing_name = self.thing_name req.shadow_name = self.shadow_name # Your handler will now receive 'NamedShadowUpdateEvent' self.sub_operation = self.ipc_client.new_subscribe_to_named_shadow_update_events(handler) self.sub_operation.activate(req) # ... rest of your activation logic except Exception as e: logger.exception(f"Failed to subscribe: {e}")
Key Advantages:
- Atomicity: You only get notified once the Shadow Manager has successfully processed the change.
- Consistency: It eliminates the race condition where your component tries to "report" a state while the local manager is still updating the "desired" state.
- Efficiency: You don't need to construct MQTT topic strings manually.
Thanks for the answer and sorry for the late reply! My SDK version is 1.19.0 and
awsiot.greengrasscoreipc.modeldoes not haveSubscribeToNamedShadowUpdateEventsRequest. I could upgrade it but it was constrained to 1.19.0 before I started working on this project and I'm not sure if there was some specific reason for this so my preferred solution would be to not change the version. But if there's not other sensible options, I will upgrade my SDK and try after that.EDIT: I also tried looking through the AWS IoT SDK Python Github and could not find that symbol from the
model.pyfile undergreengrasscoreipcso does that even exist?
Fixed this by changing the update/delta listening to use greengrasscoreipc instead of direct IoTCore subscription. Changed also to clientv2 to reduce written code in the codebase for clarity and now it seems to be working consistently.
Updated methods:
def _subscribe_to_shadow_updates(self): """ Subscribe to shadow delta updates via IoT Core MQTT topics. AWS IoT publishes to shadow/update/delta when desired != reported. This avoids processing the device's own reported updates. """ if self.ipc_client is None: logger.error("Cannot subscribe to shadow updates: IPC client not connected") return try: # Subscribe to the shadow delta topic # Only publishes when desired state differs from reported state # This prevents double-processing when device sends reported updates topic = f"{self.topic_prefix}/update/delta" self.sub_operation = self.ipc_client.subscribe_to_topic( topic=topic, on_stream_event=self._on_stream_event, on_stream_error=self._on_stream_error, ) logger.info( f"Successfully subscribed to shadow delta updates for " f"'{self.thing_name}/{self.shadow_name}'" ) except Exception as e: logger.exception(f"Failed to subscribe to shadow updates: {e}") def _report_shadow_update( self, results: dict[str, TypeConfig | AppRemoteConfig | ProcessConfig] ): """ Called after updating configuration shadow and notifying observers. Will send the 'reported' object back to cloud through the ipc client mqtt. Args: results (dict): Dictionary mapping observer names to their updated config objects """ if not results: logger.warning("No results from observers, reporting full shadow state instead") # If no observers returned results, report the current shadow state shadow_report = self.shadow_doc else: shadow_report = self._construct_shadow_report(results) if not self.ipc_client: raise Exception("IPC client not available to report shadow") # Convert to dict with aliases for cloud compatibility reported = shadow_report.model_dump(by_alias=True) payload = { "state": { "reported": reported, } } try: logger.info(f"Reporting shadow update: {json.dumps(payload)}") self.ipc_client.update_thing_shadow( thing_name=self.thing_name, shadow_name=self.shadow_name, payload=json.dumps(payload).encode("utf-8"), ) logger.info("Shadow update sent to cloud") except Exception as e: logger.exception(f"Exception in shadow update report: {e!s}") raise # Re-raise to ensure caller knows update failed
And since updating to clientv2 removes the need for a separate handler class, I removed the ShadowUpdateHandler class and made the on_stream* methods members of the original parent class
Based on the error message and behavior you're describing, this appears to be a version conflict issue during shadow synchronization between your Greengrass device and AWS IoT Core.
The key indicator is the ConflictException with status code 409 and the message "Version conflict". This occurs when the shadow document version on the device doesn't match the version in the cloud when an update is attempted. The shadow manager uses version numbers to implement optimistic locking - if the current version of the state document doesn't match the version supplied in an update request, the service rejects the request with an HTTP 409 conflict response.
Here's what's likely happening in your scenario:
- When you update the shadow from cloud (changing
falsetotrue), the Shadow Manager receives the update and logs it in greengrass.log - Your Python component may be processing this update and sending back a
reportedstate - However, there's a timing issue where the local shadow version and cloud shadow version have diverged
- When the Shadow Manager tries to sync the
reportedstate back to the cloud, it encounters a version mismatch, resulting in the 409 conflict
The Shadow Manager is designed to handle these conflicts through merge strategies. When there's a merge conflict:
- For the
deviceToCloudsync direction, the local shadow document value overwrites the cloud - For the
cloudToDevicesync direction, the cloud value overwrites the local document - For bidirectional sync, conflicts in
desiredstate are resolved in favor of the cloud, while conflicts inreportedstate are resolved in favor of the device
The asymmetric behavior you're seeing (working one direction but not the other) could be related to timing differences in how quickly your component processes and responds to the delta updates, leading to version mismatches in one direction but not the other.
To troubleshoot this issue:
- Check your Shadow Manager sync configuration - Verify the sync direction and strategy settings in your Shadow Manager component configuration
- Review timing in your code - Ensure your Python component isn't sending multiple rapid updates that could cause version conflicts
- Add version tracking - Consider logging the version numbers from both delta messages and your reported updates to identify where the version divergence occurs
- Verify shadow document structure - Ensure your
reportedstate updates include all necessary fields and don't inadvertently create conflicts with thedesiredstate
The warning message "Received conflict when processing request. Retrying as a full sync" indicates the Shadow Manager is attempting to recover by performing a full synchronization, which should eventually resolve the inconsistency, though it may take a moment to complete.
Sources
Sync local device shadows with AWS IoT Core - AWS IoT Greengrass
Tutorial: Installing the Device SDK and running the sample application for Device Shadows - AWS IoT Core
Troubleshooting AWS IoT Greengrass - AWS IoT Greengrass
Relevant content
- asked 3 years ago
- asked a year ago
- asked 4 years ago
- AWS OFFICIALUpdated 4 months ago

Forgot to put this in the actual post but this is my ShadowManager component configuration:
I haven't specified the direction there because documentation says that it is
betweenDeviceAndCloudby default