crewAIInc/crewAI · error · ValueError

NL2SQLTool blocked an unrecognised SQL command '{main_cmd}'

Error message

NL2SQLTool blocked an unrecognised SQL command '{main_cmd}' after a CTE. Only {sorted(_READ_ONLY_COMMANDS)} are allowed in read-only mode.

What it means

After read-only CTEs, if the main statement's first keyword is neither a write command nor in _READ_ONLY_COMMANDS (SELECT/SHOW/DESCRIBE/EXPLAIN), the tool blocks it as unrecognised. This is a deny-by-default allowlist: dialect-specific or unusual statements after a CTE are rejected in read-only mode.

Source

Thrown at lib/crewai-tools/src/crewai_tools/tools/nl2sql/nl2sql_tool.py:365

            main_query = _extract_main_query_after_cte(stmt)
            if main_query:
                main_cmd = main_query.split()[0].upper().rstrip(";")
                if main_cmd in _WRITE_COMMANDS:
                    if not self.allow_dml:
                        raise ValueError(
                            f"NL2SQLTool is configured in read-only mode and blocked a "
                            f"'{main_cmd}' statement after a CTE. To allow write "
                            f"operations set allow_dml=True or "
                            f"CREWAI_NL2SQL_ALLOW_DML=true."
                        )
                    logger.warning(
                        "NL2SQLTool: executing '%s' after CTE because allow_dml=True.",
                        main_cmd,
                    )
                elif main_cmd not in _READ_ONLY_COMMANDS:
                    if not self.allow_dml:
                        raise ValueError(
                            f"NL2SQLTool blocked an unrecognised SQL command '{main_cmd}' "
                            f"after a CTE. Only {sorted(_READ_ONLY_COMMANDS)} are allowed "
                            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:

View on GitHub (pinned to 754d7323be)

Solutions

  1. Remove the non-read statement or move it to a separate call against an engine configured for it.
  2. Add the missing read-only statement outside a CTE form if supported, or use plain SELECT equivalents.
  3. Set allow_dml=True only if you accept that ALL writes become possible — there is no per-keyword bypass.

Example fix

# before (blocked)
tool._run("WITH recent AS (SELECT * FROM logs) SET work_mem = '64MB'")

# after
tool._run("WITH recent AS (SELECT * FROM logs) SELECT * FROM recent LIMIT 10")
Defensive patterns

Strategy: validation

Validate before calling

READ_ONLY = {"SELECT", "SHOW", "DESCRIBE", "EXPLAIN"}

def tail_cmd_after_cte(sql: str) -> str:
    idx = sql.rfind(")")
    tail = sql[idx+1:].strip() if idx != -1 else ""
    return tail.split()[0].upper().rstrip(";") if tail.split() else ""

if not allow_dml and tail_cmd_after_cte(sql) not in READ_ONLY:
    raise ValueError(f"command after CTE not allowed: {tail_cmd_after_cte(sql)}")

Try / catch

try:
    tool._run(sql)
except ValueError as e:
    if "after a CTE" in str(e):
        # rephrase to plain SELECT or run via direct engine
        ...

Prevention

When it happens

Trigger: Running 'WITH x AS (SELECT 1) VACUUM t', 'WITH x AS (...) SET search_path TO public', or any other statement whose leading keyword is not in the allowlist, while allow_dml=False.

Common situations: Portability statements like SET/USE/PRAGMA appended after CTEs; new or dialect-specific SQL keywords the allowlist does not know; LLM hallucinating an exotic command after a CTE.

Related errors


AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15). Data as JSON: /api/errors/6a17eee993075cda. Report an issue: GitHub.