Testing cloud-custodian/cloud-custodian#11021 (aws/taggable).
Question: can we determine, ahead of time, which resource types the Resource Group
Tagging API is unable to tag — so aws.taggable stops dispatching batches that are
guaranteed to fail?
Method: synthesize one unique ARN for each of the 2260 ARN types in
tests/data/arn-types.json (380 services), pointed at a non-existent account
(111111111111), and call tag-resources on them in batches of 20. Nothing real can be
tagged because no emitted ARN names a resource that exists; the script hard-fails if any
generated ARN contains the caller's own account id.
Probe script: rgt_probe.py
Run: python rgt_probe.py tests/data/arn-types.json us-east-2 from a cloud-custodian checkout.
Roughly 340 API calls (113 batches plus binary-split isolation and throttle retries),
about 4 minutes, mutates nothing.
RGT emits a service-specific error only when it has successfully routed the ARN to that service's tagging API:
arn:aws:ec2:us-east-2:111111111111:instance/c7nprobe0689 InvalidID The ID 'c7nprobe0689' is not valid
arn:aws:s3:::c7nprobe1853 NoSuchBucket The specified bucket does not exist
arn:aws:sqs:us-east-2:111111111111:c7nprobe2041 InvalidAddress The address ... is not valid
arn:aws:cassandra:us-east-2:111111111111:/keyspace/fake/ InvalidParameterException Unrecognized service or resource type for tagging
So InvalidParameterException: Unrecognized service or resource type for tagging is the
only response that means "not taggable". Every other per-resource error is evidence of
support.
| verdict | count | meaning |
|---|---|---|
ROUTED |
1688 | RGT routed to the service — type is taggable |
NOT_TAGGABLE |
348 | Unrecognized service or resource type for tagging |
CALL_REJECTED |
223 | whole call 400'd, no per-resource detail (see below) |
REPORTED_SUCCESS |
1 | no failure reported for a resource that cannot exist |
Top ROUTED error codes: AccessDeniedException 647, InvalidParameterException 261,
InvalidID 109, BadRequestException 98, ValidationException 85, InvalidRequestException 80.
a4b, aidevops, aiops, airflow-serverless, application-autoscaling, application-signals-mcp,
appmesh-preview, appstudio, aws-external-anthropic, backup-search, bedrock-mantle, bugbust,
cassandra, chime, cleanrooms-ml, cloudsearch, cloudshell, codeguru-security,
codestar-notifications, codewhisperer, cognito-sync, cur, dax, drs, elastictranscoder,
elemental-appliances-software, elemental-inference, evidently, evs, finspace, freertos,
gameliftstreams, health-agent, honeycode, identity-sync, inspector2, interconnect,
iotfleethub, iotmanagedintegrations, launchwizard, license-manager-linux-subscriptions,
license-manager-user-subscriptions, lookoutequipment, lookoutmetrics, lookoutvision,
mediastore, mgn, migrationhub-orchestrator, monitron, mpa, neptune-db, neptune-graph,
networkflowmonitor, nimble, nova-act, observabilityadmin, odb, one, opensearch,
pca-connector-scep, pcs, pi, polly, private-networks, q, qdeveloper, rds-db, repostspace,
rolesanywhere, rtbfabric, s3-object-lambda, s3express, s3files, scn, sdb, security-ir,
securityagent, securitylake, serverlessrepo, simspaceweaver, snow-device-management,
social-messaging, sqlworkbench, sso-oauth, thinclient, timestream, transform,
transform-custom, translate, trustedadvisor, voiceid, wickr, workdocs, workmail,
workmailmessageflow
A further 16 services are partially unsupported, which matters more than the fully-dead ones because a policy will happily discover the taggable siblings:
| service | untaggable / total | untaggable types |
|---|---|---|
| personalize | 15/18 | |
| s3 | 7/13 | accessgrant, accessgrantsinstance, accessgrantslocation, accesspoint, accesspointobject, objectlambdaaccesspoint, storagelensgroup |
| iam | 6/14 | access-report, assumed-role, delegation-request, federated-user, group, sms-mfa |
| aws-marketplace | 6/16 | |
| sso | 5/6 | |
| mgh | 4/5 | |
| controltower | 3/4 | EnabledBaseline, EnabledControl, LandingZone |
| es | 2/5 | application, datasource |
| artifact, codecatalyst, qapps, signin | 2 each | |
| execute-api, payment-cryptography, sts, workspaces-instances | 1 each |
Note iam:group and the entire s3 access-point family — both plausible discovery results.
54 of the CALL_REJECTED rows returned Invocation of TagResources for this resource is not supported in this region, all in global services:
| service | types |
|---|---|
| cloudfront | 19 |
| waf | 12 |
| networkmanager | 9 |
| route53 | 8 |
| globalaccelerator | 4 |
| cloudfront-keyvaluestore | 1 |
| savingsplans | 1 |
Confirmed directly:
us-east-2: WHOLE CALL FAILED InvalidParameterException: ...not supported in this region
us-east-1: call OK -> FailedResourcesMap={...InvalidClientTokenId...}
mixed batch of 3 valid + 1 global arn: WHOLE CALL FAILED -> InvalidParameterException
Three consequences for aws.taggable, ascending in severity:
Taggable.resource_typedoes not setglobal_resource, soUniversalTag.get_client()(c7n/tags.py:1012) builds its client in the policy region. Global resources discovered outside us-east-1 are untaggable by construction.- One such ARN fails its entire batch of 20. The rejection is a whole-call 400, not a
FailedResourcesMapentry, so the 19 innocent ARNs are lost with it. - It raises a raw
ClientError, not aResourceGroupTagError, soTagActionDispatch.process(c7n/resources/taggable.py:295-307) does not catch it — it kills the policy. Identical abort mechanism to the empty-ResourceARNListcrash in the smoke test analysis.
The 2026-08-21 smoke test never hit this: us-east-2 discovery returned no global resources
(grep -c "not supported in this region" custodian-run.log → 0). A us-east-1 run, or any
account with CloudFront/WAF/Route53, will.
One row, es:opensearchservice_role →
arn:aws:iam::111111111111:role/aws-service-role/opensearchservice.amazonaws.com/AWSServiceRoleForAmazonOpenSearchService,
came back absent from FailedResourcesMap entirely — i.e. reported as tagged, in an account
that does not exist. For IAM ARNs, "no failure reported" is not evidence the tag landed.
- 169 of the 223
CALL_REJECTEDrows are probe artifacts, not findings.arn-types.jsondescribes IAM policy resource patterns, not real ARNs: 19 contain a literal*, 100 have an empty region field, the remainder empty account fields (iq,partnercentral,geo-*). These fail as... is not a valid AmazonResourceName (ARN). Check the message before believing aCALL_REJECTEDverdict. The file is not a usable source of real ARNs. - Runtime, state, and permission failures are invisible. cloudformation, kms, events,
cloudwatch and ec2 all came back cleanly
ROUTED. The probe cannot seeNo updates are to be performed, key-policyAccessDenied,ManagedRuleException, or throttling — which is to say, most of what actually failed in the smoke test. - Dead end, recorded so nobody repeats it: bucketing
ROUTEDtypes by whether they rejected the synthetic id on format grounds (thessm:managed-instancemi-[0-9a-f]{17}class of failure) flags 209 types — nearly every type in every service, because a generic filler never matches any real id format. It cannot predict which real ARNs a service will reject. Removed from the script. - 2 rows (
bcm-data-exports:export,bcm-data-exports:table) stayed throttled through 6 backoff attempts; verdicts for those are unknown. - Single region, single partition (
aws), single point in time. Results drift as AWS adds services.
| service | smoke-test failure | probe verdict | predicted? |
|---|---|---|---|
| application-autoscaling | Unrecognized service or resource type |
NOT_TAGGABLE 1/1 | yes |
| cassandra | Unrecognized service or resource type |
NOT_TAGGABLE 3/3 | yes |
| sqlworkbench | (in the never-attempted tail) | NOT_TAGGABLE | yes |
| athena | InvalidRequestException resourceARN regex |
ROUTED | no |
| ssm | ValidationException resourceId must be mi-* |
ROUTED | no |
| cloudformation | No updates are to be performed |
ROUTED | no |
| events | ManagedRuleException |
ROUTED | no |
| kms | AccessDeniedException (key policy) |
ROUTED | no |
| cloudwatch / ec2 | Throttling / RequestLimitExceeded |
ROUTED | no |
Against the smoke test's 3014 discovered resources, the skip list would have pre-filtered 7 (4 application-autoscaling + 2 cassandra + 1 sqlworkbench). The skip list is real but small; the global-region bug is the valuable catch.
Keep the probe as a test-data generator, not a live test — it needs real credentials, so it cannot run in CI.
- New P0 — global-service region routing. Partition ARNs by service in
TagActionDispatch.processand tag the global set (cloudfront, waf, route53, globalaccelerator, networkmanager, savingsplans, cloudfront-keyvaluestore) through us-east-1. More urgent than the skip list: it is a policy-killer on any account holding those resources. The already-planned P0 (catch broadly per service group and continue) contains the blast radius for this and the empty-ARN crash — that one fix does double duty. - P1 — static skip list. Check the 348
NOT_TAGGABLEservice/type pairs intotests/data/, filter against it before dispatch. Zero runtime cost, refreshed manually by re-running the probe when AWS adds services. Include the 16 partially-unsupported services; per-type granularity matters there. - Do not try to predict id-format rejections from a probe. That class of failure
(
athena:named-query,ssm:managed-instance) needs handling at dispatch time — treatInvalidRequestException/ValidationExceptionon a well-formed ARN as a skip-and-log, not a batch failure.