t8y2/dbx · error
The built-in exchange '%s' cannot be deleted
Error message
The built-in exchange '%s' cannot be deleted
What it means
Exchanges named 'amq.*' are built-in exchanges predeclared by RabbitMQ and are reserved/deletion-protected by the broker. The driver refuses to delete them (and the default '' exchange) client-side to avoid guaranteed broker failures.
Source
Thrown at agents/drivers/rabbitmq/operations.go:487
if strings.TrimSpace(name) == "" {
return "", errors.New("name is required")
}
return name, nil
}
func validateExchangeType(exchangeType string) (string, error) {
if _, ok := exchangeTypes[exchangeType]; !ok {
return "", fmt.Errorf("Invalid exchange type '%s'. Supported types: direct, fanout, topic, headers", exchangeType)
}
return exchangeType, nil
}
func assertExchangeDeletable(name string) error {
if name == "" {
return errors.New("The default exchange cannot be deleted")
}
if strings.HasPrefix(name, "amq.") {
return fmt.Errorf("The built-in exchange '%s' cannot be deleted", name)
}
return nil
}
func (s *server) listBindings(params jsonObject) (any, error) {
connection, err := s.requireConnectionConfig(params)
if err != nil {
return nil, err
}
allVhosts := allVhostsRequested(params)
bindings, err := managementGetAll(connection, managementListPath(params, connection, "bindings"))
if err != nil {
return nil, err
}
exchangeFilter := stringOrEmpty(params, "exchange")
queueFilter := stringOrEmpty(params, "queue")
result := make([]jsonObject, 0, len(bindings))
for _, value := range bindings {View on GitHub (pinned to c0390bff16)
Solutions
- Filter out 'amq.'-prefixed and empty-name exchanges from your delete list
- Delete only application-created exchanges
- Adjust discovery code to skip built-ins when enumerating exchanges
Example fix
// before
for (const e of exchanges) await client.deleteExchange({"name": e.name}) // includes amq.topic
// after
for (const e of exchanges) {
if (e.name !== "" && !e.name.startsWith("amq.")) await client.deleteExchange({"name": e.name})
} Defensive patterns
Strategy: validation
Validate before calling
function deletableExchange(name) {
return typeof name === "string" && name !== "" && !name.startsWith("amq.")
} Type guard
function isUserExchange(v) {
return typeof v === "string" && v.length > 0 && !v.startsWith("amq.")
} Try / catch
try {
await client.deleteExchange({ name })
} catch (e) {
if (String(e.message).includes("built-in exchange")) {
console.warn(`skipping protected exchange ${name}`)
} else { throw e }
} Prevention
- Filter 'amq.'-prefixed and empty names out of any bulk-delete list
- Only delete exchanges your application created and recorded
- Keep an allowlist of app-owned exchange names
When it happens
Trigger: Calling deleteExchange with a name starting with 'amq.' (e.g. 'amq.direct', 'amq.topic') or the empty-string default exchange.
Common situations: Listing exchanges from the broker and blindly deleting every entry, including built-ins; cleanup scripts iterating all exchanges without filtering.
Related errors
- Cannot delete the virtual host '%s' while connected to it
- Cannot %s user '%s' while connected as that user
- Not connected. Call connect first.
- name is required
- Invalid exchange type '%s'. Supported types: direct, fanout,
AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05).
Data as JSON: /api/errors/6e211d214898688c.
Report an issue: GitHub.