crewAIInc/crewAI · warning · ValueError
NL2SQLTool received an empty SQL query.
Error message
NL2SQLTool received an empty SQL query.
What it means
NL2SQLTool._validate_query splits the submitted sql_query on semicolons and discards empty fragments; if nothing remains, the query was empty or only whitespace/semicolons, and a ValueError is raised. This guards the executor against empty statements before touching the database.
Source
Thrown at lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py:305
self.tables = tables
self.columns = data
# Query validation
def _validate_query(self, sql_query: str) -> None:
"""Raise ValueError if *sql_query* is not permitted under the current config.
Splits the query on semicolons and validates each statement
independently. When ``allow_dml=False`` (the default), multi-statement
queries are rejected outright to prevent ``SELECT 1; DROP TABLE users``
style bypasses. When ``allow_dml=True`` every statement is checked and
a warning is emitted for write operations.
"""
statements = [s.strip() for s in sql_query.split(";") if s.strip()]
if not statements:
raise ValueError("NL2SQLTool received an empty SQL query.")
if not self.allow_dml and len(statements) > 1:
raise ValueError(
"NL2SQLTool blocked a multi-statement query in read-only mode. "
"Semicolons are not permitted when allow_dml=False."
)
for stmt in statements:
self._validate_statement(stmt)
def _validate_statement(self, stmt: str) -> None:
"""Validate a single SQL statement (no semicolons)."""
command = self._extract_command(stmt)
# EXPLAIN ANALYZE / EXPLAIN ANALYSE actually *executes* the underlying
# query. Resolve the real command so write operations are caught.
# parenthesized ("EXPLAIN (ANALYZE) DELETE …", "EXPLAIN (ANALYZE, VERBOSE) DELETE …").
# EXPLAIN ANALYZE actually executes the underlying query — resolve theView on GitHub (pinned to 754d7323be)
Solutions
- Log the raw sql_query the agent actually sent and fix the prompt/template so a real SQL statement is passed.
- Reject empty queries in your own code before invoking the tool.
- If the agent may legitimately have nothing to ask, add an early branch that skips the tool call.
Example fix
# before
tool._run("")
# after
if not sql_query or not sql_query.strip("; \t\n"):
raise ValueError("refusing to run empty SQL")
tool._run(sql_query) Defensive patterns
Strategy: validation
Validate before calling
def has_statement(sql: str) -> bool:
return any(s.strip() for s in sql.split(";"))
if not has_statement(sql_query):
raise ValueError("no SQL statement to run") Type guard
def is_non_empty_sql(sql: str | None) -> bool:
return isinstance(sql, str) and bool(sql.strip("; \t\r\n")) Try / catch
try:
tool._run(sql_query)
except ValueError as e:
if "empty SQL query" in str(e):
# ask the agent to regenerate the query
... Prevention
- Check sql_query.strip() before invoking the tool.
- Validate agent tool-call args against NL2SQLToolInput (pydantic) with min_length on sql_query.
- Log raw agent arguments when a tool call fails to spot interpolation bugs.
When it happens
Trigger: Calling the tool (or _run) with sql_query="", sql_query=" ", or sql_query=";;;". Typically an LLM agent emits an empty string because it put the SQL in the wrong field or failed to generate a query.
Common situations: Agent produces an empty tool call argument; upstream prompt/variable interpolation yields an empty string; a templating bug passes the placeholder name instead of its value.
Related errors
- Query cannot be empty
- return_columns cannot be empty. At least one column must be
- NL2SQLTool blocked a multi-statement query in read-only mode
- NL2SQLTool blocked an unrecognised SQL command '{main_cmd}'
- NL2SQLTool is configured in read-only mode and blocked a '{c
AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15).
Data as JSON: /api/errors/3209aad73aef9e91.
Report an issue: GitHub.