# OpenAI Agents SDK SQLAlchemySession on MySQL: fixing 'VARCHAR requires a length on dialect mysql'

*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:

```text
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](https://github.com/openai/openai-agents-python/issues/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](https://docs.memorysync.io/guides/openai-agents?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_openai-agents-sdk-sqlalchemysession-mysql-varchar-requires-a-length) shows a hosted `Session` for `Runner.run(..., session=...)`, so there's no session table to create. It doesn't fix this `SQLAlchemySession` bug. When you're ready, [see MemorySync](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_openai-agents-sdk-sqlalchemysession-mysql-varchar-requires-a-length). Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).

## Status (Nov 14, 2026)

- [#4740](https://github.com/openai/openai-agents-python/issues/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](https://github.com/openai/openai-agents-python/pull/4743), which was merged and documents which async driver to install for each database (`aiomysql` for `mysql+aiomysql://` URLs). It doesn't change the table definitions.
- The code fix, [#4741](https://github.com/openai/openai-agents-python/pull/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](https://pypi.org/project/openai-agents/) (published Oct 15, 2026, 08:08 IST; re-checked Nov 14). Its `sqlalchemy_session.py` still declares `Column("session_id", String, primary_key=True)` and an unbounded `String` for the messages table's `session_id`, and so does the `main` branch (source read Nov 14, not a run).
- The [SQLAlchemy session docs](https://openai.github.io/openai-agents-python/sessions/sqlalchemy_session/) still say the class works with "any database supported by SQLAlchemy (PostgreSQL, MySQL, SQLite, etc.)". The [API reference](https://openai.github.io/openai-agents-python/ref/extensions/memory/sqlalchemy_session/) describes `create_tables` as "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:

1. You use `SQLAlchemySession` (or `SQLAlchemySession.from_url(...)`) with a `mysql+...://` or `mariadb+...://` URL.
2. You pass `create_tables=True`.
3. The error is a `CompileError` naming `agent_sessions` (or your `sessions_table=` name), column `session_id`, and ends with `VARCHAR requires a length on dialect mysql` (or `mariadb`).

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](https://openai.github.io/openai-agents-python/sessions/sqlalchemy_session/)) 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()`](https://docs.sqlalchemy.org/en/20/core/type_api.html), the approach the issue suggested and PR #4741 used:

```python
# 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](https://openai.github.io/openai-agents-python/sessions/sqlalchemy_session/)). 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:

```sql
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 under `utf8mb4`" (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 make `Foo` and `foo` share one history. The PR switched to `utf8mb4_bin` and declared the character set with it, because, per the PR, a collation alone inherits the database's character set and the server rejects `utf8mb4_bin` on a non-utf8mb4 one. The same review noted that `utf8mb4_bin` still ignores trailing spaces when comparing, so don't build session IDs that differ only by a trailing space.
- **`LONGTEXT` for `message_data`.** The SDK uses `Text`, which is MySQL's 64 KB `TEXT`. In PR [#4910](https://github.com/openai/openai-agents-python/pull/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, and `LONGTEXT` kept 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:

```python
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

1. Run the Step 2 script once, then run your app with `create_tables=False`.
2. Confirm the first `Runner.run(..., session=session)` no longer raises the `CompileError`.
3. 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.
4. Optional: in a MySQL client, `SHOW CREATE TABLE agent_messages;` should show `varchar(190)` for `session_id` and `longtext` for `message_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](https://docs.memorysync.io/guides/openai-agents?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_openai-agents-sdk-sqlalchemysession-mysql-varchar-requires-a-length) 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):**

```bash
pip install openai-agents-memorysync openai-agents
```

```python
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](https://docs.memorysync.io/guides/openai-agents?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_openai-agents-sdk-sqlalchemysession-mysql-varchar-requires-a-length); 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](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_openai-agents-sdk-sqlalchemysession-mysql-varchar-requires-a-length), create an API key in the dashboard and drop the snippet into your agent.

## Troubleshooting

- **`no such table` / "doesn't exist" errors after switching to `create_tables=False`.** The Step 2 script didn't run against the same database (or used different table names). On SQLite, skipping Step 2 gave `no such table: agent_sessions` on the first `add_items()`.
- **The same `CompileError` after Step 2.** Something still has `create_tables=True` on a MySQL URL, often a second place that builds the session (a worker, a test fixture). Search your code for `create_tables=True`.
- **An import error for `aiomysql`.** The `sqlalchemy` extra doesn't install `aiomysql`; per the [docs](https://openai.github.io/openai-agents-python/sessions/sqlalchemy_session/), install `'aiomysql[rsa]'` alongside it.
- **You already created the tables by hand with `VARCHAR(255)`.** That's fine for `create_tables=False`; the SDK reads and writes by column name. Just make sure `message_data` is big enough for your largest items (see `LONGTEXT` above).
- **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](https://memorysync.hashnode.dev/openai-agents-redissession-redis-cluster-error).

## 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](https://memorysync.hashnode.dev/openai-agents-sdk-no-tool-call-found-for-function-call-output)
- [Fix "'ClusterPipeline' object has no attribute 'raise_first_error'" with OpenAI Agents SDK RedisSession on Redis Cluster](https://memorysync.hashnode.dev/openai-agents-redissession-redis-cluster-error)
- [LangGraph AsyncPostgresSaver: fixing 'psycopg.OperationalError: the connection is closed' in FastAPI](https://memorysync.hashnode.dev/langgraph-asyncpostgressaver-connection-is-closed-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_bin` design, 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; `LONGTEXT` reasoning 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 the `main` branch (read 2026-10-31, re-read 2026-11-14)
- OpenAI Agents SDK docs: SQLAlchemy sessions, Sessions overview, `SQLAlchemySession` API 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](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_openai-agents-sdk-sqlalchemysession-mysql-varchar-requires-a-length).

**Next step:** read the [MemorySync OpenAI Agents SDK guide](https://docs.memorysync.io/guides/openai-agents?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_openai-agents-sdk-sqlalchemysession-mysql-varchar-requires-a-length), then [see MemorySync](https://memorysync.io/?utm_source=hashnode&utm_medium=organic_page&utm_campaign=v5_oct25&utm_content=pseo_openai-agents-sdk-sqlalchemysession-mysql-varchar-requires-a-length). Founding offer: use code FOUNDING30 for 30% off Starter for your first 3 months (Settings → Billing).

