aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CLAUDE.md32
-rwxr-xr-xpackages/meshbay-hub/tests/harness/sticky_header_probe.py47
2 files changed, 53 insertions, 26 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index a7ecbca..f0f90d5 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -163,18 +163,26 @@ These are about working on the tree rather than about the design:
- **The SPA served in production may be older than this tree.** Check the served
`/a/<hash>/` against `meshbay_hub.api.webapp.ASSET_V` before concluding a fix
is missing. `site/` and the Caddy config have never been deployed
-- **`test_sticky_header.py[firefox]` needs no Firefox instance on the machine,
- and says nothing about the code when one is there.** The harness launches
- `firefox --headless --screenshot` with `HOME` pointed at a throwaway
- directory, which isolates the *profile* and not snap's single instance: with
- any Firefox already up it prints "Firefox is already running, but is not
- responding", never loads the page, and the fixture times out at 60 s — eleven
- errors at setup that look like a regression and are not. Neither
- `--no-remote` nor `--new-instance` changes it; a failed run also appears to
- leave a process behind, which blocks the next one. Before reading anything
- into these: `ps -eo pid,etimes,args | grep firefox`, and re-run against a
- stashed tree. The `[chrome]` half of the same file covers the same geometry
- and is the one to trust meanwhile
+- **A sandbox can refuse a path without saying so, and the browser blames
+ something else.** `test_sticky_header.py[firefox]` failed with twelve setup
+ errors whenever the developer's own Firefox was open, and the note that used
+ to be here said to close it and trust the `[chrome]` half meanwhile. The
+ cause was not snap's single instance. The harness pointed `HOME` at a
+ throwaway directory to isolate the profile; snapd sets its own `HOME` inside
+ the sandbox, so that directory came back **empty on every run** — the tell,
+ and it was there all along — and Firefox opened the developer's real profile,
+ which their browser had locked. `--profile` did not help either, for two more
+ sandbox rules: the snap has a private `/tmp`, so a directory made there is
+ not the one it sees ("Could not find profile folder"), and its `home`
+ interface grants no *hidden* directory, so `~/.cache` is refused too. Both
+ refusals surface as **"Firefox is already running, but is not responding" —
+ which it also prints when nothing is running at all**, so the message names
+ neither the real cause nor even the right question. A plain directory under
+ `$HOME` (the probe prefers `~/snap/firefox/common`) fixes it: 24 passed in
+ 15 s with a Firefox open throughout, against 12 errors in 60 s before. The
+ rule worth keeping: **when a sandboxed program cannot see a path, find out
+ what it can see before believing what it says** — and an isolation directory
+ that stays empty is not isolating anything
- **One UI source.** `packages/meshbay-hub/src/meshbay_hub/static/` is the
interface, for the web and the app alike; `packages/meshbay-client/scripts/
sync-ui.js` copies it (`npm run sync-ui`) and CI fails if the copy drifts —
diff --git a/packages/meshbay-hub/tests/harness/sticky_header_probe.py b/packages/meshbay-hub/tests/harness/sticky_header_probe.py
index 7579bf9..3e75494 100755
--- a/packages/meshbay-hub/tests/harness/sticky_header_probe.py
+++ b/packages/meshbay-hub/tests/harness/sticky_header_probe.py
@@ -29,7 +29,6 @@ half of MeshBay's readers and has its own history with `position: sticky`.
import argparse
import http.server
import json
-import os
import socketserver
import subprocess
import sys
@@ -525,22 +524,41 @@ ENGINES = {
# is driven through `--screenshot`: the picture is thrown away, the point
# is that the page runs and the load event is what ends the process.
#
- # And it is isolated with `HOME`, not `--profile`: given `--profile` on a
- # directory it has not initialised itself, this Firefox starts, prints its
- # headless banner, and then never requests the URL at all — no error, no
- # page, nothing to debug. Pointing HOME at a throwaway directory lets it
- # create its own default profile there, which works and leaves the
- # developer's real one alone.
+ # `--profile`, and the directory has to be one the browser can actually
+ # read — see PROFILE_PARENT. This used to point `HOME` at a throwaway
+ # directory instead, which isolated nothing at all: snapd sets its own HOME
+ # inside the sandbox, the throwaway one came back empty every time, and
+ # Firefox opened the developer's real profile. With their browser open that
+ # profile is locked, so every run printed "Firefox is already running" and
+ # measured nothing — twelve errors at setup that looked like a regression
+ # and were not.
"firefox": lambda profile, size: [
- "firefox", "--headless",
+ "firefox", "--headless", "--profile", profile,
"--screenshot", str(Path(profile) / "shot.png"),
"--window-size", size],
}
-# The environment each engine is launched with, on top of the current one.
-ENGINE_ENV = {"firefox": lambda profile: {"HOME": profile}}
# Which engines need the load event held until the measurement is in.
HOLDS_LOAD = {"firefox"}
+# Where an engine's throwaway profile has to live. Chrome takes /tmp and is
+# absent from this map.
+#
+# Firefox on this distribution is a snap, and two rules of that sandbox decide
+# this between them: it has a private /tmp, so a directory made there is simply
+# not the one it sees, and the `home` interface grants no *hidden* directory, so
+# `~/.cache` is refused as well. Both failures are silent in their own way —
+# "Could not find profile folder" for the first, and for an unreadable profile
+# the same misleading "Firefox is already running" it prints when nothing is
+# running at all. What is left is a plain directory under $HOME; the snap's own
+# data directory is preferred where it exists, so a run leaves nothing in the
+# developer's home even for the moment it takes.
+def _profile_parent() -> Path | None:
+ snap_data = Path.home() / "snap" / "firefox" / "common"
+ return snap_data if snap_data.is_dir() else Path.home()
+
+
+PROFILE_PARENT = {"firefox": _profile_parent}
+
def main() -> int:
ap = argparse.ArgumentParser()
@@ -555,12 +573,13 @@ def main() -> int:
# ignore_cleanup_errors for the same reason group_tab_probe.py gives:
# Chrome's children outlive terminate() by a moment and go on writing
# into the profile, and a throwaway profile is not worth a failed run.
- with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as profile:
- env = dict(os.environ,
- **ENGINE_ENV.get(args.engine, lambda _p: {})(profile))
+ parent = PROFILE_PARENT.get(args.engine)
+ with tempfile.TemporaryDirectory(
+ ignore_cleanup_errors=True, prefix="meshbay-probe-",
+ dir=str(parent()) if parent else None) as profile:
proc = subprocess.Popen(
launcher(profile, "1200,900") + [f"http://127.0.0.1:{PORT}/"],
- env=env, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
for _ in range(900):
if RECORDS:
break