diff --git a/pygitweb/static/main.css b/pygitweb/static/main.css
index 13ded8f..0615669 100644
--- a/pygitweb/static/main.css
+++ b/pygitweb/static/main.css
@@ -1090,3 +1090,342 @@ pre {
 	border: 1px solid var(--pgw-border);
 	background-color: var(--pgw-bg-raised);
 }
+
+/*
+ * pytest-html report embedded in a project subpage (pygitweb_pytesthtml).
+ * Replaces pytest-html's bundled stylesheet; uses --pgw-* with Tabler fallbacks.
+ */
+.pytest-html-report {
+	color: var(--pgw-text);
+	font-size: 0.875rem;
+	line-height: 1.5;
+	min-width: 0;
+
+	& > p {
+		color: var(--pgw-text-muted);
+		margin: 0 0 1rem;
+	}
+
+	& a {
+		color: var(--pgw-link);
+
+		&:visited {
+			color: var(--pgw-link-visited);
+		}
+
+		&:hover {
+			color: var(--pgw-link-hover);
+		}
+	}
+
+	& h2 {
+		color: var(--pgw-text);
+		font-size: 1.125rem;
+		font-weight: 600;
+		margin: 1.25rem 0 0.5rem;
+	}
+
+	&__title {
+		font-size: 1.25rem;
+		margin-top: 0;
+	}
+
+	#environment-header h2 {
+		cursor: pointer;
+	}
+
+	#environment-header.collapsed + #environment {
+		display: none;
+	}
+
+	.summary {
+		margin-bottom: 1rem;
+	}
+
+	.run-count,
+	.filter {
+		color: var(--pgw-text);
+		margin: 0.25rem 0;
+	}
+
+	.summary__reload__button {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		padding: 0.75rem 1rem;
+		border-radius: var(--tblr-border-radius, 0.25rem);
+		background-color: var(--tblr-green, #2fb344);
+		color: var(--tblr-green-fg, #fff);
+		cursor: pointer;
+		text-align: center;
+
+		&:hover {
+			filter: brightness(1.05);
+		}
+	}
+
+	.summary__spacer {
+		display: none;
+	}
+
+	.controls {
+		display: flex;
+		flex-wrap: wrap;
+		gap: 0.75rem 1.5rem;
+		align-items: center;
+		justify-content: space-between;
+		margin-top: 0.75rem;
+	}
+
+	.filters,
+	.collapse {
+		display: flex;
+		flex-wrap: wrap;
+		align-items: center;
+		gap: 0.25rem 0.5rem;
+	}
+
+	.filters button,
+	.collapse button {
+		color: var(--pgw-link);
+		border: none;
+		background: none;
+		cursor: pointer;
+		text-decoration: underline;
+		padding: 0;
+
+		&:hover {
+			color: var(--pgw-link-hover);
+		}
+	}
+
+	.filters input.filter {
+		accent-color: var(--pgw-link);
+	}
+
+	span.passed,
+	.passed .col-result {
+		color: var(--pgw-add-fg, var(--tblr-green, #2fb344));
+	}
+
+	span.skipped,
+	span.xfailed,
+	span.rerun,
+	.skipped .col-result,
+	.xfailed .col-result,
+	.rerun .col-result {
+		color: var(--pgw-warn-fg, var(--tblr-yellow, #f59f00));
+	}
+
+	span.error,
+	span.failed,
+	span.xpassed,
+	.error .col-result,
+	.failed .col-result,
+	.xpassed .col-result,
+	.log span.error {
+		color: var(--pgw-rem-fg, var(--tblr-red, #d63939));
+	}
+
+	.col-links__extra {
+		margin-right: 0.25rem;
+	}
+
+	#results-table,
+	#environment {
+		margin: 0.75rem 0 1rem;
+		font-size: inherit;
+	}
+
+	.collapsible td:not(.col-links) {
+		cursor: pointer;
+	}
+
+	.collapsible td:not(.col-links):hover::after {
+		color: var(--pgw-text-dim);
+		font-style: italic;
+	}
+
+	.col-result {
+		width: 8rem;
+		white-space: nowrap;
+	}
+
+	.col-result:hover::after {
+		content: " (hide details)";
+	}
+
+	.col-result.collapsed:hover::after {
+		content: " (show details)";
+	}
+
+	#environment-header h2:hover::after {
+		content: " (hide details)";
+		color: var(--pgw-text-dim);
+		font-style: italic;
+		font-size: 0.75rem;
+		font-weight: 400;
+	}
+
+	#environment-header.collapsed h2:hover::after {
+		content: " (show details)";
+	}
+
+	.sortable {
+		cursor: pointer;
+		user-select: none;
+	}
+
+	.sortable.desc::after,
+	.sortable.asc::after {
+		content: "";
+		display: inline-block;
+		position: relative;
+		left: 0.35rem;
+		border: 0.4rem solid transparent;
+	}
+
+	.sortable.desc::after {
+		bottom: 0.15rem;
+		border-top-color: var(--tblr-green, var(--pgw-add-fg, #2fb344));
+		border-bottom: 0;
+	}
+
+	.sortable.asc::after {
+		bottom: -0.1rem;
+		border-bottom-color: var(--tblr-green, var(--pgw-add-fg, #2fb344));
+		border-top: 0;
+	}
+
+	.hidden,
+	.summary__reload__button.hidden {
+		display: none !important;
+	}
+
+	.logwrapper {
+		max-height: 14rem;
+		overflow-y: auto;
+		background-color: var(--pgw-code-bg);
+		border: 1px solid var(--pgw-code-border);
+		border-radius: var(--tblr-border-radius, 0.25rem);
+		margin-top: 0.5rem;
+
+		&.expanded {
+			max-height: none;
+		}
+
+		&.expanded .logexpander::after {
+			content: "collapse [-]";
+		}
+	}
+
+	.logwrapper .logexpander {
+		position: sticky;
+		top: 0.5rem;
+		z-index: 1;
+		width: max-content;
+		margin: 0.5rem 0 0.5rem auto;
+		padding: 0.25rem 0.5rem;
+		border: 1px solid var(--pgw-border);
+		border-radius: var(--tblr-border-radius, 0.25rem);
+		background-color: var(--pgw-bg-raised);
+		color: var(--pgw-text-muted);
+		cursor: pointer;
+		font-size: 0.75rem;
+
+		&::after {
+			content: "expand [+]";
+		}
+
+		&:hover {
+			color: var(--pgw-text);
+			border-color: var(--pgw-border-muted);
+		}
+	}
+
+	.logwrapper .log {
+		min-height: 2.5rem;
+		position: relative;
+		top: -2.25rem;
+		height: calc(100% + 2.25rem);
+		margin: 0;
+		padding: 0.5rem 4rem 0.5rem 0.5rem;
+		border: none;
+		background: transparent;
+		color: var(--pgw-text);
+		font-family: ui-monospace, "Fira Code", "Fira Mono", monospace;
+		font-size: 0.8125rem;
+		white-space: pre-wrap;
+		word-break: break-word;
+	}
+
+	div.media {
+		float: right;
+		width: 20rem;
+		max-width: 100%;
+		height: 15rem;
+		margin: 0 0 0.5rem 0.5rem;
+		overflow: hidden;
+		border: 1px solid var(--pgw-border);
+		border-radius: var(--tblr-border-radius, 0.25rem);
+		background-color: var(--pgw-bg-muted);
+	}
+
+	.media-container {
+		display: grid;
+		grid-template-columns: 1.5rem 1fr 1.5rem;
+		align-items: center;
+		height: 12.5rem;
+		overflow: hidden;
+	}
+
+	.media-container--fullscreen {
+		grid-template-columns: 0 1fr 0;
+	}
+
+	.media-container__nav--left,
+	.media-container__nav--right {
+		text-align: center;
+		cursor: pointer;
+		color: var(--pgw-text-muted);
+
+		&:hover {
+			color: var(--pgw-text);
+		}
+	}
+
+	.media-container__viewport {
+		cursor: pointer;
+		text-align: center;
+		height: inherit;
+
+		img,
+		video {
+			max-width: 100%;
+			max-height: 100%;
+			object-fit: contain;
+		}
+	}
+
+	.media__name,
+	.media__counter {
+		display: flex;
+		justify-content: space-around;
+		align-items: center;
+		padding: 0.25rem;
+		font-size: 0.75rem;
+		color: var(--pgw-text-muted);
+	}
+
+	.extras-row td.extra {
+		background-color: var(--pgw-bg-muted);
+	}
+
+	.extraHTML {
+		margin-bottom: 0.5rem;
+	}
+
+	template {
+		display: none;
+	}
+}
diff --git a/pygitweb_pytesthtml/pygitweb_pytesthtml/action_pytesthtml.py b/pygitweb_pytesthtml/pygitweb_pytesthtml/action_pytesthtml.py
index 2363401..72de865 100644
--- a/pygitweb_pytesthtml/pygitweb_pytesthtml/action_pytesthtml.py
+++ b/pygitweb_pytesthtml/pygitweb_pytesthtml/action_pytesthtml.py
@@ -1,14 +1,13 @@
 from __future__ import annotations
 
 import subprocess
-from pathlib import Path
 
 from fastapi import Request
 from starlette.responses import Response
 
 from pygitweb.api.action import Action
-from pygitweb.config import settings
 from pygitweb_pytesthtml.constants import PYTESTHTML_ACTION_NAME
+from pygitweb_pytesthtml.worktree import cleanup_pytest_run, prepare_pytest_run
 
 
 class ActionPytestHtml(Action):
@@ -20,9 +19,12 @@ class ActionPytestHtml(Action):
 		return PYTESTHTML_ACTION_NAME
 
 	def action(self, project: str, req: Request | None = None) -> Response | None:
-		cwd = Path(settings.PROJECTROOT) / project
-		subprocess.run(
-			["pytest", "--html=.pygitweb/pytest_report.html", "--self-contained-html"],
-			cwd=str(cwd),
-		)
+		cwd, report = prepare_pytest_run(project)
+		try:
+			subprocess.run(
+				["pytest", f"--html={report}", "--self-contained-html"],
+				cwd=str(cwd),
+			)
+		finally:
+			cleanup_pytest_run(project)
 		return None
diff --git a/pygitweb_pytesthtml/pygitweb_pytesthtml/report_embed.py b/pygitweb_pytesthtml/pygitweb_pytesthtml/report_embed.py
new file mode 100644
index 0000000..6014559
--- /dev/null
+++ b/pygitweb_pytesthtml/pygitweb_pytesthtml/report_embed.py
@@ -0,0 +1,65 @@
+from __future__ import annotations
+
+import re
+
+_REPORT_ROOT_CLASS = "pytest-html-report"
+_TITLE_H1 = re.compile(
+	r'<h1\s+id="title"([^>]*)>(.*?)</h1>',
+	re.IGNORECASE | re.DOTALL,
+)
+
+
+def _extract_body_inner(html: str) -> str:
+	lower = html.lower()
+	body_open = lower.find("<body")
+	if body_open == -1:
+		return html
+	content_start = html.find(">", body_open)
+	if content_start == -1:
+		return html
+	content_start += 1
+	body_close = lower.rfind("</body>")
+	if body_close == -1:
+		return html[content_start:].strip()
+	return html[content_start:body_close].strip()
+
+
+def _demote_title_h1(body: str) -> str:
+	return _TITLE_H1.sub(
+		rf'<h2 class="{_REPORT_ROOT_CLASS}__title"\1>\2</h2>',
+		body,
+		count=1,
+	)
+
+
+def _add_table_classes(body: str) -> str:
+	body = re.sub(
+		r'<table\s+id="environment"',
+		'<table id="environment" class="table table-vcenter card-table"',
+		body,
+		count=1,
+		flags=re.IGNORECASE,
+	)
+	return re.sub(
+		r'<table\s+id="results-table"',
+		'<table id="results-table" class="table table-vcenter card-table"',
+		body,
+		count=1,
+		flags=re.IGNORECASE,
+	)
+
+
+def embed_pytest_html_report(html: str) -> str:
+	"""Turn a pytest-html full document into a PyGitWeb fragment (no embedded report CSS)."""
+	stripped = html.strip()
+	if not stripped:
+		return stripped
+	lower = stripped.lower()
+	if not (lower.startswith("<!doctype") or lower.startswith("<html")):
+		if _REPORT_ROOT_CLASS in stripped:
+			return stripped
+		return f'<div class="{_REPORT_ROOT_CLASS}">{stripped}</div>'
+	body = _extract_body_inner(stripped)
+	body = _demote_title_h1(body)
+	body = _add_table_classes(body)
+	return f'<div class="{_REPORT_ROOT_CLASS}">\n{body}\n</div>'
diff --git a/pygitweb_pytesthtml/pygitweb_pytesthtml/report_embed_test.py b/pygitweb_pytesthtml/pygitweb_pytesthtml/report_embed_test.py
new file mode 100644
index 0000000..fe282c1
--- /dev/null
+++ b/pygitweb_pytesthtml/pygitweb_pytesthtml/report_embed_test.py
@@ -0,0 +1,77 @@
+from __future__ import annotations
+
+import subprocess
+import sys
+from pathlib import Path
+
+import pytest
+
+from pygitweb_pytesthtml.report_embed import embed_pytest_html_report
+
+_SAMPLE_DOC = """<!DOCTYPE html>
+<html>
+<head>
+<title id="head-title">report.html</title>
+<style type="text/css">body { color: red; }</style>
+</head>
+<body>
+<h1 id="title">report.html</h1>
+<p>Report generated</p>
+<table id="environment"></table>
+<table id="results-table">
+<thead id="results-table-head"><tr><th>Result</th></tr></thead>
+</table>
+<footer><div id="data-container" data-jsonblob="{}"></div><script>void 0;</script></footer>
+</body>
+</html>
+"""
+
+
+def test_embed_strips_document_wrapper_and_style() -> None:
+	out = embed_pytest_html_report(_SAMPLE_DOC)
+	assert "<!doctype" not in out.lower()
+	assert "<html" not in out.lower()
+	assert "<style" not in out.lower()
+	assert 'class="pytest-html-report"' in out
+	assert "<h2 " in out and 'class="pytest-html-report__title"' in out
+	assert "<h1 " not in out
+
+
+def test_embed_adds_table_classes() -> None:
+	out = embed_pytest_html_report(_SAMPLE_DOC)
+	assert 'id="environment" class="table table-vcenter card-table"' in out
+	assert 'id="results-table" class="table table-vcenter card-table"' in out
+
+
+def test_embed_preserves_scripts_and_data() -> None:
+	out = embed_pytest_html_report(_SAMPLE_DOC)
+	assert 'id="data-container"' in out
+	assert "<script>void 0;</script>" in out
+
+
+def test_embed_idempotent_for_fragment() -> None:
+	once = embed_pytest_html_report(_SAMPLE_DOC)
+	twice = embed_pytest_html_report(once)
+	assert once == twice
+
+
+def test_embed_real_pytest_html_report(tmp_path: Path) -> None:
+	(tmp_path / "test_ok.py").write_text("def test_ok() -> None:\n    assert True\n", encoding="utf-8")
+	report = tmp_path / "report.html"
+	subprocess.run(
+		[
+			sys.executable,
+			"-m",
+			"pytest",
+			f"--html={report}",
+			"--self-contained-html",
+		],
+		cwd=tmp_path,
+		check=True,
+	)
+	full = report.read_text(encoding="utf-8")
+	assert "<style" in full
+	out = embed_pytest_html_report(full)
+	assert "<style" not in out
+	assert 'id="results-table" class="table' in out
+	assert "data-jsonblob" in out
diff --git a/pygitweb_pytesthtml/pygitweb_pytesthtml/subpage_pytesthtml.py b/pygitweb_pytesthtml/pygitweb_pytesthtml/subpage_pytesthtml.py
index d609a0c..9c370e7 100644
--- a/pygitweb_pytesthtml/pygitweb_pytesthtml/subpage_pytesthtml.py
+++ b/pygitweb_pytesthtml/pygitweb_pytesthtml/subpage_pytesthtml.py
@@ -1,11 +1,11 @@
 from __future__ import annotations
 
-from pathlib import Path
 from urllib.parse import quote
 
 from pygitweb.api.subpage import Subpage
-from pygitweb.config import settings
 from pygitweb_pytesthtml.constants import PYTESTHTML_ACTION_NAME
+from pygitweb_pytesthtml.report_embed import embed_pytest_html_report
+from pygitweb_pytesthtml.worktree import report_path
 
 
 class SubpagePytestHtml(Subpage):
@@ -20,10 +20,10 @@ class SubpagePytestHtml(Subpage):
 		return "Pytest Results"
 
 	def subpage_html(self, project: str) -> str | None:
-		report_path = Path(settings.PROJECTROOT) / project / ".pygitweb" / "pytest_report.html"
-		if not report_path.is_file():
+		path = report_path(project)
+		if not path.is_file():
 			return "<p>No pytest report found.</p>"
-		return report_path.read_text(encoding="utf-8")
+		return embed_pytest_html_report(path.read_text(encoding="utf-8"))
 
 	def summary_value_suffix_html(self, project: str, project_enc: str) -> str | None:
 		a = quote(PYTESTHTML_ACTION_NAME, safe="")
diff --git a/pygitweb_pytesthtml/pygitweb_pytesthtml/worktree.py b/pygitweb_pytesthtml/pygitweb_pytesthtml/worktree.py
new file mode 100644
index 0000000..d9d5109
--- /dev/null
+++ b/pygitweb_pytesthtml/pygitweb_pytesthtml/worktree.py
@@ -0,0 +1,108 @@
+from __future__ import annotations
+
+import shutil
+from contextlib import suppress
+from pathlib import Path
+
+import pygit2
+
+from pygitweb.config import settings
+
+PYGITWEB_DIR = ".pygitweb"
+WORKTREE_DIR = "wt"
+WORKTREE_NAME = "pytesthtml"
+REPORT_FILENAME = "pytest_report.html"
+
+
+def project_root(project: str) -> Path:
+	return (Path(settings.PROJECTROOT) / project).resolve()
+
+
+def pygitweb_dir(project: str) -> Path:
+	return project_root(project) / PYGITWEB_DIR
+
+
+def report_path(project: str) -> Path:
+	return pygitweb_dir(project) / REPORT_FILENAME
+
+
+def _branch_reference(repo: pygit2.Repository) -> pygit2.Reference:
+	try:
+		return repo.lookup_reference(repo.head.name)
+	except pygit2.GitError:
+		pass
+	for ref_name in repo.references:
+		if ref_name.startswith("refs/heads/"):
+			return repo.lookup_reference(ref_name)
+	raise RuntimeError("repository has no branches to check out for pytest")
+
+
+def _prune_worktree(repo: pygit2.Repository, name: str) -> None:
+	with suppress(pygit2.GitError):
+		repo.lookup_worktree(name).prune(True)
+
+
+def _remove_untracked(wt_root: Path, wt_repo: pygit2.Repository) -> None:
+	for rel_path, status in wt_repo.status().items():
+		if status != pygit2.GIT_STATUS_WT_NEW:
+			continue
+		target = wt_root / rel_path
+		if target.is_dir():
+			shutil.rmtree(target)
+		elif target.exists():
+			target.unlink()
+
+
+def _sync_worktree(wt_repo: pygit2.Repository, wt_root: Path, commit: pygit2.Commit) -> None:
+	wt_repo.reset(commit.id, pygit2.GIT_RESET_HARD)
+	_remove_untracked(wt_root, wt_repo)
+
+
+def _ensure_bare_worktree(repo: pygit2.Repository, wt_path: Path) -> None:
+	ref = _branch_reference(repo)
+	commit = ref.peel(pygit2.Commit)
+	wt_path.parent.mkdir(parents=True, exist_ok=True)
+	path_str = str(wt_path.resolve())
+
+	if WORKTREE_NAME in repo.list_worktrees():
+		registered = Path(repo.lookup_worktree(WORKTREE_NAME).path)
+		_sync_worktree(pygit2.Repository(str(registered)), registered, commit)
+		return
+
+	if wt_path.exists():
+		shutil.rmtree(wt_path)
+
+	try:
+		repo.add_worktree(WORKTREE_NAME, path_str, ref)
+	except pygit2.GitError:
+		_prune_worktree(repo, WORKTREE_NAME)
+		shutil.rmtree(wt_path, ignore_errors=True)
+		repo.add_worktree(WORKTREE_NAME, path_str, ref)
+
+
+def prepare_pytest_run(project: str) -> tuple[Path, Path]:
+	"""Return (pytest cwd, html report path). Bare repos use a worktree under .pygitweb/wt."""
+	repo_path = project_root(project)
+	data_dir = pygitweb_dir(project)
+	report = report_path(project)
+	data_dir.mkdir(parents=True, exist_ok=True)
+
+	repo = pygit2.Repository(str(repo_path))
+	if not repo.is_bare:
+		return repo_path, report
+
+	wt_path = data_dir / WORKTREE_DIR
+	_ensure_bare_worktree(repo, wt_path)
+	return wt_path, report
+
+
+def cleanup_pytest_run(project: str) -> None:
+	"""Remove the bare-repo worktree after pytest; leaves .pygitweb/pytest_report.html."""
+	repo_path = project_root(project)
+	repo = pygit2.Repository(str(repo_path))
+	if not repo.is_bare:
+		return
+
+	_prune_worktree(repo, WORKTREE_NAME)
+	shutil.rmtree(pygitweb_dir(project) / WORKTREE_DIR, ignore_errors=True)
+	shutil.rmtree(repo_path / "worktrees" / WORKTREE_NAME, ignore_errors=True)
diff --git a/pygitweb_pytesthtml/pygitweb_pytesthtml/worktree_test.py b/pygitweb_pytesthtml/pygitweb_pytesthtml/worktree_test.py
new file mode 100644
index 0000000..c4655bb
--- /dev/null
+++ b/pygitweb_pytesthtml/pygitweb_pytesthtml/worktree_test.py
@@ -0,0 +1,107 @@
+from __future__ import annotations
+
+from pathlib import Path
+from unittest.mock import patch
+
+import pygit2
+import pytest
+
+from pygitweb_pytesthtml import worktree as wt
+
+
+def _commit(repo: pygit2.Repository, repo_dir: Path, branch: str = "main") -> None:
+	content = b"hello\n"
+	sig = pygit2.Signature("tester", "tester@example.com")
+	if repo.is_bare:
+		blob_oid = repo.create_blob(content)
+		tree_builder = repo.TreeBuilder()
+		tree_builder.insert("README.md", blob_oid, pygit2.GIT_FILEMODE_BLOB)
+		tree_oid = tree_builder.write()
+		repo.create_commit(f"refs/heads/{branch}", sig, sig, "init", tree_oid, [])
+		return
+	(repo_dir / "README.md").write_bytes(content)
+	index = repo.index
+	index.add("README.md")
+	index.write()
+	tree = index.write_tree()
+	repo.create_commit(f"refs/heads/{branch}", sig, sig, "init", tree, [])
+
+
+@pytest.fixture
+def projectroot(tmp_path: Path) -> Path:
+	return tmp_path
+
+
+def test_prepare_pytest_run_non_bare_uses_project_root(projectroot: Path) -> None:
+	repo_dir = projectroot / "app"
+	repo_dir.mkdir()
+	repo = pygit2.init_repository(str(repo_dir), bare=False)
+	_commit(repo, repo_dir)
+
+	with patch.object(wt.settings, "PROJECTROOT", str(projectroot)):
+		cwd, report = wt.prepare_pytest_run("app")
+
+	assert cwd == repo_dir.resolve()
+	assert report == (repo_dir / ".pygitweb" / "pytest_report.html").resolve()
+	assert not (repo_dir / ".pygitweb" / "wt").exists()
+
+
+def test_prepare_pytest_run_bare_creates_worktree(projectroot: Path) -> None:
+	repo_dir = projectroot / "bare.git"
+	repo_dir.mkdir()
+	repo = pygit2.init_repository(str(repo_dir), bare=True)
+	_commit(repo, repo_dir)
+
+	with patch.object(wt.settings, "PROJECTROOT", str(projectroot)):
+		cwd, report = wt.prepare_pytest_run("bare.git")
+		cwd2, _ = wt.prepare_pytest_run("bare.git")
+
+	assert cwd == (repo_dir / ".pygitweb" / "wt").resolve()
+	assert cwd == cwd2
+	assert (cwd / "README.md").read_text(encoding="utf-8") == "hello\n"
+	assert report == (repo_dir / ".pygitweb" / "pytest_report.html").resolve()
+	repo2 = pygit2.Repository(str(repo_dir))
+	assert wt.WORKTREE_NAME in repo2.list_worktrees()
+	assert Path(repo2.lookup_worktree(wt.WORKTREE_NAME).path).resolve() == cwd
+
+
+def test_prepare_pytest_run_bare_resets_worktree_on_repeat(projectroot: Path) -> None:
+	repo_dir = projectroot / "bare.git"
+	repo_dir.mkdir()
+	repo = pygit2.init_repository(str(repo_dir), bare=True)
+	_commit(repo, repo_dir, branch="main")
+
+	with patch.object(wt.settings, "PROJECTROOT", str(projectroot)):
+		cwd, _ = wt.prepare_pytest_run("bare.git")
+		(cwd / "dirty.txt").write_text("x", encoding="utf-8")
+		wt.prepare_pytest_run("bare.git")
+
+	assert not (cwd / "dirty.txt").exists()
+	assert (cwd / "README.md").is_file()
+
+
+def test_cleanup_pytest_run_removes_bare_worktree(projectroot: Path) -> None:
+	repo_dir = projectroot / "bare.git"
+	repo_dir.mkdir()
+	repo = pygit2.init_repository(str(repo_dir), bare=True)
+	_commit(repo, repo_dir)
+
+	with patch.object(wt.settings, "PROJECTROOT", str(projectroot)):
+		wt.prepare_pytest_run("bare.git")
+		wt.cleanup_pytest_run("bare.git")
+
+	repo2 = pygit2.Repository(str(repo_dir))
+	assert wt.WORKTREE_NAME not in repo2.list_worktrees()
+	assert not (repo_dir / ".pygitweb" / "wt").exists()
+
+
+def test_cleanup_pytest_run_non_bare_noop(projectroot: Path) -> None:
+	repo_dir = projectroot / "app"
+	repo_dir.mkdir()
+	repo = pygit2.init_repository(str(repo_dir), bare=False)
+	_commit(repo, repo_dir)
+
+	with patch.object(wt.settings, "PROJECTROOT", str(projectroot)):
+		wt.cleanup_pytest_run("app")
+
+	assert not (repo_dir / ".pygitweb" / "wt").exists()
