Syncing security_monitoring_rules fails to create every non-default rule that has case conditions. There are two compounding bugs:
- Masking bug: the 400-handling code does error_obj["errors"], but the API error body uses a different shape, so it raises KeyError: 'errors'. That KeyError replaces the real Datadog message, and the log only shows a useless message.
| exceptCustomClientHTTPErroraserr: |
| iferr.status_code==400: |
| preamble="400 Bad Request - " |
| error_json_no_preamble=err.args[0][len(preamble) :] |
| error_obj=json.loads(error_json_no_preamble) |
| errors=error_obj["errors"] |
| forerror_messageinerrors: |
| iferror_messageinself.errors_to_skip: |
| raiseSkipResource(_id, self.resource_type, err.args[0]) |
| raiseerr |
Example output:
2026-06-25 16:33:22,449 - ERROR - [security_monitoring_rules - xxx-xxx-xxx] - 'errors'
The body is {"error": {...}}, not the {"errors": [...]} the code assumes. Once I fixed this bug locally, I found the real bug
2026-06-25 16:59:31,955 - ERROR - [security_monitoring_rules - xxx-xxx-xxx] - 400 Bad Request - {"error":{"code":"InvalidArgument","message":"Invalid rule configuration","details":[{"code":"InvalidArgument","message":"Query a must be used at least once in a case","target":"cases"},{"code":"InvalidArgument","message":"Case condition cannot be empty","target":"cases[0].condition"}]}}
- Root cause: "cases.condition" is listed in excluded_attributes. Excluded attributes doesn't only affect diffing, but also it is stripped from the create/update payload too. Since condition is required by the rule API, every create is rejected with HTTP 400.
| classSecurityMonitoringRules(BaseResource): |
| """Security Monitoring Rules inherits from BaseResource""" |
| |
| resource_type="security_monitoring_rules" |
| resource_config=ResourceConfig( |
| base_path="/api/v2/security_monitoring/rules", |
| excluded_attributes=[ |
| "createdAt", |
| "creationAuthorId", |
| "updateAuthorId", |
| "updatedAt", |
| "isPartner", |
| "isBeta", |
| "isDeleted", |
| "isDeprecated", |
| "defaultTags", |
| "version", |
| "options.anomalyDetectionOptions", |
| "options.impossibleTravelOptions", |
| "cases.condition", |
| ], |
I can bypass this second bug by removing the cases.condition from the excluded attributes, which allows me to create the resources at the new account
Note: simply removing cases.condition from excluded_attributes fixes creation but may reintroduce the spurious diffs that #311 was trying to suppress (the API computes condition for some rule types, e.g. anomaly). The two concerns should be decoupled: keep condition in the payload, but ignore it in the diff via DeepDiff exclude_regex_paths (exclude_paths as used by excluded_attributes doesn't match list indices like root['cases'][0]['condition']).
Syncing security_monitoring_rules fails to create every non-default rule that has case conditions. There are two compounding bugs:
datadog-sync-cli/datadog_sync/model/security_monitoring_rules.py
Lines 114 to 123 in 8d60e76
Example output:
The body is {"error": {...}}, not the {"errors": [...]} the code assumes. Once I fixed this bug locally, I found the real bug
datadog-sync-cli/datadog_sync/model/security_monitoring_rules.py
Lines 26 to 46 in 8d60e76
I can bypass this second bug by removing the cases.condition from the excluded attributes, which allows me to create the resources at the new account
Note: simply removing cases.condition from excluded_attributes fixes creation but may reintroduce the spurious diffs that #311 was trying to suppress (the API computes condition for some rule types, e.g. anomaly). The two concerns should be decoupled: keep condition in the payload, but ignore it in the diff via DeepDiff exclude_regex_paths (exclude_paths as used by excluded_attributes doesn't match list indices like root['cases'][0]['condition']).