Skip to content

[4.0.0-beta desktop] Standalone app prints port 8888 but Flask binds 8080 #249

Closed

Description

@Fujio-Turner

PROBLEM

Double-clicking the .app / .exe (no PORT env var) produces two different ports:

Code path Port
app_base.py: PORT = int(os.environ.get('PORT', 8888)) 8888 at import
Startup banner in app.py: Open http://localhost:{PORT} prints 8888
app.py get_server_port(default=8080) + config.json server.port binds 8080
__main__ webbrowser.open(...) 8080
GitHub Release notes 8080
README "local Python" 8888
README "Docker" 5000 (compose actually maps 8080)

Symptom: console says open :8888 (connection refused). The auto-opened browser may hit :8080. Users think the app is broken.

Existing unit test encodes the bug:

tests/python/test_app_flask.py::TestServerConfiguration.test_port_default_is_8888

Suggested RCA

Port was never unified after the CBL migration.

  • app_base.py kept the old AirPlay-avoidance default of 8888 (comment even says "5000 is used by AirPlay").
  • app.py / config.default.json / Docker / compose moved to 8080.
  • The import-time banner uses app_base.PORT before get_server_port() runs.
  • _startup_banner / print happen at import (gunicorn + desktop), not at bind time.

Suggested FIX

One constant, one resolver, print the port you actually bind.

# app.py  (or a tiny app/ports.py so app_base can import it without a cycle)
DEFAULT_PORT = 8080

def get_server_port(default: int = DEFAULT_PORT) -> int:
    env = os.environ.get("PORT")
    if env:
        try:
            return int(env)
        except ValueError:
            logger.warning("ignoring invalid PORT env var=%s", env)
    cfg = _load_server_config().get("server") or {}
    val = cfg.get("port")
    if isinstance(val, int) and 0 < val < 65536:
        return val
    if isinstance(val, str) and val.isdigit():
        return int(val)
    return default
# app_base.py — stop inventing a second default
from app import get_server_port, DEFAULT_PORT  # or move helper to ports.py
PORT = get_server_port()

Better (avoids import cycle): put DEFAULT_PORT + get_server_port in app/ports.py. Both app.py and app_base.py import from there.

Print after resolution:

PORT = get_server_port()
print(f"🌐 Open http://localhost:{PORT} in your browser")

Update README / AGENT.md / packaging docs / Release body: Docker and desktop are 8080. Delete the "Docker uses 5000" sentence.

Unit tests

Replace the change-detector that asserts 8888.

# tests/python/test_ports.py
import os
from unittest.mock import patch
from ports import DEFAULT_PORT, get_server_port

def test_default_port_is_8080():
    assert DEFAULT_PORT == 8080

def test_env_port_wins(monkeypatch):
    monkeypatch.setenv("PORT", "5555")
    assert get_server_port() == 5555

def test_invalid_env_port_falls_back(monkeypatch):
    monkeypatch.setenv("PORT", "not-a-port")
    with patch("ports._load_server_config", return_value={}):
        assert get_server_port() == DEFAULT_PORT

def test_config_json_port_used_when_no_env(monkeypatch):
    monkeypatch.delenv("PORT", raising=False)
    with patch("ports._load_server_config", return_value={"server": {"port": 9090}}):
        assert get_server_port() == 9090

def test_banner_port_matches_bind_port(capsys, monkeypatch):
    monkeypatch.setenv("PORT", "8080")
    import app
    # After fix, the printed URL must contain the resolved port, not 8888.
    # Assert via a small _startup_banner(port) helper:
    app._startup_banner(8080)
    out = capsys.readouterr().out
    assert "localhost:8080" in out
    assert "localhost:8888" not in out

Delete or invert test_port_default_is_8888.

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

  1. Fujio-Turner commented on Sep 15, 2026

    @Fujio-Turner
    OwnerAuthor

    Fixed on release-4.0.0-beta via #263

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions