Merge pull request #336 from spencerbull/fix-hermes-desktop-launch
Fix Hermes desktop launch settings (2026.8.31-3)
This commit is contained in:
3 files changed
+158
-67
No files matched your search
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
pkgname=hermes-desktop
|
pkgname=hermes-desktop
|
||||||
pkgver=2026.8.31
|
pkgver=2026.8.31
|
||||||
pkgrel=2
|
pkgrel=3
|
||||||
pkgdesc='Native desktop shell for Hermes Agent'
|
pkgdesc='Native desktop shell for Hermes Agent'
|
||||||
arch=('x86_64')
|
arch=('x86_64')
|
||||||
url='https://github.com/NousResearch/hermes-agent'
|
url='https://github.com/NousResearch/hermes-agent'
|
||||||
@@ -75,11 +75,11 @@ source=("${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz
|
|||||||
'runtime.patch'
|
'runtime.patch'
|
||||||
'runtime-test.py')
|
'runtime-test.py')
|
||||||
sha256sums=('78fb3ff707ec1d17044b875ecac8bef28aa39d44242824f6871ca40afe7bf217'
|
sha256sums=('78fb3ff707ec1d17044b875ecac8bef28aa39d44242824f6871ca40afe7bf217'
|
||||||
'7324b1d5e7db16c6169dd9e685f4093925c4a8c0568cb7ce416ac734f3336606'
|
'094d5f3191109a80eea9f23053b78a2e00dbecf90d62d1ca04c8e48866251469'
|
||||||
'3ef685bfcf366776b025d26c37d32854d8d4aa2023b2bd07c8e08b001ef1e8c4'
|
'3ef685bfcf366776b025d26c37d32854d8d4aa2023b2bd07c8e08b001ef1e8c4'
|
||||||
'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52'
|
'd60d164e24fdcf6532133b8ea43c77a201e4b9e9dbc396187b58d51d8590ef52'
|
||||||
'03b67e26c234c797a6b1d4c9f34a57502dba37f462540e47ce6c88f6ea79302a'
|
'03b67e26c234c797a6b1d4c9f34a57502dba37f462540e47ce6c88f6ea79302a'
|
||||||
'69bc57d04610567eca35679328668619fcd3f129ed099bf5e2a1f24d1f197de9')
|
'461e1120e7e6779f531c114d9926479e47fab79113d1b4646efea36bc771b3c5')
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
cd "${srcdir}/${_srcdir}"
|
cd "${srcdir}/${_srcdir}"
|
||||||
@@ -130,11 +130,6 @@ package() {
|
|||||||
install -Dm644 "${pkgdir}/opt/${pkgname}/LICENSE.electron.txt" \
|
install -Dm644 "${pkgdir}/opt/${pkgname}/LICENSE.electron.txt" \
|
||||||
"${pkgdir}/usr/share/licenses/${pkgname}/LICENSE.electron.txt"
|
"${pkgdir}/usr/share/licenses/${pkgname}/LICENSE.electron.txt"
|
||||||
|
|
||||||
# Chromium's setuid sandbox is only needed where unprivileged user namespaces
|
# The launcher requires the namespace sandbox on the target host.
|
||||||
# are unavailable; where they work, setuid root is the worse of the two.
|
chmod 0755 "${pkgdir}/opt/${pkgname}/chrome-sandbox"
|
||||||
if ! { [[ -L /proc/self/ns/user ]] && unshare --user true; }; then
|
|
||||||
chmod 4755 "${pkgdir}/opt/${pkgname}/chrome-sandbox"
|
|
||||||
else
|
|
||||||
chmod 0755 "${pkgdir}/opt/${pkgname}/chrome-sandbox"
|
|
||||||
fi
|
|
||||||
}
|
}
|
||||||
@@ -1,37 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# Use the runtime prepared by Omarchy rather than a separate CLI on PATH.
|
unset ELECTRON_RUN_AS_NODE PYTHONPATH PYTHONHOME
|
||||||
export HERMES_DESKTOP_IGNORE_EXISTING=1
|
export HERMES_DESKTOP_IGNORE_EXISTING=1
|
||||||
|
|
||||||
# Chromium cannot reliably infer the Secret Service password-store backend
|
# Reconcile direct package installs and interrupted Omarchy setup as well.
|
||||||
# from a Hyprland session, even when GNOME Keyring is already providing it.
|
|
||||||
# Keep an explicit user choice (such as KWallet), otherwise select the
|
|
||||||
# libsecret backend that this package depends on.
|
|
||||||
export HERMES_DESKTOP_PASSWORD_STORE="${HERMES_DESKTOP_PASSWORD_STORE:-gnome-libsecret}"
|
|
||||||
|
|
||||||
# Reconcile every launch rather than trusting whatever installed us. A plain
|
|
||||||
# `pacman -S hermes-desktop`, or an install interrupted partway, leaves any
|
|
||||||
# Hermes the terminal agent had built still sitting there, and by then the
|
|
||||||
# menu entry that would have tidied it up is disabled because we are present.
|
|
||||||
if command -v omarchy-install-hermes-cli >/dev/null 2>&1; then
|
if command -v omarchy-install-hermes-cli >/dev/null 2>&1; then
|
||||||
omarchy-install-hermes-cli >/dev/null 2>&1 || true
|
omarchy-install-hermes-cli >/dev/null 2>&1 || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Chromium's own Ozone detection falls back to XWayland often enough to matter,
|
|
||||||
# and the result is a blurry window on every scaled display. Ask for Wayland
|
|
||||||
# directly, unless the user has already picked a platform themselves.
|
|
||||||
platform_flags=()
|
|
||||||
if [[ -n "${WAYLAND_DISPLAY:-}" || ${XDG_SESSION_TYPE:-} == wayland ]]; then
|
|
||||||
platform_flags=(--ozone-platform=wayland)
|
|
||||||
|
|
||||||
for flag in "$@"; do
|
|
||||||
case "$flag" in
|
|
||||||
--ozone-platform=* | --ozone-platform-hint=*) platform_flags=() ;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
hermes_home=$(realpath -ms -- "${HERMES_HOME:-$HOME/.hermes}")
|
hermes_home=$(realpath -ms -- "${HERMES_HOME:-$HOME/.hermes}")
|
||||||
parent=${hermes_home%/*}
|
parent=${hermes_home%/*}
|
||||||
if [[ ${parent##*/} == [Pp][Rr][Oo][Ff][Ii][Ll][Ee][Ss] ]]; then
|
if [[ ${parent##*/} == [Pp][Rr][Oo][Ff][Ii][Ll][Ee][Ss] ]]; then
|
||||||
@@ -41,15 +18,57 @@ fi
|
|||||||
export HERMES_HOME="$hermes_home"
|
export HERMES_HOME="$hermes_home"
|
||||||
runtime="$hermes_home/hermes-agent"
|
runtime="$hermes_home/hermes-agent"
|
||||||
native="$runtime/apps/desktop/release/linux-unpacked/Hermes"
|
native="$runtime/apps/desktop/release/linux-unpacked/Hermes"
|
||||||
|
if [[ ! -x $native || ! -x $runtime/venv/bin/hermes ]]; then
|
||||||
if [[ -x $native && -x $runtime/venv/bin/hermes ]]; then
|
native=/opt/hermes-desktop/Hermes
|
||||||
# Use the namespace sandbox without asking the CLI to make a user-writable
|
|
||||||
# helper setuid-root. Keep the same launch path for menu entries and URLs.
|
|
||||||
if ! timeout 5 unshare --user --map-root-user true 2>/dev/null; then
|
|
||||||
echo "Hermes Desktop requires working unprivileged user namespaces for its sandbox." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
exec "$native" --disable-setuid-sandbox "${platform_flags[@]}" "$@"
|
|
||||||
else
|
|
||||||
exec /opt/hermes-desktop/Hermes "${platform_flags[@]}" "$@"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Both app locations use namespaces, never a user-writable setuid helper.
|
||||||
|
if ! timeout 5 unshare --user --map-root-user true 2>/dev/null; then
|
||||||
|
echo "Hermes Desktop requires working unprivileged user namespaces for its sandbox." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
python=/usr/bin/python
|
||||||
|
if [[ -x $runtime/venv/bin/python && -f $runtime/hermes_cli/main.py ]]; then
|
||||||
|
python="$runtime/venv/bin/python"
|
||||||
|
else
|
||||||
|
runtime=""
|
||||||
|
fi
|
||||||
|
exec "$python" - "$native" "$runtime" "$@" <<'PY'
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
native, runtime, *args = sys.argv[1:]
|
||||||
|
env = os.environ.copy()
|
||||||
|
flags, gpu, store, ozone = [], "auto", "auto", "auto"
|
||||||
|
if runtime:
|
||||||
|
sys.path.insert(0, runtime)
|
||||||
|
try:
|
||||||
|
# Upstream moved the helper out of main after the packaged release.
|
||||||
|
if Path(runtime, "hermes_cli/main_desktop.py").is_file():
|
||||||
|
from hermes_cli.main_desktop import _desktop_launch_options
|
||||||
|
else:
|
||||||
|
from hermes_cli.main import _desktop_launch_options
|
||||||
|
from hermes_constants import with_hermes_node_path
|
||||||
|
|
||||||
|
flags, gpu, store, ozone = _desktop_launch_options()
|
||||||
|
env = with_hermes_node_path(env)
|
||||||
|
except ImportError:
|
||||||
|
print("Could not load Hermes desktop settings; using launch defaults.", file=sys.stderr)
|
||||||
|
|
||||||
|
env["HERMES_DESKTOP_CWD"] = os.getcwd()
|
||||||
|
if gpu != "auto":
|
||||||
|
env.setdefault("HERMES_DESKTOP_DISABLE_GPU", gpu)
|
||||||
|
if ozone != "auto":
|
||||||
|
env.setdefault("ELECTRON_OZONE_PLATFORM_HINT", ozone)
|
||||||
|
env.setdefault("HERMES_DESKTOP_PASSWORD_STORE", store if store != "auto" else "gnome-libsecret")
|
||||||
|
|
||||||
|
# Explicit config, environment and command-line choices override the Wayland default.
|
||||||
|
if (env.get("WAYLAND_DISPLAY") or env.get("XDG_SESSION_TYPE") == "wayland") and (
|
||||||
|
"ELECTRON_OZONE_PLATFORM_HINT" not in env
|
||||||
|
and not any(arg.startswith(("--ozone-platform=", "--ozone-platform-hint=")) for arg in flags + args)
|
||||||
|
):
|
||||||
|
flags.insert(0, "--ozone-platform=wayland")
|
||||||
|
os.execve(native, [native, "--disable-setuid-sandbox", *flags, *args], env)
|
||||||
|
PY
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
"""Check native launch and the release updater without running Hermes or sudo."""
|
"""Check launch settings and sandbox gates without running Hermes or sudo."""
|
||||||
|
import ast
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -26,6 +27,9 @@ Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({
|
|||||||
"args": sys.argv[1:], "home": os.environ["HERMES_HOME"],
|
"args": sys.argv[1:], "home": os.environ["HERMES_HOME"],
|
||||||
"store": os.environ["HERMES_DESKTOP_PASSWORD_STORE"],
|
"store": os.environ["HERMES_DESKTOP_PASSWORD_STORE"],
|
||||||
"gpu": os.environ.get("HERMES_DESKTOP_DISABLE_GPU"),
|
"gpu": os.environ.get("HERMES_DESKTOP_DISABLE_GPU"),
|
||||||
|
"ozone": os.environ.get("ELECTRON_OZONE_PLATFORM_HINT"),
|
||||||
|
"cwd": os.environ.get("HERMES_DESKTOP_CWD"),
|
||||||
|
"inherited": [name for name in ("ELECTRON_RUN_AS_NODE", "PYTHONPATH", "PYTHONHOME") if name in os.environ],
|
||||||
}))
|
}))
|
||||||
''')
|
''')
|
||||||
executable.chmod(0o755)
|
executable.chmod(0o755)
|
||||||
@@ -33,6 +37,30 @@ Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({
|
|||||||
sandbox.write_text("fixture")
|
sandbox.write_text("fixture")
|
||||||
sandbox.chmod(0o755)
|
sandbox.chmod(0o755)
|
||||||
|
|
||||||
|
# Execute the release's real option parser, isolating config I/O and avoiding
|
||||||
|
# unrelated CLI imports, startup hooks and third-party dependencies.
|
||||||
|
module = runtime / "hermes_cli"
|
||||||
|
module.mkdir()
|
||||||
|
(module / "__init__.py").touch()
|
||||||
|
upstream = ast.parse((source / "hermes_cli/main.py").read_text())
|
||||||
|
option_parser = next(node for node in upstream.body
|
||||||
|
if isinstance(node, ast.FunctionDef) and node.name == "_desktop_launch_options")
|
||||||
|
stores = next(node for node in upstream.body if isinstance(node, ast.Assign)
|
||||||
|
and any(isinstance(target, ast.Name) and target.id == "_LINUX_PASSWORD_STORES"
|
||||||
|
for target in node.targets))
|
||||||
|
helper = "import os, shlex\n" + ast.unparse(stores) + "\n" + ast.unparse(option_parser) + "\n"
|
||||||
|
(module / "main.py").write_text(helper)
|
||||||
|
(module / "config.py").write_text('''import json, os
|
||||||
|
from pathlib import Path
|
||||||
|
def load_config():
|
||||||
|
path = Path(os.environ["HERMES_HOME"], "config.yaml")
|
||||||
|
return json.loads(path.read_text()) if path.exists() else {}
|
||||||
|
''')
|
||||||
|
(runtime / "hermes_constants.py").write_text('''import os
|
||||||
|
def with_hermes_node_path(env=None):
|
||||||
|
return (os.environ if env is None else env).copy()
|
||||||
|
''')
|
||||||
|
|
||||||
mock_bin = root / "bin"
|
mock_bin = root / "bin"
|
||||||
mock_bin.mkdir()
|
mock_bin.mkdir()
|
||||||
unshare = mock_bin / "unshare"
|
unshare = mock_bin / "unshare"
|
||||||
@@ -43,6 +71,7 @@ Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({
|
|||||||
cli.parent.mkdir(parents=True)
|
cli.parent.mkdir(parents=True)
|
||||||
cli.write_text(forbidden)
|
cli.write_text(forbidden)
|
||||||
cli.chmod(0o755)
|
cli.chmod(0o755)
|
||||||
|
(cli.parent / "python").symlink_to(sys.executable)
|
||||||
for command in ("sudo", "omarchy-install-hermes-cli"):
|
for command in ("sudo", "omarchy-install-hermes-cli"):
|
||||||
target = mock_bin / command
|
target = mock_bin / command
|
||||||
target.write_text(forbidden if command == "sudo" else '#!/bin/bash\nexit 0\n')
|
target.write_text(forbidden if command == "sudo" else '#!/bin/bash\nexit 0\n')
|
||||||
@@ -53,33 +82,81 @@ Path(os.environ["TEST_OUTPUT"]).write_text(json.dumps({
|
|||||||
env = {"HOME": str(home), "PATH": f"{mock_bin}:/usr/bin:/bin",
|
env = {"HOME": str(home), "PATH": f"{mock_bin}:/usr/bin:/bin",
|
||||||
"TEST_OUTPUT": str(output), "TEST_FORBIDDEN": str(forbidden_output)}
|
"TEST_OUTPUT": str(output), "TEST_FORBIDDEN": str(forbidden_output)}
|
||||||
launch = ["bash", str(launcher.resolve())]
|
launch = ["bash", str(launcher.resolve())]
|
||||||
for args, overrides, expected_args, expected_store in (
|
|
||||||
([], {"WAYLAND_DISPLAY": "wayland-1"}, ["--ozone-platform=wayland"], "gnome-libsecret"),
|
def check_launch(args=(), overrides=None, expected_args=(), store="gnome-libsecret", gpu=None, ozone=None):
|
||||||
(["--ozone-platform=x11", "hermes://open?text=a%20b"],
|
subprocess.run(launch + list(args), env={**env, **(overrides or {})}, cwd=home, check=True)
|
||||||
{"WAYLAND_DISPLAY": "wayland-1", "HERMES_DESKTOP_PASSWORD_STORE": "kwallet6"},
|
|
||||||
["--ozone-platform=x11", "hermes://open?text=a%20b"], "kwallet6"),
|
|
||||||
([], {"HERMES_HOME": str(home / ".hermes/profiles/work"), "HERMES_DESKTOP_DISABLE_GPU": "1"},
|
|
||||||
[], "gnome-libsecret"),
|
|
||||||
):
|
|
||||||
subprocess.run(launch + args, env={**env, **overrides}, check=True)
|
|
||||||
result = json.loads(output.read_text())
|
result = json.loads(output.read_text())
|
||||||
assert result["args"] == ["--disable-setuid-sandbox", *expected_args], result
|
assert result == {"args": ["--disable-setuid-sandbox", *expected_args],
|
||||||
assert result["home"] == str(home / ".hermes"), result
|
"home": str(home / ".hermes"), "store": store, "gpu": gpu,
|
||||||
assert result["store"] == expected_store, result
|
"ozone": ozone, "cwd": str(home), "inherited": []}, result
|
||||||
assert result["gpu"] == overrides.get("HERMES_DESKTOP_DISABLE_GPU"), result
|
|
||||||
assert not forbidden_output.exists(), "launcher invoked CLI or sudo"
|
assert not forbidden_output.exists(), "launcher invoked CLI or sudo"
|
||||||
output.unlink()
|
output.unlink()
|
||||||
for code in ("1", "127"):
|
|
||||||
for args in ([], ["hermes://open"]):
|
wayland = {"WAYLAND_DISPLAY": "wayland-1"}
|
||||||
result = subprocess.run(launch + args, env={**env, "TEST_NAMESPACE_RESULT": code},
|
check_launch(overrides=wayland, expected_args=["--ozone-platform=wayland"])
|
||||||
capture_output=True, text=True)
|
url = "hermes://open?text=a%20b"
|
||||||
assert result.returncode != 0 and "user namespaces" in result.stderr, result
|
check_launch(["--ozone-platform=x11", url], {**wayland, "HERMES_DESKTOP_PASSWORD_STORE": "kwallet6"},
|
||||||
assert not output.exists() and not forbidden_output.exists()
|
["--ozone-platform=x11", url], store="kwallet6")
|
||||||
|
check_launch(overrides={"HERMES_HOME": str(home / ".hermes/profiles/work"),
|
||||||
|
"HERMES_DESKTOP_DISABLE_GPU": "1"}, gpu="1")
|
||||||
|
check_launch(overrides={"ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid"})
|
||||||
|
|
||||||
|
config = home / ".hermes/config.yaml"
|
||||||
|
config.write_text(json.dumps({"desktop": {"disable_gpu": True, "password_store": "kwallet5",
|
||||||
|
"ozone_platform_hint": "x11",
|
||||||
|
"electron_flags": '--force-device-scale-factor=1.5 "--test=a b"'}}))
|
||||||
|
config_flags = ["--force-device-scale-factor=1.5", "--test=a b"]
|
||||||
|
for args in ([], [url]):
|
||||||
|
check_launch(args, wayland, config_flags + args, store="kwallet5", gpu="1", ozone="x11")
|
||||||
|
check_launch(overrides={**wayland, "HERMES_DESKTOP_DISABLE_GPU": "0",
|
||||||
|
"HERMES_DESKTOP_PASSWORD_STORE": "basic", "ELECTRON_OZONE_PLATFORM_HINT": "wayland"},
|
||||||
|
expected_args=config_flags, store="basic", gpu="0", ozone="wayland")
|
||||||
|
config.write_text(json.dumps({"desktop": {"electron_flags": ["--ozone-platform=x11"], "disable_gpu": False}}))
|
||||||
|
check_launch(overrides=wayland, expected_args=["--ozone-platform=x11"], gpu="0")
|
||||||
|
# New upstream revisions expose the same helper from main_desktop instead.
|
||||||
|
(module / "main_desktop.py").write_text(helper)
|
||||||
|
(module / "main.py").write_text('raise AssertionError("old helper import after update")\n')
|
||||||
|
check_launch([url], wayland, ["--ozone-platform=x11", url], gpu="0")
|
||||||
|
config.unlink()
|
||||||
|
(module / "main_desktop.py").write_text(helper + '\nimport os\nos.environ.update(' + repr({
|
||||||
|
"ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid",
|
||||||
|
"HERMES_HOME": "/invalid", "HERMES_DESKTOP_DISABLE_GPU": "1",
|
||||||
|
"HERMES_DESKTOP_PASSWORD_STORE": "basic", "ELECTRON_OZONE_PLATFORM_HINT": "x11",
|
||||||
|
}) + ')\n')
|
||||||
|
check_launch(overrides={**wayland, "HERMES_DESKTOP_DISABLE_GPU": "0"},
|
||||||
|
expected_args=["--ozone-platform=wayland"], gpu="0")
|
||||||
|
(module / "main_desktop.py").write_text(helper)
|
||||||
|
|
||||||
|
def check_namespace_failure():
|
||||||
|
for code in ("1", "127"):
|
||||||
|
for args in ([], [url]):
|
||||||
|
result = subprocess.run(launch + args, env={**env, "TEST_NAMESPACE_RESULT": code},
|
||||||
|
capture_output=True, text=True)
|
||||||
|
assert result.returncode != 0 and "user namespaces" in result.stderr, result
|
||||||
|
assert not output.exists() and not forbidden_output.exists()
|
||||||
|
|
||||||
|
check_namespace_failure()
|
||||||
|
# Exercise the real fallback branch with only its absolute app path redirected.
|
||||||
|
fallback = root / "packaged-Hermes"
|
||||||
|
shutil.copyfile(executable, fallback)
|
||||||
|
fallback.chmod(0o755)
|
||||||
|
fallback_launcher = root / "fallback-launcher"
|
||||||
|
fallback_launcher.write_text(launcher.read_text().replace("/opt/hermes-desktop/Hermes", str(fallback)))
|
||||||
|
launch = ["bash", str(fallback_launcher)]
|
||||||
|
executable.unlink()
|
||||||
|
check_launch([url], wayland, ["--ozone-platform=wayland", url])
|
||||||
|
(module / "main_desktop.py").write_text('raise ImportError("incomplete Python dependencies")\n')
|
||||||
|
check_launch([url], wayland, ["--ozone-platform=wayland", url])
|
||||||
|
shutil.rmtree(runtime / "venv")
|
||||||
|
check_launch(overrides={"ELECTRON_RUN_AS_NODE": "1", "PYTHONPATH": "/invalid", "PYTHONHOME": "/invalid"})
|
||||||
|
check_namespace_failure()
|
||||||
|
|
||||||
gate = ["bash", str(destination), "--self-test-gate", "--install-root", str(runtime),
|
gate = ["bash", str(destination), "--self-test-gate", "--install-root", str(runtime),
|
||||||
"--relaunch-target", str(executable)]
|
"--relaunch-target", str(executable)]
|
||||||
|
executable.touch()
|
||||||
|
executable.chmod(0o755)
|
||||||
assert subprocess.check_output(gate, env=env, text=True).strip() == "relaunch"
|
assert subprocess.check_output(gate, env=env, text=True).strip() == "relaunch"
|
||||||
assert subprocess.check_output(gate, env={**env, "TEST_NAMESPACE_RESULT": "1"}, text=True).startswith("manual:")
|
assert subprocess.check_output(gate, env={**env, "TEST_NAMESPACE_RESULT": "1"}, text=True).startswith("manual:")
|
||||||
gate[-1] = "/opt/hermes-desktop/Hermes"
|
gate[-1] = "/opt/hermes-desktop/Hermes"
|
||||||
assert subprocess.check_output(gate, env=env, text=True).startswith("skew:")
|
assert subprocess.check_output(gate, env=env, text=True).startswith("skew:")
|
||||||
print("PASS: direct native launches fail closed; release updater accepts the namespace sandbox")
|
print("PASS: desktop settings, environment, native/fallback namespace sandbox and updater gate")
|
||||||
Reference in new issue
Block a user