crewAIInc/crewAI · error · ValueError

NL2SQLTool is configured in read-only mode and blocked a '{m

Error message

NL2SQLTool is configured in read-only mode and blocked a '{main_cmd}' statement after a CTE. To allow write operations set allow_dml=True or CREWAI_NL2SQL_ALLOW_DML=true.

What it means

For WITH statements whose CTEs are read-only, NL2SQLTool extracts the main query after the CTE definitions (_extract_main_query_after_cte) and checks its first keyword. If that keyword is in _WRITE_COMMANDS (DELETE, UPDATE, INSERT, ...), read-only mode raises ValueError — e.g. 'WITH x AS (SELECT 1) DELETE FROM users' is a write.

Source

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

                if not self.allow_dml:
                    raise ValueError(
                        f"NL2SQLTool is configured in read-only mode and blocked a "
                        f"writable CTE containing a '{found}' statement. To allow "
                        f"write operations set allow_dml=True or "
                        f"CREWAI_NL2SQL_ALLOW_DML=true."
                    )
                logger.warning(
                    "NL2SQLTool: executing writable CTE with '%s' because allow_dml=True.",
                    found,
                )
                return

            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

View on GitHub (pinned to 754d7323be)

Solutions

  1. If the mutation is intended, opt in: allow_dml=True or CREWAI_NL2SQL_ALLOW_DML=true.
  2. Otherwise strip the write part and keep only read statements.
  3. Tell the agent in the tool description/prompt that only SELECT/SHOW/DESCRIBE/EXPLAIN are allowed.

Example fix

# before (blocked)
tool._run("WITH t AS (SELECT id FROM users) DELETE FROM orders WHERE user_id IN (SELECT id FROM t)")

# after
NL2SQLTool(db_uri=uri, allow_dml=True)  # explicit opt-in for writes
Defensive patterns

Strategy: validation

Validate before calling

WRITE_CMDS = {"INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "CREATE", "TRUNCATE", "MERGE"}

def main_cmd_after_cte(sql: str) -> str:
    import re
    matches = list(re.finditer(r"AS\s*\(", sql))
    if matches:
        # naive: text after last top-level ')'
        idx = sql.rfind(")")
        tail = sql[idx+1:].strip()
        return tail.split()[0].upper() if tail.split() else ""
    return ""

if not allow_dml and main_cmd_after_cte(sql) in WRITE_CMDS:
    raise ValueError("write after CTE blocked")

Try / catch

try:
    tool._run(sql)
except ValueError as e:
    if "statement after a CTE" in str(e):
        route_to_write_tool_or_reject(sql)

Prevention

When it happens

Trigger: Passing 'WITH cte AS (SELECT ...) DELETE/UPDATE/INSERT/...' with allow_dml=False. LLMs commonly emit 'WITH latest AS (...) UPDATE ...' style statements on Postgres.

Common situations: Agent-generated CTE-wrapped mutations; refactoring of write scripts to use CTEs while the tool remains in default read-only mode.

Related errors


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