diff --git a/integration/harness.py b/integration/harness.py
index 145ba25..b3fa043 100644
--- a/integration/harness.py
+++ b/integration/harness.py
@@ -44,6 +44,8 @@ def running_server(
         stack.enter_context(patch.object(settings, "PROJECTROOT", str(projects)))
         stack.enter_context(patch.object(settings, "PROJECTS_LIST", str(projects)))
         stack.enter_context(patch("pygitweb.smart_http.smart_http_log_path", return_value=log_path))
+        # TODO: sessions/settings/auth_config are process-global; move them onto
+        # create_app()/app.state so an in-process server cannot affect other tests.
         if auth_config_file is not None:
             stack.enter_context(patch.object(settings, "AUTH_CONFIG", str(auth_config_file)))
         if auth:
@@ -68,4 +70,9 @@ def running_server(
             yield base
         finally:
             server.should_exit = True
-            thread.join(timeout=5.0)
+            thread.join(timeout=30.0)
+            if thread.is_alive():
+                raise RuntimeError("integration server did not stop")
+            # begin_shutdown() sets this on the shared app; undo so later TestClients
+            # are not stuck in shutting_down until the next autouse reset.
+            app.state.shutting_down = False
diff --git a/integration/local_auth.json b/integration/local_auth.json
new file mode 100644
index 0000000..51c3682
--- /dev/null
+++ b/integration/local_auth.json
@@ -0,0 +1,23 @@
+{
+  "salt": "0123456789abcdef",
+  "auth_mode": "local",
+  "oauth_permissions": {
+    "*": [],
+    "pgw.read.*": ["*"],
+    "pgw.write.*": ["*"],
+    "pgw.mr.create.*": ["*"],
+    "pgw.mr.merge.*": ["*"]
+  },
+  "local_users": [
+    {
+      "user": "alice",
+      "pass": "secretsecret",
+      "email": "alice@example.com"
+    },
+    {
+      "user": "bob",
+      "pass": "secretsecret",
+      "email": "bob@example.com"
+    }
+  ]
+}
diff --git a/integration/local_auth_push_test.py b/integration/local_auth_push_test.py
index 2bec619..ec43d75 100644
--- a/integration/local_auth_push_test.py
+++ b/integration/local_auth_push_test.py
@@ -1,10 +1,12 @@
-"""Local account → browser-issued HTTP key → git push to an empty project."""
+"""Local accounts → browser-issued HTTP keys → git push → merge request."""
 
 from __future__ import annotations
 
-import json
 import os
+import shutil
 import subprocess
+from collections.abc import Iterator
+from dataclasses import dataclass
 from pathlib import Path
 
 import pytest
@@ -13,38 +15,32 @@ from integration.harness import running_server
 
 sync_api = pytest.importorskip("playwright.sync_api")
 
-_USERNAME = "alice"
-_PASSWORD = "secretsecret"
+_AUTH_CONFIG_SRC = Path(__file__).with_name("local_auth.json")
+_ALICE = "alice"
+_ALICE_PASSWORD = "secretsecret"
+_ALICE_EMAIL = "alice@example.com"
+_BOB = "bob"
+_BOB_PASSWORD = "secretsecret"
+_BOB_EMAIL = "bob@example.com"
 _KEY_ID = "laptop"
 _PROJECT = "emptyproj"
+_FEATURE = "feature"
 
 
-def _write_local_auth_config(path: Path) -> None:
-    path.write_text(
-        json.dumps(
-            {
-                "salt": "0123456789abcdef",
-                "auth_mode": "local",
-                "oauth_permissions": {
-                    "*": [],
-                    "pgw.read.*": ["*"],
-                    "pgw.write.*": ["*"],
-                },
-                "local_users": [
-                    {
-                        "user": _USERNAME,
-                        "pass": _PASSWORD,
-                        "email": "alice@example.com",
-                    }
-                ],
-            },
-            indent=2,
-        )
-        + "\n",
-        encoding="utf-8",
-    )
+@dataclass
+class LocalAuthHarness:
+    base: str
+    tmp_path: Path
+    bare: Path
+    alice_secret: str = ""
+    bob_secret: str = ""
+    feature_tip: str = ""
+
+
+def _install_auth_config(dest: Path) -> None:
+    shutil.copyfile(_AUTH_CONFIG_SRC, dest)
     if os.name != "nt":
-        path.chmod(0o600)
+        dest.chmod(0o600)
 
 
 def _run_git(repo_dir: Path, *args: str, git_config: tuple[str, ...] = ()) -> str:
@@ -64,36 +60,34 @@ def _key_git_config(username: str, secret: str) -> tuple[str, ...]:
     return ("-c", "credential.helper=", "-c", f"credential.helper={helper}")
 
 
-def _issue_key_via_browser(base: str) -> str:
-    with sync_api.sync_playwright() as playwright:
-        browser = playwright.chromium.launch(headless=True)
-        try:
-            page = browser.new_page()
-            page.goto(f"{base}login?next=/user", wait_until="domcontentloaded")
-            page.fill("#login_username", _USERNAME)
-            page.fill("#login_password", _PASSWORD)
-            page.get_by_role("button", name="Sign in").click()
-            page.wait_for_url("**/user")
-            sync_api.expect(page.get_by_role("heading", name="Account")).to_be_visible()
-            page.fill("#api-key-id", _KEY_ID)
-            page.get_by_role("button", name="Create key").click()
-            page.locator("#api-key-secret-panel").wait_for(state="visible")
-            secret = page.locator("#api-key-secret").input_value()
-            assert secret, "expected a plaintext API key after Create key"
-            return secret
-        finally:
-            browser.close()
+def _login(page: sync_api.Page, base: str, username: str, password: str) -> None:
+    page.goto(f"{base}login?next=/user", wait_until="domcontentloaded")
+    page.fill("#login_username", username)
+    page.fill("#login_password", password)
+    page.get_by_role("button", name="Sign in").click()
+    page.wait_for_url("**/user")
+    sync_api.expect(page.get_by_role("heading", name="Account")).to_be_visible()
 
 
-@pytest.mark.playwright
-def test_local_account_issues_key_and_pushes(tmp_path: Path) -> None:
+def _issue_key_on_page(page: sync_api.Page, key_id: str) -> str:
+    page.fill("#api-key-id", key_id)
+    page.get_by_role("button", name="Create key").click()
+    page.locator("#api-key-secret-panel").wait_for(state="visible")
+    secret = page.locator("#api-key-secret").input_value()
+    assert secret, "expected a plaintext API key after Create key"
+    return secret
+
+
+@pytest.fixture(scope="module")
+def harness(tmp_path_factory: pytest.TempPathFactory) -> Iterator[LocalAuthHarness]:
+    tmp_path = tmp_path_factory.mktemp("local_auth_push")
     projects = tmp_path / "projects"
     projects.mkdir()
     bare = projects / _PROJECT
     subprocess.run(["git", "init", "--bare", "-b", "main", str(bare)], check=True, capture_output=True)
 
     auth_path = tmp_path / "auth.json"
-    _write_local_auth_config(auth_path)
+    _install_auth_config(auth_path)
 
     with running_server(
         tmp_path=tmp_path,
@@ -101,22 +95,97 @@ def test_local_account_issues_key_and_pushes(tmp_path: Path) -> None:
         auth=True,
         auth_config_file=auth_path,
     ) as base:
-        secret = _issue_key_via_browser(base)
-
-        work = tmp_path / "work"
-        work.mkdir()
-        _run_git(work, "init", "-b", "main")
-        _run_git(work, "config", "user.name", "tester")
-        _run_git(work, "config", "user.email", "tester@example.com")
-        (work / "README.md").write_text("from key\n", encoding="utf-8")
-        _run_git(work, "add", "README.md")
-        _run_git(work, "commit", "-m", "initial via http key")
-        remote = f"{base}http/{_PROJECT}.git"
-        _run_git(work, "remote", "add", "origin", remote)
-        git_config = _key_git_config(_USERNAME, secret)
-        _run_git(work, "push", "-u", "origin", "main", git_config=git_config)
-
-        head = _run_git(bare, "rev-parse", "refs/heads/main")
-        assert head
-        subject = _run_git(bare, "log", "-1", "--format=%s", "refs/heads/main")
-        assert subject == "initial via http key"
+        yield LocalAuthHarness(base=base, tmp_path=tmp_path, bare=bare)
+
+
+@pytest.mark.playwright
+def test_auth_setup(harness: LocalAuthHarness) -> None:
+    with sync_api.sync_playwright() as playwright:
+        browser = playwright.chromium.launch(headless=True)
+        try:
+            page = browser.new_page()
+            _login(page, harness.base, _ALICE, _ALICE_PASSWORD)
+            harness.alice_secret = _issue_key_on_page(page, _KEY_ID)
+
+            page.goto(f"{harness.base}logout?next=/", wait_until="domcontentloaded")
+            _login(page, harness.base, _BOB, _BOB_PASSWORD)
+            harness.bob_secret = _issue_key_on_page(page, _KEY_ID)
+        finally:
+            browser.close()
+
+    assert harness.alice_secret
+    assert harness.bob_secret
+    assert harness.alice_secret != harness.bob_secret
+
+
+@pytest.mark.playwright
+def test_push(harness: LocalAuthHarness) -> None:
+    assert harness.alice_secret and harness.bob_secret, "run test_auth_setup first"
+
+    remote = f"{harness.base}http/{_PROJECT}.git"
+    alice_cfg = _key_git_config(_ALICE, harness.alice_secret)
+    bob_cfg = _key_git_config(_BOB, harness.bob_secret)
+
+    alice_work = harness.tmp_path / "alice_work"
+    alice_work.mkdir()
+    _run_git(alice_work, "init", "-b", "main")
+    _run_git(alice_work, "config", "user.name", _ALICE)
+    _run_git(alice_work, "config", "user.email", _ALICE_EMAIL)
+    (alice_work / "README.md").write_text("from key\n", encoding="utf-8")
+    _run_git(alice_work, "add", "README.md")
+    _run_git(alice_work, "commit", "-m", "initial via http key")
+    _run_git(alice_work, "remote", "add", "origin", remote)
+    _run_git(alice_work, "push", "-u", "origin", "main", git_config=alice_cfg)
+
+    head = _run_git(harness.bare, "rev-parse", "refs/heads/main")
+    assert head
+    subject = _run_git(harness.bare, "log", "-1", "--format=%s", "refs/heads/main")
+    assert subject == "initial via http key"
+
+    bob_work = harness.tmp_path / "bob_work"
+    _run_git(harness.tmp_path, "clone", remote, "bob_work", git_config=bob_cfg)
+    _run_git(bob_work, "config", "user.name", _BOB)
+    _run_git(bob_work, "config", "user.email", _BOB_EMAIL)
+    _run_git(bob_work, "checkout", "-b", _FEATURE)
+    (bob_work / "FEATURE.md").write_text("bob feature\n", encoding="utf-8")
+    _run_git(bob_work, "add", "FEATURE.md")
+    _run_git(bob_work, "commit", "-m", "feature via http key")
+    _run_git(bob_work, "push", "-u", "origin", _FEATURE, git_config=bob_cfg)
+
+    harness.feature_tip = _run_git(harness.bare, "rev-parse", f"refs/heads/{_FEATURE}")
+    assert harness.feature_tip
+    feature_subject = _run_git(harness.bare, "log", "-1", "--format=%s", f"refs/heads/{_FEATURE}")
+    assert feature_subject == "feature via http key"
+
+
+@pytest.mark.playwright
+def test_mr_flow(harness: LocalAuthHarness) -> None:
+    assert harness.feature_tip, "run test_push first"
+
+    with sync_api.sync_playwright() as playwright:
+        browser = playwright.chromium.launch(headless=True)
+        try:
+            page = browser.new_page()
+            _login(page, harness.base, _BOB, _BOB_PASSWORD)
+            page.goto(f"{harness.base}project/{_PROJECT}", wait_until="domcontentloaded")
+            page.locator("#summary-mr-theirs").select_option(f"refs/heads/{_FEATURE}")
+            page.locator("#summary-mr-ours").select_option("refs/heads/main")
+            page.get_by_role("button", name="Open merge request").click()
+            sync_api.expect(page.get_by_role("heading", name="Merge request")).to_be_visible()
+            sync_api.expect(page.get_by_role("heading", name=f"Merge {_FEATURE} into main")).to_be_visible()
+
+            page.goto(f"{harness.base}logout?next=/", wait_until="domcontentloaded")
+            _login(page, harness.base, _ALICE, _ALICE_PASSWORD)
+            page.goto(f"{harness.base}project/{_PROJECT}", wait_until="domcontentloaded")
+            page.get_by_role("link", name=f"Merge {_FEATURE} into main").click()
+            sync_api.expect(page.get_by_role("heading", name="Merge request")).to_be_visible()
+            page.get_by_role("button", name="Merge (fast-forward only)").click()
+            sync_api.expect(page.get_by_role("heading", name="Merge request")).to_be_visible()
+            sync_api.expect(page.get_by_text("Already up to date")).to_be_visible()
+        finally:
+            browser.close()
+
+    main_tip = _run_git(harness.bare, "rev-parse", "refs/heads/main")
+    assert main_tip == harness.feature_tip
+    subject = _run_git(harness.bare, "log", "-1", "--format=%s", "refs/heads/main")
+    assert subject == "feature via http key"
diff --git a/pygitweb/src/pygitweb/main.py b/pygitweb/src/pygitweb/main.py
index f7e5a34..b11071a 100644
--- a/pygitweb/src/pygitweb/main.py
+++ b/pygitweb/src/pygitweb/main.py
@@ -112,7 +112,6 @@ from pygitweb.plugin_loader import (
     apply_plugin_reload,
 )
 from pygitweb.projects import git_get_project_owner, git_get_projects_list
-from pygitweb.sessions import clear_all_sessions
 from pygitweb.settings import router as settings_router
 from pygitweb.shutdown import begin_shutdown, install_graceful_shutdown_wakeup
 from pygitweb.smart_http import http_router
@@ -174,7 +173,6 @@ async def app_lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
     finally:
         begin_shutdown(app)
         clear_timeline_cache_async()
-        clear_all_sessions()
 
 
 _app_description = Path(__file__).parent.parent.parent.joinpath("README.md").read_text(encoding="utf-8")
