floci-io/floci · error · AwsException
DistributionNotDisabled
DistributionNotDisabled
Error message
The distribution you are trying to delete has not been disabled.
What it means
Floci's CloudFront service layer refuses to delete a distribution whose config still has Enabled=true, returning DistributionNotDisabled (HTTP 409). AWS requires a distribution to be fully disabled before deletion, and the emulator enforces the same state machine after the ETag check passes.
Source
Thrown at src/main/java/io/github/hectorvent/floci/services/cloudfront/CloudFrontService.java:165
updated.setId(id);
updated.setArn(existing.getArn());
updated.setDomainName(existing.getDomainName());
updated.setStatus("Deployed");
updated.setLastModifiedTime(Instant.now());
updated.setEtag(UUID.randomUUID().toString());
updated.setTags(existing.getTags());
distStore.put(id, updated);
return updated;
}
public synchronized void deleteDistribution(String id, String ifMatch) {
Distribution existing = getDistribution(id);
if (!existing.getEtag().equals(ifMatch)) {
throw new AwsException("InvalidIfMatchVersion",
"The If-Match version is missing or not valid for the resource.", 400);
}
if (existing.getConfig() != null && existing.getConfig().isEnabled()) {
throw new AwsException("DistributionNotDisabled",
"The distribution you are trying to delete has not been disabled.", 409);
}
distStore.delete(id);
invalidationStore.delete(id);
tagStore.delete("distribution/" + id);
}
/**
* Removes a distribution and its associated invalidations/tags without the disable/If-Match guards
* enforced by {@link #deleteDistribution(String, String)}. Used by CloudFormation stack deletion,
* which owns the resource lifecycle at the stack level.
*/
public synchronized void removeDistribution(String id) {
distStore.delete(id);
invalidationStore.delete(id);
tagStore.delete("distribution/" + id);
}
View on GitHub (pinned to 62ff490619)
Solutions
- First update the distribution with Enabled=false (using the current ETag as If-Match)
- Then re-GET the config for the new ETag and issue the delete with it
- Model the full sequence: disable -> (optionally wait for status) -> delete
Example fix
// before cf.deleteDistribution(req -> req.id(id).ifMatch(etag)); // after: disable first, then delete with refreshed etag DistributionConfig disabled = cfg.toBuilder().enabled(false).build(); cf.updateDistribution(req -> req.id(id).ifMatch(etag).distributionConfig(disabled)); String freshEtag = cf.getDistributionConfig(r -> r.id(id)).eTag(); cf.deleteDistribution(req -> req.id(id).ifMatch(freshEtag));
Defensive patterns
Strategy: validation
Validate before calling
GetDistributionConfigResponse cur = cf.getDistributionConfig(r -> r.id(id));
if (Boolean.TRUE.equals(cur.distributionConfig().enabled())) {
// must disable first: update with enabled=false using cur.eTag()
} Try / catch
try {
cf.deleteDistribution(req -> req.id(id).ifMatch(etag));
} catch (CloudFrontException e) {
if ("DistributionNotDisabled".equals(e.awsErrorDetails().errorCode())) {
DistributionConfig off = cfg.toBuilder().enabled(false).build();
cf.updateDistribution(req -> req.id(id).ifMatch(etag).distributionConfig(off));
String fresh = cf.getDistributionConfig(r -> r.id(id)).eTag();
cf.deleteDistribution(req -> req.id(id).ifMatch(fresh));
} else { throw e; }
} Prevention
- Encode the disable-before-delete sequence in a single teardown helper
- Treat 409 from CloudFront deletes as a state error, not a transient one — fix state, don't blind-retry
When it happens
Trigger: DELETE /distribution/{Id} with a valid If-Match while the distribution's DistributionConfig has Enabled=true.
Common situations: Teardown scripts that create, test, and delete in one run without a disable step; forgetting that disable is itself an update (which also rotates the ETag); assuming the emulator skips lifecycle guards.
Related errors
- StreamingDistributionNotDisabled
- InvalidIfMatchVersion
- TrustedKeyGroupDoesNotExist
- Boot hook execution interrupted
- Boot hook execution failed
AI-assisted analysis of floci-io/floci@62ff490619 (2026-08-14).
Data as JSON: /api/errors/3e2413c7259c9513.
Report an issue: GitHub.