Stario does not ship a watcher; watchfiles does the job and needs no configuration beyond the command:
uv run watchfiles 'python -m app.main' app/Add it once with uv add --dev watchfiles — a scaffolded app already has it, and uv run --with watchfiles watchfiles ... works without adding anything.
The first argument is the command to run, the second is the directory to watch. Run the app as watchfiles' direct child — python, not uv run python. watchfiles kills only its child; a uv run wrapper dies and leaves an orphaned server holding the port and any litewriter claim, so every later restart stalls. It is a restart, not an in-process swap of modules. Every reload is a fresh interpreter, a clean import, and a fresh read of the STARIO_* variables, so what you see after a save is exactly what python -m app.main would start. A one-file app runs uv run watchfiles 'python main.py' . the same way.
What it watches#
The directories you pass, recursively. List several roots as extra arguments, and exclude paths with a comma-separated --ignore-paths list:
uv run watchfiles 'python -m app.main' app/ static/ --ignore-paths data,static/generatedIgnoring the data directory keeps SQLite files from restarting the app every time it writes to them — a running app would otherwise restart itself. To limit restarts to Python files only, add --filter python.
When the import fails#
If the new code does not import, the process exits and watchfiles waits for the next change. Fix the file and save again; the restart shows the traceback or the page.
Live pages across a restart#
A restart closes every connection, including the open stream a live page holds. Datastar reconnects when the stream was opened with retry="always", the new process sends a fresh render, and the page catches up without a refresh. Nothing in your code needs to know a reload happened. Realtime tiles has the one line that makes this work: data.init(at.get(SUBSCRIBE.href(), retry="always")).
By the way#
The port and every other server setting are environment variables, not flags:
STARIO_PORT=8001 uv run watchfiles 'python -m app.main' app/. Configuration lists them.A busy port or a
WriterBusyfrom litewriter after a reload means an old process still runs — usually an orphan from auv runwrapper (see above). Find the holder withlsofon the port or the*.sqlite3-claimfile and kill it.Stario does not read
.envfiles. Use your shell,direnv, or your process manager.The watcher is for development. Production runs the module directly; see Deployment.
Want to watch signals live?
debug_inspector()fromstario.debugis a draggable overlay — opt in by adding it to the page body in development.