crewAIInc/crewAI · error · ValueError
NL2SQLTool blocked an unrecognised SQL command '{command}'.
Error message
NL2SQLTool blocked an unrecognised SQL command '{command}'. Only {sorted(_READ_ONLY_COMMANDS)} are allowed in read-only mode. What it means
Deny-by-default allowlist for unknown commands: if a statement's first keyword is neither in _WRITE_COMMANDS nor _READ_ONLY_COMMANDS (SELECT, SHOW, DESCRIBE, EXPLAIN), read-only mode raises ValueError listing the allowed set. This catches dialect utilities (SET, PRAGMA, VACUUM, CALL, GRANT, ...) and typos that would otherwise slip past a write-only blocklist.
Source
Thrown at lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py:386
f"in read-only mode."
)
return
if command in _WRITE_COMMANDS:
if not self.allow_dml:
raise ValueError(
f"NL2SQLTool is configured in read-only mode and blocked a "
f"'{command}' statement. To allow write operations set "
f"allow_dml=True or CREWAI_NL2SQL_ALLOW_DML=true."
)
logger.warning(
"NL2SQLTool: executing write statement '%s' because allow_dml=True.",
command,
)
elif command not in _READ_ONLY_COMMANDS:
# Unknown command — block by default unless DML is explicitly enabled
if not self.allow_dml:
raise ValueError(
f"NL2SQLTool blocked an unrecognised SQL command '{command}'. "
f"Only {sorted(_READ_ONLY_COMMANDS)} are allowed in read-only "
f"mode."
)
@staticmethod
def _extract_command(sql_query: str) -> str:
"""Return the uppercased first keyword of *sql_query*."""
stripped = sql_query.strip().lstrip("(")
first_token = stripped.split()[0] if stripped.split() else ""
return first_token.upper().rstrip(";")
# Schema introspection helpers
def _fetch_available_tables(self) -> list[dict[str, Any]] | str:
return self.execute_sql(
"SELECT table_name FROM information_schema.tables "
"WHERE table_schema = 'public';"View on GitHub (pinned to 754d7323be)
Solutions
- Check the first keyword of your SQL; correct typos or rephrase to an allowed read statement (SELECT/SHOW/DESCRIBE/EXPLAIN).
- Run session or admin commands through a direct SQLAlchemy connection, not NL2SQLTool.
- Set allow_dml=True only if you understand it disables all read-only guarding, including this allowlist.
Example fix
# before (blocked: 'SELEC' typo)
tool._run("SELEC * FROM users")
# after
tool._run("SELECT * FROM users") Defensive patterns
Strategy: validation
Validate before calling
READ_ONLY = {"SELECT", "SHOW", "DESCRIBE", "EXPLAIN"}
WRITE = {"INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "CREATE", "TRUNCATE", "MERGE"}
def classify(sql: str) -> str:
kw = sql.strip().lstrip("(").split()[0].upper().rstrip(";") if sql.strip() else ""
return "read" if kw in READ_ONLY else "write" if kw in WRITE else "unknown"
if not tool.allow_dml and classify(sql) == "unknown":
raise ValueError(f"unknown command will be blocked: {sql[:40]}") Type guard
def is_known_sql_command(sql: str) -> bool:
kw = sql.strip().lstrip("(").split()[0].upper().rstrip(";") if sql.strip() else ""
return kw in {"SELECT", "SHOW", "DESCRIBE", "EXPLAIN"} | {"INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "CREATE", "TRUNCATE", "MERGE"} Try / catch
try:
tool._run(sql)
except ValueError as e:
if "unrecognised SQL command" in str(e):
sql = repair_keyword_typo(sql) # e.g. SELEC -> SELECT
tool._run(sql) Prevention
- Allowlist-check the first keyword before submitting agent-generated SQL.
- Route SET/PRAGMA/CALL and other utilities through a direct SQLAlchemy connection.
- Add few-shot examples of valid SELECT queries to the agent prompt.
When it happens
Trigger: Running 'SET timezone=UTC', 'PRAGMA table_info(t)', 'CALL proc()', 'USE mydb', or a typo like 'SELEC * FROM t' with allow_dml=False.
Common situations: LLM emits session/dialect commands the allowlist does not know; SQLite PRAGMAs; MySQL USE statements; keyword typos from the model.
Related errors
- NL2SQLTool blocked an unrecognised SQL command '{main_cmd}'
- NL2SQLTool blocked a multi-statement query in read-only mode
- NL2SQLTool is configured in read-only mode and blocked a '{c
- NL2SQLTool received an empty SQL query.
- NL2SQLTool is configured in read-only mode and blocked a wri
AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15).
Data as JSON: /api/errors/3ee5d2c8562e92b5.
Report an issue: GitHub.