Repository navigation
[4.0.0-beta desktop] Frozen app loads config.json via __file__ instead of _MEIPASS #256
Description
PROBLEM
Static assets go through app_base.get_resource_path(), which already handles PyInstaller:
def get_resource_path():
if getattr(sys, "frozen", False):
return sys._MEIPASS
return os.path.dirname(os.path.abspath(__file__))Server config does not:
# app.py _load_server_config()
here = os.path.dirname(os.path.abspath(__file__))
candidates.extend([
os.path.join(here, "config.json"),
os.path.join(here, "config.default.json"),
])On Windows --onefile, __file__ for app.py is not reliably the same directory as the extracted datas (config.json is added as ('.',) into _MEIPASS). Result: server.port from config is ignored (falls back to the 8080 default — currently masks the bug) and any future keys in config.json never load in the packaged app.
config.json is also in .gitignore but tracked on this branch, so CI happens to copy it. A clean clone that respects gitignore would make PyInstaller fail the datas entry ('config.json', '.') because the file is absent — another landmine.
Suggested RCA
Resource-path handling was added for index.html / assets in app_base.py and never reused for the later _load_server_config() helper. CI --add-data "config.json;." assumes a file that gitignore says should not be in the repo (config.default.json is the one that should be bundled).
Suggested FIX
# app.py
def _load_server_config() -> dict:
candidates = []
env_path = os.environ.get("APP_CONFIG_FILE")
if env_path:
candidates.append(env_path)
here = get_resource_path() # same helper as static files
candidates.extend([
os.path.join(here, "config.json"), # user override, optional
os.path.join(here, "config.default.json"), # shipped default
])
...Import get_resource_path from app_base (already imported as DIRECTORY).
PyInstaller datas: ship config.default.json, not config.json.
datas += [('config.default.json', '.')]Unit tests
# tests/python/test_server_config_path.py
import os
import json
from unittest.mock import patch
def test_load_server_config_uses_meipass_when_frozen(tmp_path, monkeypatch):
cfg = tmp_path / "config.default.json"
cfg.write_text(json.dumps({"server": {"port": 9090}}))
import app
monkeypatch.setattr(app.sys, "frozen", True, raising=False)
monkeypatch.setattr(app.sys, "_MEIPASS", str(tmp_path), raising=False)
monkeypatch.delenv("PORT", raising=False)
monkeypatch.delenv("APP_CONFIG_FILE", raising=False)
with patch("app.get_resource_path", return_value=str(tmp_path)):
data = app._load_server_config()
assert data["server"]["port"] == 9090
def test_spec_bundles_default_config_not_user_config():
from pathlib import Path
mac = Path("app/build_mac.spec").read_text()
assert "config.default.json" in mac
# user config.json must not be required at build time
assert "('config.json'" not in macContext: found on branch release-4.0.0-beta while reviewing standalone macOS .app / Windows .exe packaging. Issues in this batch share the title prefix [4.0.0-beta desktop].
Activity
Fujio-Turner commentedon Sep 15, 2026
Fixed on
release-4.0.0-betavia #263