hasura/graphql-engine · error · DuplicateRootFieldError

Cannot add query root field {0} as it already in use

Error message

Cannot add query root field {0} as it already in use

What it means

DuplicateRootFieldError::Query from the TrackGraphQLRootFields helper in metadata-resolve. It fires when two metadata components try to register a GraphQL root field with the same name on the Query root type (e.g. two commands or a command and a model with identical GraphQL names).

Source

Thrown at v3/crates/metadata-resolve/src/helpers/types.rs:36

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Hash)]
pub struct NdcColumnForComparison {
    pub column: DataConnectorColumnName,
    pub equal_operator: DataConnectorOperatorName,
}

/// Track the root fields of the GraphQL schema while resolving the metadata.
/// This is used to ensure that the schema has unique root fields for Query, Mutation and Subscription.
// NOTE: The `ast::Name` is cheap to clone, so storing them directly without references
pub struct TrackGraphQLRootFields {
    pub query: BTreeSet<ast::Name>,
    pub mutation: BTreeSet<ast::Name>,
    pub subscription: BTreeSet<ast::Name>,
}

#[derive(Debug, thiserror::Error)]
pub enum DuplicateRootFieldError {
    #[error("Cannot add query root field {0} as it already in use")]
    Query(ast::Name),
    #[error("Cannot add mutation root field {0} as it already in use")]
    Mutation(ast::Name),
    #[error("Cannot add subscription root field {0} as it already in use")]
    Subscription(ast::Name),
}

impl TrackGraphQLRootFields {
    pub fn new() -> Self {
        Self {
            query: BTreeSet::new(),
            mutation: BTreeSet::new(),
            subscription: BTreeSet::new(),
        }
    }
    pub fn track_query_root_field(
        &mut self,
        name: &ast::Name,

View on GitHub (pinned to 724551b9ae)

Solutions

  1. Rename one of the colliding GraphQL root fields (set a distinct graphql field name on the command/model)
  2. Delete the duplicated metadata object if it was accidentally cloned
  3. If a model plus custom command overlap, expose the command on the model rather than at the root or vice versa

Example fix

# before
- kind: Command
  name: users
# ...
- kind: Model
  name: users   # same root field name

# after
- kind: Command
  name: users_custom   # renamed to avoid collision
Defensive patterns

Strategy: validation

Validate before calling

// Before adding, check uniqueness (mirrors the library's own BTreeSet check):
let inserted = query_fields.insert(name.clone());
if !inserted {
    return Err(DuplicateRootFieldError::Query(name));
}

Try / catch

match err {
    DuplicateRootFieldError::Query(name) => eprintln!("rename the command/model exposing query field '{name}'"),
    _ => {}
}

Prevention

When it happens

Trigger: Calling the query root-field registration API twice with the same ast::Name, e.g. a model and a derived command both named users_by_pk exposed at the Query root.

Common situations: Copy-pasted commands with the same GraphQL field name, a command name colliding with a model root field, or two relationships/commands in different subgraphs producing the same root field name.

Related errors


AI-assisted analysis of hasura/graphql-engine@724551b9ae (2026-08-28). Data as JSON: /api/errors/868c217a9d8b97c7. Report an issue: GitHub.