diff --git a/docs/adr/033-async-seeder-stub.md b/docs/adr/033-async-seeder-stub.md
new file mode 100644
index 00000000..7dc9efa5
--- /dev/null
+++ b/docs/adr/033-async-seeder-stub.md
@@ -0,0 +1,26 @@
+# 033. Generated seeders have an async run
+
+Date: 2026-10-21
+
+## Problem
+
+The `seed` command writes table seeders from `create_seed.stub`. The stub has a synchronous `def run(self)` and docstrings. `Seeder.call`, `run_database_seed` and `run_specific_seed` all `await ...run()`, so every generated seeder fails when it is seeded:
+
+```
+TypeError: 'NoneType' object can't be awaited
+```
+
+## Alternatives
+
+1. Make `Seeder` accept both forms by awaiting the result only when it is awaitable. This would keep blocking seeders working, but seeders write through the async ORM, so a synchronous `run` cannot await queries anyway.
+2. Make the stub match the async seeder API.
+
+## Decision
+
+Option 2. The stub generates `async def run(self) -> None` with no docstrings. The import, `from fastapi_startkit.masoniteorm.seeders import Seeder`, is already correct and stays. `Seeder` is unchanged.
+
+## Validation
+
+- A new test generates `PostTableSeeder` with `MakeSeedCommand`, checks the import, the `async def run` and that there are no docstrings, and imports it through `Seeder.run_specific_seed`, which awaits `run()`.
+- The test fails on the old stub.
+- Running the old stub's output through `run_specific_seed` raises the `TypeError` above.
diff --git a/docs/adr/AGENTS.md b/docs/adr/AGENTS.md
index b65dfe83..737ad4e5 100644
--- a/docs/adr/AGENTS.md
+++ b/docs/adr/AGENTS.md
@@ -33,3 +33,4 @@ Read this index first, then open the records relevant to the change. Before impl
| [025](025-model-lifecycle-events.md) | 2026-10-20 | Model lifecycle events, automatic timestamps and datetime casts | `save()` fires `saving → creating/updating → query → created/updated → saved`. Observer changes are persisted, and `updating` fires only when the model is dirty. A new `Model.delete()` fires `deleting → DELETE → deleted`. `observe_events` is async and awaits async handlers. Mass query-builder writes fire no events. Laravel-style auto timestamps: `created_at`/`updated_at` are filled on create unless supplied, `updated_at` moves on update, and `__timestamps__ = False` disables both. `DateCast.set` returns a UTC datetime; `Connection.prepare_binding` passes it unchanged to Postgres/MySQL, and SQLite converts it to a naive UTC string. Postgres registers an asyncpg `timestamp` codec that stores aware values as naive UTC; `timestamptz` keeps asyncpg's codec. `Annotated[T, Cast]` (a `BaseCast` subclass or instance) overrides a column's cast, and `Attribute.__casts__` is removed. |
| [026](026-schema-column-change.md) | 2026-10-20 | Altering existing columns with `.change()` | `.change()` fully redefines the column, the way Laravel does. MySQL (`MODIFY`) and SQLite (table rebuild) were already correct. Postgres now emits `TYPE ... USING` casts, correct lengths, quoted defaults or `DROP DEFAULT`, `SET`/`DROP NOT NULL`, and replaces the enum check constraint. MSSQL drops the current default constraint (looked up in `sys.default_constraints`), runs one `ALTER COLUMN` per column, then adds `[DF_
_]`. Identifiers are bracket- or double-quote-escaped and the MSSQL drop target is built server-side with `QUOTENAME`; changing a Postgres serial column drops its `nextval` default. |
| [032](032-migrate-seed-option.md) | 2026-10-21 | db:migrate:fresh and db:migrate:refresh --seed run seeders in the same event loop | `--seed` called the unregistered `seed:run` through Cleo `call` (which also drops the first argument) inside a running event loop, where `db:seed` would call `asyncio.run` again. The seeder selection moves into async `DBSeedCommand.run_seeders`, which `db:seed`, `db:migrate:fresh` and `db:migrate:refresh` all await. `db:migrate:refresh` is now registered. |
+| [033](033-async-seeder-stub.md) | 2026-10-21 | Generated seeders have an async run | `create_seed.stub` generated a synchronous `run` with docstrings, but `Seeder` awaits `run()`, so every generated table seeder raised `TypeError`. The stub now generates `async def run(self) -> None` with no docstrings; `Seeder` is unchanged. |
diff --git a/fastapi_startkit/src/fastapi_startkit/masoniteorm/commands/stubs/create_seed.stub b/fastapi_startkit/src/fastapi_startkit/masoniteorm/commands/stubs/create_seed.stub
index 9ce9b583..cb993761 100644
--- a/fastapi_startkit/src/fastapi_startkit/masoniteorm/commands/stubs/create_seed.stub
+++ b/fastapi_startkit/src/fastapi_startkit/masoniteorm/commands/stubs/create_seed.stub
@@ -1,9 +1,6 @@
-"""__SEEDER_NAME__ Seeder."""
-
from fastapi_startkit.masoniteorm.seeders import Seeder
class __SEEDER_NAME__(Seeder):
- def run(self):
- """Run the database seeds."""
+ async def run(self) -> None:
pass
diff --git a/fastapi_startkit/tests/masoniteorm/commands/test_make_commands.py b/fastapi_startkit/tests/masoniteorm/commands/test_make_commands.py
index 5ad20843..d479afa8 100644
--- a/fastapi_startkit/tests/masoniteorm/commands/test_make_commands.py
+++ b/fastapi_startkit/tests/masoniteorm/commands/test_make_commands.py
@@ -2,6 +2,7 @@
import importlib.util
import os
import shutil
+import sys
import tempfile
import unittest
@@ -12,6 +13,7 @@
from fastapi_startkit.masoniteorm.commands.MakeObserverCommand import MakeObserverCommand
from fastapi_startkit.masoniteorm.commands.MakeSeedCommand import MakeSeedCommand
from fastapi_startkit.masoniteorm.migrations.Migration import Migration
+from fastapi_startkit.masoniteorm.seeders import Seeder
from .fixtures.app import create_app, DB_PATH
@@ -236,6 +238,29 @@ def test_reports_when_seed_already_exists(self):
output = tester.io.fetch_output()
self.assertIn("already exists.", output)
+ def test_generated_seeder_runs_through_seeding_path(self):
+ os.makedirs("generated_seeds/seeders")
+ self._tester(MakeSeedCommand).execute("Post --directory generated_seeds/seeders")
+
+ content = open(os.path.join("generated_seeds/seeders", "post_table_seeder.py")).read()
+ self.assertIn("from fastapi_startkit.masoniteorm.seeders import Seeder", content)
+ self.assertIn("async def run(self) -> None:", content)
+ self.assertNotIn('"""', content)
+
+ sys.path.insert(0, self._tmp_dir)
+ self.addCleanup(sys.path.remove, self._tmp_dir)
+ self.addCleanup(self._forget_modules, "generated_seeds")
+
+ seeder = Seeder(seed_path="generated_seeds/seeders")
+ asyncio.run(seeder.run_specific_seed("post_table_seeder.PostTableSeeder"))
+
+ self.assertEqual([seed.__name__ for seed in seeder.ran_seeds], ["PostTableSeeder"])
+
+ @staticmethod
+ def _forget_modules(package):
+ for name in [name for name in sys.modules if name == package or name.startswith(f"{package}.")]:
+ del sys.modules[name]
+
class TestMakeObserverCommand(_TempCwdTestCase):
# make:observer emits a plain observer class with no schema side effects, so