open-webui / open-webui/open-webui
bug: Importing open_webui.config deletes every tracked file in backend/open_webui/static/ when the frontend is not built
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 153k
- Forks
- 22.3k
- Avg merge
- 1d 4h
- Merged PRs (30d)
- 194
Description
Bug
backend/open_webui/config.py clears STATIC_DIR at import time before checking whether there is a frontend build to replace it with. In a source checkout there is no build/static/, so the copy loop that follows finds nothing and the files are simply gone.
The result is that importing open_webui.config deletes 17 of the 32 files this repository tracks under backend/open_webui/static/ — including BRANDING.md, README.md, custom.css, every favicon, logo.png, splash.png, site.webmanifest, loader.js, user.png and user-import.csv. iterdir() is not recursive, so assets/, fonts/ and swagger-ui/ survive, which makes the loss look partial and easy to misread.
It fails silently. Both loops swallow every exception with pass, nothing is logged, and the import succeeds. The only symptom is a dirty git status.
Steps to reproduce
From a clean checkout of main, with backend dependencies installed and without running npm run build:
git status --porcelain backend/open_webui/static/ # clean
cd backend
WEBUI_SECRET_KEY=test DATA_DIR=/tmp/owui-data \
python -c "import open_webui.config"
cd ..
git status --porcelain backend/open_webui/static/ # 17 deletions
Anything that imports the module is enough — pytest collecting the backend test suite, a linter that imports, an editor language server, python -c "import open_webui.main".
Expected: importing a config module does not delete tracked files from the working tree.
Actual: 17 tracked files are deleted, silently.
Cause
backend/open_webui/config.py (lines 99-110 on main at 0.11.3):
try:
if STATIC_DIR.exists():
for item in STATIC_DIR.iterdir():
if item.is_file() or item.is_symlink():
try:
item.unlink()
except Exception as e:
pass # silent
except Exception as e:
pass # silent
for file_path in (FRONTEND_BUILD_DIR / 'static').glob('**/*'): # nothing to copy
...
The wipe is unconditional; only the copy is conditional on the build existing. Introduced in 4cee7c29a5.
The intent is clear and reasonable — replace stale copied assets so a rebuilt frontend's static/ wins — but the order means the "replace" half can be skipped while the "delete" half always runs.
Suggested fix
Make the wipe conditional on the same thing the copy is conditional on, so delete and replace succeed or are skipped together:
if (FRONTEND_BUILD_DIR / 'static').is_dir():
try:
if STATIC_DIR.exists():
for item in STATIC_DIR.iterdir():
...
A container build is unaffected: build/static/ exists, the wipe runs, the fresh assets replace the old ones. A source checkout keeps its files.
Two smaller points, if you want to harden it further:
- The bare
except Exception: passon both loops is what made this invisible for so long. Even alogging.debugon the unlink failure path would have surfaced it. - Deleting files from the package directory at import time is surprising in itself; moving this into an explicit startup step (or
main.py's lifespan) would keep a plainimport open_webui.configside-effect-free for tooling.
Environment
- Open WebUI: 0.11.3 (
main) - Python 3.11, Linux
- Reproduced in a source checkout with no frontend build present
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in backend/open_webui/config.py around lines 99-110 and inspect the cleanup and frontend static-copy paths. Run the provided import reproduction from a clean checkout, then verify that importing without build/static preserves backend/open_webui/static/ files while a container build with build/static/ still replaces stale assets. Confirm the result with git status.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 88/100