OpenAI Agents SDK SQLAlchemySession on MySQL: fixing 'VARCHAR requires a length on dialect mysql'
Point the Agents SDK's SQLAlchemy session at MySQL or MariaDB with create_tables=True and the first write fails before any table exists. Here's why, and a small schema script that works around it.
Disclosure: I work on MemorySync (I'm Mohammed, the founder). I drafted this article with AI assistance and checked it against the GitHub issue, the two related PRs, the OpenAI Agents SDK docs, the SDK source on PyPI and GitHub, the SQLAlchemy docs and the MemorySync docs listed under Sources on Oct 31, 2026. Code status: I ran everything offline with outbound connections blocked and no API keys, on Python 3.12.15 with openai-agents 0.22.0 (SQLAlchemy 2.0.52, the issue's versions) and 0.24.0 (SQLAlchemy 2.1.6, the latest release when I ran it on Oct 31). I compiled the SDK's own table definitions for MySQL, MariaDB, PostgreSQL and SQLite with SQLAlchemy's mock engine (no database server), compiled the Step 2 schema script for MySQL and MariaDB the same way, and ran the Step 2 script and the Step 3 session calls against a local SQLite file. I didn't run anything against a real MySQL or MariaDB server, a real model, Runner.run, or the MemorySync snippet.
You pointed the OpenAI Agents SDK's SQLAlchemySession at MySQL, passed create_tables=True, and the first add_items() (or the first Runner.run(..., session=session)) fails with:
sqlalchemy.exc.CompileError: (in table 'agent_sessions', column 'session_id'): VARCHAR requires a length on dialect mysql
With a mariadb+...:// URL, the last word is mariadb. That's openai-agents-python issue #4740, "SQLAlchemySession(create_tables=True) cannot create tables on MySQL due to unbounded String columns", reported on openai-agents 0.22.0 with MySQL 8.0.46.
Short version: the SDK declares both session_id columns as a String with no length, and SQLAlchemy won't compile that into a MySQL or MariaDB CREATE TABLE. There's no released fix. The fix PR was closed without merging. The workaround is to create the two tables yourself with a bounded VARCHAR (Step 2), then use create_tables=False (Step 3).
MemorySync doesn't fix this bug. It's in the SDK's SQLAlchemy session, and the fix below is plain SQLAlchemy. MemorySync comes up once, near the end, as a different place to keep session history.
Skip the session-table plumbing. The MemorySync OpenAI Agents SDK guide shows a hosted
SessionforRunner.run(..., session=...), so there's no session table to create. It doesn't fix thisSQLAlchemySessionbug. When you're ready, see MemorySync. Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).
Status (Nov 14, 2026)
- #4740 was opened Aug 28, 2026 (17:09 IST) and closed as completed on Sep 26 (03:19 IST) with 3 comments (still closed when I re-checked on Nov 14). The maintainer's closing comment points to the docs PR #4743, which was merged and documents which async driver to install for each database (
aiomysqlformysql+aiomysql://URLs). It doesn't change the table definitions. - The code fix, #4741 "fix(sessions): support MySQL and MariaDB schema creation", was closed without merging on Sep 25 (23:37 IST). The maintainer's comment: "We won't continue working on this due to our current priorities but may revisit it in the future."
- The latest release on PyPI is still openai-agents 0.24.0 (published Oct 15, 2026, 08:08 IST; re-checked Nov 14). Its
sqlalchemy_session.pystill declaresColumn("session_id", String, primary_key=True)and an unboundedStringfor the messages table'ssession_id, and so does themainbranch (source read Nov 14, not a run). - The SQLAlchemy session docs still say the class works with "any database supported by SQLAlchemy (PostgreSQL, MySQL, SQLite, etc.)". The API reference describes
create_tablesas "Defaults to False for production use. Set to True for development and testing when migrations aren't used."
Step 1: Confirm it's this bug
You're looking at this bug if all of these are true:
- You use
SQLAlchemySession(orSQLAlchemySession.from_url(...)) with amysql+...://ormariadb+...://URL. - You pass
create_tables=True. - The error is a
CompileErrornamingagent_sessions(or yoursessions_table=name), columnsession_id, and ends withVARCHAR requires a length on dialect mysql(ormariadb).
Per the issue, the connection succeeds and the error comes while SQLAlchemy compiles the first CREATE TABLE, "before any CREATE statement reaches MySQL. No tables are created."
MySQL and MariaDB need a length on every VARCHAR. PostgreSQL and SQLite don't, which is why the same code works there. Here's what I got compiling the SDK's own two tables for each database with SQLAlchemy's mock engine:
| Database (dialect) | openai-agents 0.22.0 + SQLAlchemy 2.0.52 | openai-agents 0.24.0 + SQLAlchemy 2.1.6 |
|---|---|---|
| MySQL | CompileError: ... VARCHAR requires a length on dialect mysql |
same error |
| MariaDB | CompileError: ... VARCHAR requires a length on dialect mariadb |
same error |
| PostgreSQL | both tables and the index compile | same |
| SQLite | both tables and the index compile | same |
The error text matched the issue's exactly. The mock engine only compiles SQL, so this confirms the cause, not anything that happens on a live server.
If switching databases is an option, PostgreSQL (postgresql+asyncpg://, whose driver the sqlalchemy extra already installs, per the docs) or SQLite works with create_tables=True as is. If you need MySQL or MariaDB, keep going.
Step 2: Create the tables yourself
SQLAlchemySession only needs two tables with the right names and columns. The column types matter only when the tables are created: reading and writing just use the column names (Step 3). So you can create the tables once with MySQL-safe types and let the SDK use them.
Save this as create_agent_session_tables.py. It mirrors the SDK's two tables and index, but gives session_id a bounded type on MySQL and MariaDB using SQLAlchemy's with_variant(), the approach the issue suggested and PR #4741 used:
# create_agent_session_tables.py
"""Create the SQLAlchemySession tables with MySQL/MariaDB-safe column types.
Run it once per database, or copy the two tables into your migration tool."""
import asyncio
import os
from sqlalchemy import (
TIMESTAMP, Column, ForeignKey, Index, Integer, MetaData, String, Table, Text, text,
)
from sqlalchemy.dialects import mysql
from sqlalchemy.ext.asyncio import create_async_engine
SESSIONS_TABLE = "agent_sessions" # match sessions_table= if you override it
MESSAGES_TABLE = "agent_messages" # match messages_table= if you override it
# Bounded, case-sensitive session IDs on MySQL/MariaDB; plain VARCHAR elsewhere.
SESSION_ID = String().with_variant(
mysql.VARCHAR(190, charset="utf8mb4", collation="utf8mb4_bin"), "mysql", "mariadb"
)
# LONGTEXT on MySQL/MariaDB so large items aren't cut off at 64 KB.
MESSAGE_DATA = Text().with_variant(mysql.LONGTEXT(), "mysql", "mariadb")
metadata = MetaData()
Table(
SESSIONS_TABLE,
metadata,
Column("session_id", SESSION_ID, primary_key=True),
Column("created_at", TIMESTAMP(timezone=False),
server_default=text("CURRENT_TIMESTAMP"), nullable=False),
Column("updated_at", TIMESTAMP(timezone=False),
server_default=text("CURRENT_TIMESTAMP"), nullable=False),
)
Table(
MESSAGES_TABLE,
metadata,
Column("id", Integer, primary_key=True, autoincrement=True),
Column("session_id", SESSION_ID,
ForeignKey(f"{SESSIONS_TABLE}.session_id", ondelete="CASCADE"), nullable=False),
Column("message_data", MESSAGE_DATA, nullable=False),
Column("created_at", TIMESTAMP(timezone=False),
server_default=text("CURRENT_TIMESTAMP"), nullable=False),
Index(f"idx_{MESSAGES_TABLE}_session_time", "session_id", "created_at"),
sqlite_autoincrement=True,
)
async def main() -> None:
engine = create_async_engine(os.environ["DATABASE_URL"])
async with engine.begin() as conn:
await conn.run_sync(metadata.create_all)
await engine.dispose()
if __name__ == "__main__":
asyncio.run(main())
Run it once with DATABASE_URL set to the same mysql+aiomysql://... URL your app uses (install the driver first: pip install 'openai-agents[sqlalchemy]' 'aiomysql[rsa]', per the docs). If you use Alembic or another migration tool, copy the two tables into a migration instead.
For MySQL, this compiled (mock engine, both SQLAlchemy versions) to:
CREATE TABLE agent_sessions (
session_id VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (session_id)
)
CREATE TABLE agent_messages (
id INTEGER NOT NULL AUTO_INCREMENT,
session_id VARCHAR(190) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
message_data LONGTEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
FOREIGN KEY(session_id) REFERENCES agent_sessions (session_id) ON DELETE CASCADE
)
CREATE INDEX idx_agent_messages_session_time ON agent_messages (session_id, created_at)
MariaDB compiled to the same three statements. You can also run these statements by hand, or adapt them to your migrations.
Why these choices, and where each comes from:
VARCHAR(190). PR #4741 picked 190 so the(session_id, created_at)index "remains within the traditional 767-byte InnoDB key limit underutf8mb4" (190 characters × 4 bytes = 760). If your server allows longer index keys and your session IDs are longer, pick a bigger length. Either way, session IDs longer than the column won't fit, so keep yours short (a UUID is 36 characters).CHARACTER SET utf8mb4 COLLATE utf8mb4_bin. In the PR #4741 review, a case-insensitive collation would makeFooandfooshare one history. The PR switched toutf8mb4_binand declared the character set with it, because, per the PR, a collation alone inherits the database's character set and the server rejectsutf8mb4_binon a non-utf8mb4 one. The same review noted thatutf8mb4_binstill ignores trailing spaces when comparing, so don't build session IDs that differ only by a trailing space.LONGTEXTformessage_data. The SDK usesText, which is MySQL's 64 KBTEXT. In PR #4910 (also closed without merging), the author's live test stored a 1,200,028-byte serialized item: strict mode rejected the insert with error 1406, non-strict mode cut it to 65,535 bytes of invalid JSON, andLONGTEXTkept it intact.
What I checked offline, on both SDK versions:
| Check | Result |
|---|---|
| Step 2 tables compile for MySQL and MariaDB (mock engine) | yes, the statements above |
| Same table names, column names, primary keys, nullability, foreign key and index name as the SDK's own tables | yes |
The SDK's own INSERT / SELECT / DELETE against these tables compile for MySQL with the unbounded String columns |
yes (only CREATE TABLE needs a length) |
Step 2 script run against a local SQLite file, then Step 3's session: add_items, get_items, pop_item, clear_session |
all worked; clear_session left another session's items alone |
I didn't run the script against a real MySQL or MariaDB server, so I haven't seen server-side behavior (character set, index limits, strict mode) myself. The PR #4741 author reported live runs on MySQL 8.0.46, MySQL 5.7.44 and MariaDB 11.8.9 for the same column type, with the SDK's patched code rather than this script.
Step 3: Use the session with create_tables=False
Now point the SDK at the tables you created and turn off its table creation:
import os
from agents import Agent, Runner
from agents.extensions.memory import SQLAlchemySession
agent = Agent(name="Assistant", instructions="Reply concisely.")
session = SQLAlchemySession.from_url(
"user-123",
url=os.environ["DATABASE_URL"], # mysql+aiomysql://...
create_tables=False,
)
result = await Runner.run(agent, "Hello", session=session)
If you passed sessions_table= or messages_table= to the session, set SESSIONS_TABLE and MESSAGES_TABLE in Step 2 to the same names. I ran the session calls Runner.run makes (add_items, get_items, pop_item, clear_session) on SQLite with create_tables=False; I didn't run Runner.run itself or the MySQL URL.
Why False: with create_tables=True the SDK runs SQLAlchemy's create_all() before its first operation. PR #4741 notes that create_all(checkfirst=True) "leaves an existing table untouched", and on SQLite, create_tables=True on top of my Step 2 tables worked. But the SDK still builds its MySQL table definitions with the unbounded String, so False keeps it from compiling them at all. I didn't test True against a real MySQL server with existing tables. The API reference already recommends False for production.
Step 4: Check it worked
- Run the Step 2 script once, then run your app with
create_tables=False. - Confirm the first
Runner.run(..., session=session)no longer raises theCompileError. - Run a second turn with the same session ID and check that the agent sees the first turn.
await session.get_items()should return the items from both turns. - Optional: in a MySQL client,
SHOW CREATE TABLE agent_messages;should showvarchar(190)forsession_idandlongtextformessage_data.
Skip the plumbing with MemorySync
Disclosure: I build MemorySync.
This doesn't fix the SQLAlchemySession bug, and if you need your session history in your own MySQL database, Steps 2 and 3 are the way. If MySQL was only where you planned to keep conversation history, the MemorySync OpenAI Agents SDK guide documents MemorySyncSession, which implements the SDK's Session protocol for Runner.run(..., session=...) and keeps conversation history on MemorySync's side, so there's no session table to create or migrate. Per the guide, items come back "exactly as the SDK wrote them", and user messages are also sent to fact extraction so durable facts become that user's long-term memories.
From the guide:
Unverified (not run):
pip install openai-agents-memorysync openai-agents
from agents import Agent, Runner
from openai_agents_memorysync import MemorySyncSession
agent = Agent(
name="Assistant",
instructions="You are a helpful assistant.",
)
session = MemorySyncSession(
"thread-42", # the conversation
user_id="customer-7", # the end user it belongs to — required
)
result = await Runner.run(agent, "I'm vegetarian and I fly aisle.", session=session)
The guide says it needs Python 3.10+ and MEMORYSYNC_API_KEY in the environment (or api_key passed explicitly). Snippet adapted from the MemorySync OpenAI Agents SDK guide; I haven't run it for this page.
Try it: MemorySync is optional here: the steps above work without it. If you want the memory layer from the snippet, see MemorySync, create an API key in the dashboard and drop the snippet into your agent.
Troubleshooting
no such table/ "doesn't exist" errors after switching tocreate_tables=False. The Step 2 script didn't run against the same database (or used different table names). On SQLite, skipping Step 2 gaveno such table: agent_sessionson the firstadd_items().- The same
CompileErrorafter Step 2. Something still hascreate_tables=Trueon a MySQL URL, often a second place that builds the session (a worker, a test fixture). Search your code forcreate_tables=True. - An import error for
aiomysql. Thesqlalchemyextra doesn't installaiomysql; per the docs, install'aiomysql[rsa]'alongside it. - You already created the tables by hand with
VARCHAR(255). That's fine forcreate_tables=False; the SDK reads and writes by column name. Just make suremessage_datais big enough for your largest items (seeLONGTEXTabove). - You use Redis Cluster instead. That's a different session backend with a different error; see Fix "'ClusterPipeline' object has no attribute 'raise_first_error'" with OpenAI Agents SDK RedisSession on Redis Cluster.
Common questions
Does this affect SQLiteSession or PostgreSQL?
No. In my compile check, the SDK's tables compiled for SQLite and PostgreSQL on both versions. The problem is specific to MySQL and MariaDB.
Will a future SDK release fix this?
Maybe. The maintainers said they "may revisit it in the future". Until then, the Step 2 tables keep working with create_tables=False. If a release adds MySQL-safe types, check its changelog before switching back to create_tables=True.
Do I need to change existing data?
Not for this error: it happens before any table exists, so there's nothing to migrate. If you already have tables with a TEXT message_data column, PR #4910 notes it doesn't migrate existing tables; changing the column to LONGTEXT is your call.
Is 190 characters enough?
It's enough for UUIDs and most user-123:thread-456 style IDs. If you need longer IDs, raise the length in Step 2 and check that your MySQL version and row format allow the longer index key.
Related pages
- OpenAI Agents SDK: 'No tool call found for function call output' after an approval resume
- Fix "'ClusterPipeline' object has no attribute 'raise_first_error'" with OpenAI Agents SDK RedisSession on Redis Cluster
- LangGraph AsyncPostgresSaver: fixing 'psycopg.OperationalError: the connection is closed' in FastAPI
Sources
- github.com/openai/openai-agents-python/issues/4740 (opened 2026-08-28, closed as completed 2026-09-26 IST, 3 comments; exact error, reproduction, versions; read via the GitHub API 2026-10-31, re-checked 2026-11-14)
- github.com/openai/openai-agents-python/pull/4741 (closed without merging 2026-09-25;
VARCHAR(190)+utf8mb4_bindesign, 767-byte reasoning, review notes, maintainer's closing comment; diff and comments read 2026-10-31) - github.com/openai/openai-agents-python/pull/4743 (merged 2026-09-07; driver installation docs)
- github.com/openai/openai-agents-python/pull/4910 (closed without merging 2026-09-25;
LONGTEXTreasoning and the author's live MySQL/MariaDB results) - PyPI: openai-agents 0.22.0 and 0.24.0 (release times; installed
agents/extensions/memory/sqlalchemy_session.py); the same file on themainbranch (read 2026-10-31, re-read 2026-11-14) - OpenAI Agents SDK docs: SQLAlchemy sessions, Sessions overview,
SQLAlchemySessionAPI reference (fetched 2026-10-31) - SQLAlchemy docs:
TypeEngine.with_variant()(docs.sqlalchemy.org, core type API) - Offline runs (2026-10-31): the SDK's tables compiled with SQLAlchemy's mock engine for MySQL, MariaDB, PostgreSQL and SQLite on openai-agents 0.22.0 and 0.24.0; the Step 2 script compiled for MySQL and MariaDB and run against SQLite; Step 3 session calls on SQLite; no network, no database server, no keys
- MemorySync OpenAI Agents SDK guide: docs.memorysync.io/guides/openai-agents (fetched 2026-10-31)
Try it
For an overview of MemorySync, see the MemorySync homepage.
Next step: read the MemorySync OpenAI Agents SDK guide, then see MemorySync. Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).
About the author
- Building MemorySync (memorysync.io) — persistent memory infrastructure for AI agents, Cursor, and Claude Code.
Comments
Join the discussionNo comments yet. Be the first to comment.
