"""End-to-end check of the session engine against a real tmux server.
Runs on a throwaway socket so it can never see, touch or kill a live session.
Deliberately not mocked: the failure modes worth catching here (quoting, pane
history limits, send-keys interpreting a prompt as key names) only exist when
tmux is actually running.
"""
from __future__ import annotations
import datetime
import json
import os
import re
import shutil
import subprocess
import sys
import time
from pathlib import Path
from types import SimpleNamespace
from typing import Any, ClassVar
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
from clique import app as app_mod
from clique import (
attention,
files,
gitinfo,
notify,
services,
sysinfo,
termstrip,
tmux,
usage,
working,
)
from clique.__main__ import config_path
from clique.registry import Registry, RegistryError
# Per process: two worktrees running this at once must not kill each other's sessions.
SOCKET = f"clique-smoke-{os.getpid()}"
ROOT = Path(__file__).resolve().parents[1]
passed = failed = 0
def check(label: str, cond: bool, detail: object = "") -> None:
global passed, failed
if cond:
passed += 1
print(f" ok {label}")
else:
failed += 1
print(f" FAIL {label} {detail}")
def check_mcp() -> None:
"""The read-only MCP server, against a throwaway panel.
Same shape as ``tools/smoke_http.py``: own port, own home, own tmux
socket, so this cannot see or kill a live session. Speaks JSON-RPC on
the server's stdin, which is what an MCP client actually does.
"""
import contextlib
import select
import socket
import urllib.error
import urllib.request
print("mcp")
home = Path(f"/tmp/clique-mcp-smoke-home-{os.getpid()}")
socket_name = f"clique-mcp-smoke-{os.getpid()}"
# Throwaway panel on loopback. Gone before this function returns.
password = "mcp-smoke-check" # noqa: S105
env = dict(os.environ, CLIQUE_HOME=str(home), CLIQUE_TMUX_SOCKET=socket_name)
env.pop("CLIQUE_TOKEN", None)
env.pop("CLIQUE_URL", None)
try:
missing = subprocess.run(
[sys.executable, "-m", "clique", "mcp"],
cwd=str(ROOT),
env=env,
capture_output=True,
text=True,
timeout=15,
)
except subprocess.TimeoutExpired:
missing = None
check("mcp refuses to start without CLIQUE_TOKEN", False, "hung on stdin")
if missing is not None:
check(
"mcp refuses to start without CLIQUE_TOKEN",
missing.returncode == 1,
missing.returncode,
)
check(
"the error names how to create a token",
"python3 -m clique token create" in missing.stderr,
missing.stderr.strip()[:200],
)
check(
"and that error does not contain a token",
"mxp_" not in missing.stderr and "mxp_" not in missing.stdout,
)
# The panel clamps peek to 40, so a client asking for a megabyte would
# still look fine on the wire. The cap has to be checked here, or a
# regression in the wrapper cannot fail this test.
from clique.mcp_server import ToolError, _lines, _timeout
check("peek lines default to 8", _lines({}) == 8)
check("peek lines cap at 200", _lines({"lines": 10000}) == 200)
try:
_lines({"lines": 0})
rejected = False
except ToolError:
rejected = True
check("peek lines reject 0", rejected)
check("omitted timeout is left to the route", _timeout({}) is None)
# Read-only is true today by inspection, not by anything that would fail
# if it stopped being true. This is that check: one Client.get(), method
# always GET, no write verb anywhere in the file.
src = (ROOT / "clique" / "mcp_server.py").read_text()
check("exactly one call site makes an HTTP request", src.count("urllib.request.Request(") == 1)
check("that call is hardcoded GET", 'method="GET"' in src)
check(
"no write HTTP verb appears anywhere in the file",
not any(verb in src for verb in ('"POST"', '"PUT"', '"DELETE"', '"PATCH"')),
)
check("timeout caps at 300", _timeout({"timeout": 9000}) == 300)
shutil.rmtree(home, ignore_errors=True)
home.mkdir(parents=True)
tmux._run(["kill-server"], socket_name, check=False)
probe = socket.socket()
probe.bind(("127.0.0.1", 0))
port = probe.getsockname()[1]
probe.close()
base = f"http://127.0.0.1:{port}"
panel = None
mcp = None
token = ""
err_fh = (home / "panel.err").open("w")
try:
panel = subprocess.Popen(
[
sys.executable,
"-m",
"clique",
"--host",
"127.0.0.1",
"--port",
str(port),
"--password",
password,
"--state",
str(home / "state.json"),
],
cwd=str(ROOT),
env=env,
stdout=subprocess.DEVNULL,
stderr=err_fh,
)
up = False
for _ in range(80):
if panel.poll() is not None:
break
try:
urllib.request.urlopen(base + "/healthz", timeout=2).read()
up = True
break
except (urllib.error.URLError, OSError):
time.sleep(0.25)
if not up:
err_fh.flush()
tail = (home / "panel.err").read_text(encoding="utf-8", errors="replace")[-200:]
check("throwaway panel is up", False, tail.strip())
return
check("throwaway panel is up", True)
minted = subprocess.run(
[sys.executable, "-m", "clique", "token", "create", "mcp-smoke", "--read-only"],
cwd=str(ROOT),
env=env,
capture_output=True,
text=True,
timeout=20,
)
for line in minted.stdout.splitlines():
if line.strip().startswith("mxp_"):
token = line.strip()
check(
"mcp smoke token minted",
token.startswith("mxp_"),
minted.stderr.strip()[:200],
)
if not token:
return
mcp = subprocess.Popen(
[sys.executable, "-m", "clique", "mcp"],
cwd=str(ROOT),
env={**env, "CLIQUE_URL": base, "CLIQUE_TOKEN": token, "PYTHONUNBUFFERED": "1"},
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
pending = bytearray()
def rpc(msg: dict, timeout: float = 10) -> dict:
assert mcp is not None and mcp.stdin is not None and mcp.stdout is not None
mcp.stdin.write((json.dumps(msg) + "\n").encode())
mcp.stdin.flush()
fd = mcp.stdout.fileno()
os.set_blocking(fd, False)
deadline = time.time() + timeout
while b"\n" not in pending:
if time.time() > deadline:
raise TimeoutError("mcp server did not answer")
ready, _, _ = select.select([fd], [], [], 0.2)
if not ready:
if mcp.poll() is not None:
raise RuntimeError(f"mcp exited {mcp.returncode}")
continue
try:
chunk = os.read(fd, 65536)
except BlockingIOError:
continue
if not chunk:
raise RuntimeError("mcp closed stdout")
pending.extend(chunk)
line, _, rest = bytes(pending).partition(b"\n")
pending.clear()
pending.extend(rest)
return json.loads(line)
init = rpc(
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {},
"clientInfo": {"name": "smoke", "version": "0"},
},
}
)
agreed = (init.get("result") or {}).get("protocolVersion")
check(
"initialize answers", init.get("id") == 1 and agreed == "2025-11-25", init.get("error")
)
listed = rpc({"jsonrpc": "2.0", "id": 2, "method": "tools/list"})
tools = (listed.get("result") or {}).get("tools") or []
names = [tool.get("name") for tool in tools]
check(
"tools/list has the five read-only tools",
names
== [
"list_sessions",
"get_session",
"wait",
"preview_pane",
"conversation",
],
names,
)
check(
"each tool declares an object input schema",
all(
isinstance(tool.get("inputSchema"), dict)
and tool["inputSchema"].get("type") == "object"
for tool in tools
),
names,
)
called = rpc(
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {"name": "list_sessions", "arguments": {}},
}
)
result = called.get("result") or {}
content = result.get("content") or []
text = content[0].get("text") if content and isinstance(content[0], dict) else ""
try:
parsed = json.loads(text) if isinstance(text, str) else None
except json.JSONDecodeError:
parsed = None
check(
"list_sessions returns the session list",
result.get("isError") is False and isinstance(parsed, list),
(text or "")[:200],
)
missing_pane = rpc(
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "preview_pane",
"arguments": {"id": "no-such-session", "lines": 8},
},
}
)
bad = missing_pane.get("result") or {}
bad_content = bad.get("content") or []
bad_text = bad_content[0].get("text") if bad_content else ""
check(
"a missing session is a tool error, not an empty pane",
bad.get("isError") is True and "404" in (bad_text or ""),
(bad_text or "")[:200],
)
except Exception as exc: # noqa: BLE001 — a hung server must fail the check, not the file
check("mcp dialogue", False, f"{type(exc).__name__}: {exc}")
finally:
err_text = ""
if mcp is not None:
if mcp.stdin is not None:
with contextlib.suppress(OSError):
mcp.stdin.close()
try:
mcp.wait(timeout=5)
except subprocess.TimeoutExpired:
mcp.terminate()
try:
mcp.wait(timeout=5)
except subprocess.TimeoutExpired:
mcp.kill()
mcp.wait(timeout=5)
if mcp.stderr is not None:
err_text = mcp.stderr.read().decode("utf-8", "replace")
if mcp is not None and token:
check("mcp stderr never echoes the token", token not in err_text)
if panel is not None and panel.poll() is None:
panel.terminate()
try:
panel.wait(timeout=10)
except subprocess.TimeoutExpired:
panel.kill()
panel.wait(timeout=5)
err_fh.close()
tmux._run(["kill-server"], socket_name, check=False)
shutil.rmtree(home, ignore_errors=True)
def main() -> int:
if not tmux.available():
print("tmux not installed — cannot run engine smoke test")
return 1
tmux._run(["kill-server"], SOCKET, check=False)
print("isolation")
# A test that shares the live socket or the live state file is how a
# /tmp shell lands on the tab someone is looking at. These two are the
# whole wall; if either fails, the rest of the suite is not safe to run.
stripped = {k: v for k, v in os.environ.items() if k != "CLIQUE_TMUX_SOCKET"}
default_socket = subprocess.run(
[sys.executable, "-c", "from clique import tmux; print(tmux.SOCKET)"],
capture_output=True,
text=True,
cwd=str(ROOT),
env=stripped,
)
check(
"the engine defaults to its own socket",
default_socket.stdout.strip() == "clique",
default_socket.stdout,
)
named = subprocess.run(
[sys.executable, "-c", "from clique import tmux; print(tmux.SOCKET)"],
capture_output=True,
text=True,
cwd=str(ROOT),
env={**os.environ, "CLIQUE_TMUX_SOCKET": "clique-env-check"},
)
check(
"CLIQUE_TMUX_SOCKET is how a test gets a different one",
named.stdout.strip() == "clique-env-check",
named.stdout,
)
sandbox = "/tmp/clique-state-home-test"
state_out = subprocess.run(
[
sys.executable,
"-c",
"from clique.__main__ import default_state_path; print(default_state_path())",
],
capture_output=True,
text=True,
cwd=str(ROOT),
env={**os.environ, "CLIQUE_HOME": sandbox},
)
check(
"a test home does not inherit the live state file",
state_out.stdout.strip() == sandbox + "/state.json",
state_out.stdout.strip() or state_out.stderr[-200:],
)
print("boxed CLIs do not steal the mouse from the browser")
filt = termstrip.boxed_stream()
check("plain text is untouched", filt.feed(b"hello") == b"hello")
check("mouse tracking on is hidden", filt.feed(b"\x1b[?1000h\x1b[?1006hhi") == b"hi")
check("mouse tracking off is hidden too", filt.feed(b"\x1b[?1000lbye") == b"bye")
check(
"bracketed paste stays when mixed with mouse",
filt.feed(b"\x1b[?1000;2004h") == b"\x1b[?2004h",
)
check("colour is not a mouse code", filt.feed(b"\x1b[31mred") == b"\x1b[31mred")
split = termstrip.boxed_stream()
check("a split sequence is held", split.feed(b"\x1b[?100") == b"")
check("and dropped once it completes", split.feed(b"0hOK") == b"OK")
check("the alt screen switch is hidden", filt.feed(b"\x1b[?1049hview") == b"view")
check("wiping scrollback is hidden", filt.feed(b"\x1b[3Jkeep") == b"keep")
check("a visible clear still happens", filt.feed(b"\x1b[2J") == b"\x1b[2J")
shell = termstrip.plain_stream()
check("a shell keeps mouse tracking", shell.feed(b"\x1b[?1000h") == b"\x1b[?1000h")
# The browser in the alt buffer shows one screen and none of the history.
check("a shell hides the alt screen switch too", shell.feed(b"\x1b[?1049hls") == b"ls")
print("registry")
# The same file the app resolves to, asked for the same way. Naming the
# path here by hand is what let it rot when the catalogue moved into the
# package: the suite went on passing against a stale copy left on disk
# while CI, which has no stale copy, failed.
reg = Registry(config_path(None))
types = reg.types()
check("loads clis.toml", set(types) >= {"claude", "grok", "shell"}, sorted(types))
check("mode pill on for claude", types["claude"].has_modes)
check("mode pill off for grok", not types["grok"].has_modes)
argv = reg.launch_argv("shell", session_id="a" * 32, name="smoke", cwd="/tmp")
# argv[0] is the *resolved* path, not the bare name: a CLI installed
# outside the service's PATH still has to launch. This is what grok needed.
check(
"renders argv with a resolved binary",
argv[0].endswith("/bash") and Path(argv[0]).is_file(),
argv,
)
check("detects an installed CLI", types["shell"].installed)
check(
"reports a missing CLI as absent",
not any(c.installed for c in types.values() if c.command == "definitely-not-here"),
"",
)
from clique.registry import parse as parse_registry
absent = parse_registry({"cli": {"nope": {"command": "definitely-not-here-9x"}}})["nope"]
check(
"resolve() returns None for a binary that is not here",
absent.resolve() is None and not absent.installed,
)
print("files")
# A click on a printed path is a filesystem read, so these are real files
# in a throwaway directory, not mocks.
import tempfile
png = (
b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00"
b"\x01\x08\x02\x00\x00\x00\x90wS\xde\x00\x00\x00\x0cIDATx\x9cc``"
b"\x00\x00\x00\x04\x00\x01\xf6\x178U\x00\x00\x00\x00IEND\xaeB`\x82"
)
tmp = Path(tempfile.mkdtemp(prefix="clique-files-"))
(tmp / "note.md").write_text("hello\n", encoding="utf-8")
(tmp / "shot.png").write_bytes(png)
(tmp / "bin.dat").write_bytes(b"\x00\x01\x02")
(tmp / "sub").mkdir()
(tmp / "sub" / "child.md").write_text("nested\n", encoding="utf-8")
(tmp / "big.txt").write_bytes(b"x" * (files.TEXT_CAP + 8))
check("strips a compiler suffix", files.clean("src/app.js:42:7") == "src/app.js")
check("strips trailing punctuation", files.clean("docs/foo.md.") == "docs/foo.md")
check("a lone dot is this folder", files.clean(".") == ".")
check("and so is parent", files.clean("..") == "..")
text = files.inspect(str(tmp), "note.md")
check("reads a relative text file", text["kind"] == "text" and text["text"] == "hello\n", text)
check(
"and a :line suffix still finds it", files.inspect(str(tmp), "note.md:12")["kind"] == "text"
)
check(
"an image is an image from its bytes",
files.inspect(str(tmp), "shot.png")["kind"] == "image",
)
check(
"a nul in the first block is binary", files.inspect(str(tmp), "bin.dat")["kind"] == "binary"
)
listing = files.inspect(str(tmp), "sub")
check("a directory is a directory", listing["kind"] == "dir")
names = [row["name"] for row in listing.get("entries") or []]
check(
"and it lists what is inside",
"child.md" in names and ".." in names,
names,
)
check(
"listed paths stay inside the folder",
all(
row["path"].startswith(str(tmp / "sub")) or row["name"] == ".."
for row in listing.get("entries") or []
),
listing.get("entries"),
)
top = files.inspect(str(tmp), ".")
top_names = [row["name"] for row in top.get("entries") or []]
check("the session folder listing has no parent climb", ".." not in top_names, top_names)
check("and it still names a child", "note.md" in top_names, top_names)
check(
"missing stays missing, not an error",
files.inspect(str(tmp), "nope.md")["kind"] == "missing",
)
# The temp folder is readable from any session (CLIs print scratch paths
# there), but these fixtures live in temp too, so point the exception
# elsewhere for the fence checks below.
check(
"a temp-folder file is readable from another session dir",
files.inspect("/", str(tmp / "note.md"))["kind"] == "text",
)
files._TEMP = Path("/nonexistent-clique-temp")
# Reads are fenced to the session directory by default now, so climbing out
# with `..` is refused. Opting out (CLIQUE_FENCE_READS=0) restores the old
# trusted-local behaviour where the path simply resolves.
climbed = files.inspect(str(tmp / "sub"), "../note.md")
check(
".. outside the session dir is refused by the default fence",
climbed["kind"] == "missing",
climbed,
)
outside_abs = files.inspect(str(tmp), "/etc/hostname")
check(
"an absolute path outside the session dir is refused",
outside_abs["kind"] == "missing" and not outside_abs.get("entries"),
outside_abs,
)
# Credential and key material is refused even inside the session dir, and by
# its whole family / key extensions — .env.local and a .pem, not just .env.
for secret in (".env", ".env.local", "server.pem", ".bw-session", ".npmrc"):
(tmp / secret).write_text("SECRET=1\n", encoding="utf-8")
check(
f"a credential file is refused: {secret}",
files.inspect(str(tmp), secret)["kind"] == "missing",
files.inspect(str(tmp), secret),
)
# Realpath containment: a symlink inside the dir that resolves outside it is
# refused, because resolution happens before the containment check.
outside = Path(tempfile.mkdtemp(prefix="clique-outside-"))
(outside / "secret.txt").write_text("out\n", encoding="utf-8")
try:
(tmp / "escape").symlink_to(outside / "secret.txt")
check(
"a symlink resolving outside the session dir is refused",
files.inspect(str(tmp), "escape")["kind"] == "missing",
files.inspect(str(tmp), "escape"),
)
except OSError:
pass # a filesystem without symlinks — skip rather than fail
finally:
shutil.rmtree(outside, ignore_errors=True)
_saved_fence = files._FENCE
try:
files._FENCE = False
opened = files.inspect(str(tmp / "sub"), "../note.md")
check(
".. resolves again when the fence is off",
opened["kind"] == "text" and opened["text"] == "hello\n",
opened,
)
# ...but the credential block is not the fence: it holds regardless.
check(
"a credential is still refused with the fence off",
files.inspect(str(tmp), ".env")["kind"] == "missing",
)
finally:
files._FENCE = _saved_fence
big = files.inspect(str(tmp), "big.txt")
check(
"caps the text it will dump in a browser",
big["truncated"] and len(big["text"]) == files.TEXT_CAP,
big["size"],
)
# files.write: the same gate as a read, plus overwrite-existing-only.
(tmp / "note.md").chmod(0o640)
n = files.write(str(tmp), "note.md", "edited\n")
check(
"saves edited text back to an existing file",
n == 7 and (tmp / "note.md").read_text() == "edited\n",
)
check("preserves the file's mode on save", ((tmp / "note.md").stat().st_mode & 0o777) == 0o640)
def _refused(_reason, *args):
try:
files.write(*args)
except (ValueError, OSError):
return True
return False
check("save refuses a credential file", _refused("cred", str(tmp), ".env", "x"))
check(
"save refuses outside the session dir",
_refused("escape", str(tmp / "sub"), "../note.md", "x"),
)
check("save will not create a new file", _refused("new", str(tmp), "brand-new.md", "x"))
check("save refuses a directory", _refused("dir", str(tmp), "sub", "x"))
outside2 = Path(tempfile.mkdtemp(prefix="clique-outside2-"))
(outside2 / "keep.txt").write_text("out\n", encoding="utf-8")
try:
(tmp / "esc").symlink_to(outside2 / "keep.txt")
check(
"save refuses a symlink that resolves out of the dir",
_refused("symesc", str(tmp), "esc", "hacked"),
)
check("and the symlink target is untouched", (outside2 / "keep.txt").read_text() == "out\n")
except OSError:
pass # no symlinks on this fs — skip
finally:
shutil.rmtree(outside2, ignore_errors=True)
shutil.rmtree(tmp, ignore_errors=True)
print("tokens")
from clique.tokens import TokenStore
tok_dir = Path(tempfile.mkdtemp(prefix="clique-tokens-"))
store = TokenStore(tok_dir / "tokens.json")
op, _ = store.create("an-operator-token", ["read", "write"])
bound, _ = store.create("hook:abc", ["attention"], session="sess-1")
check("a bound token records its session", bound.session == "sess-1")
check("an operator token has no session", op.session == "")
check(
"per-session tokens are hidden from the operator listing",
[t["id"] for t in store.listing()] == [op.id],
)
check(
"revoke_session drops the bound token and reports it",
store.revoke_session("sess-1") == 1 and all(t.session != "sess-1" for t in store.tokens),
)
check(
"revoke_session on an unknown session drops nothing",
store.revoke_session("nope") == 0 and len(store.tokens) == 1,
)
shutil.rmtree(tok_dir, ignore_errors=True)
print("pairing")
from clique.auth import LOGIN_PAGE, login_page
from clique.pairing import Desk, grouped
blank = login_page()
check(
"the password page is byte-for-byte what it was",
blank == LOGIN_PAGE.replace("__ERROR__", "").replace("__NONCE__", "").encode(),
)
errored = login_page("Wrong password.", "nonce-1")
check(
"an error still only fills the existing slots",
errored
== LOGIN_PAGE.replace("__ERROR__", '
Wrong password.
')
.replace("__NONCE__", "nonce-1")
.encode(),
)
paired = login_page(pair="K7PM-3XQF", nonce="abc").decode()
check(
"a pair page posts the code, not a password",
'name="pair"' in paired
and 'value="K7PM-3XQF"' in paired
and 'name="password"' not in paired,
)
check(
"the button stays a real visible control",
"Sign in on this device" in paired and "display:none" not in paired,
paired[paired.find("