tools/resume_check.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
"""Verify that a dead resume key cannot leave a tab that does nothing.
A resume key outlives the conversation it points at. Open a session, never
type in it, let the idle reaper stop it, and the CLI has nothing on disk to
come back to: the launch exits inside two seconds and the tab goes quiet with
no pane, no error and nothing to click. One of Justin's sessions had been
doing exactly that for a week when this was found on 2026-09-01.
Three things, against a real panel with a fake CLI that refuses every resume:
1. Starting a stopped session uses the resume form, and says so.
2. When that form dies, the panel notices, drops the key, and starts the
session clean by itself -- no second click.
3. A session stopped inside the grace window stays stopped. The recovery
must never resurrect something a person just shut down.
Its own home, port and tmux socket; nothing touches a panel you are using.
python3 tools/resume_check.py
Exit status is 0 on pass, 1 on fail.
"""
from __future__ import annotations
import json
import os
import shutil
import subprocess
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
PASSWORD = "resume-check" # noqa: S105 — a throwaway panel on loopback
PORT = 3287
BASE = f"http://127.0.0.1:{PORT}"
HOME = Path("/tmp/clique-resume-check-home")
SOCKET = "clique-resume-check"
#: The fake CLI's resume takes REFUSE_AFTER seconds to give up, so a stop
#: issued before then is unambiguously a person stopping a session that was
#: still starting. SETTLE is long enough to cover the refusal, the panel
#: noticing, and the clean start that follows.
REFUSE_AFTER = 3
SETTLE = 8.0
# A CLI whose resume always refuses, the way Claude Code refuses a session id
# with no conversation behind it. `args` carries {id}, which is what makes the
# panel treat our own id as the resume key on the next start -- the exact
# promotion this check is about.
CATALOGUE = """
[cli.faker]
label = "Faker"
command = "faker"
args = ["fresh", "{id}"]
resume = ["resume", "{cli_session_id}"]
color = "#8b8b8b"
[cli.shell]
label = "Shell"
command = "bash"
args = ["-l"]
color = "#8b8b8b"
"""
FAKER = """#!/bin/bash
if [ "$1" = "resume" ]; then
sleep REFUSE_AFTER
echo "No conversation found with session ID: $2"
exit 1
fi
echo "FRESH START $2"
exec sleep 600
"""
def _panel() -> tuple[subprocess.Popen, str]:
shutil.rmtree(HOME, ignore_errors=True)
HOME.mkdir(parents=True)
(HOME / "clis.toml").write_text(CATALOGUE)
faker = HOME / "faker"
faker.write_text(FAKER.replace("REFUSE_AFTER", str(REFUSE_AFTER)))
faker.chmod(0o755)
env = dict(
os.environ,
CLIQUE_HOME=str(HOME),
CLIQUE_TMUX_SOCKET=SOCKET,
PATH=f"{HOME}:{os.environ.get('PATH', '')}",
)
subprocess.run(
[sys.executable, "-m", "clique", "password"],
input=f"{PASSWORD}\n{PASSWORD}\n",
text=True,
env=env,
cwd=str(ROOT),
capture_output=True,
)
mint = subprocess.run(
[sys.executable, "-m", "clique", "token", "create", "admin"],
env=env,
cwd=str(ROOT),
capture_output=True,
text=True,
).stdout
token = next((ln.strip() for ln in mint.splitlines() if ln.strip().startswith("mxp_")), "")
proc = subprocess.Popen(
[
sys.executable,
"-m",
"clique",
"--host",
"127.0.0.1",
"--port",
str(PORT),
"--state",
str(HOME / "state.json"),
],
env=env,
cwd=str(ROOT),
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
for _ in range(80):
try:
urllib.request.urlopen(BASE + "/healthz", timeout=2).read()
return proc, token
except (urllib.error.URLError, OSError):
time.sleep(0.25)
proc.kill()
raise SystemExit(f"the check's own panel never came up on {PORT}")
def main() -> int:
proc, token = _panel()
def api(path, method="GET", body=None):
req = urllib.request.Request(
BASE + path,
data=(json.dumps(body).encode() if body is not None else None),
method=method,
headers={"Authorization": "Bearer " + token, "Content-Type": "application/json"},
)
try:
with urllib.request.urlopen(req, timeout=15) as r:
return json.loads(r.read() or "{}")
except urllib.error.HTTPError as err:
raise SystemExit(f"{method} {path} -> {err.code} {err.read()[:200]!r}") from err
def mux(sid):
return "sm-" + sid.replace("-", "")[:8]
def alive(sid):
return (
subprocess.run(
["tmux", "-L", SOCKET, "has-session", "-t", "=" + mux(sid)],
capture_output=True,
).returncode
== 0
)
def pane(sid):
return subprocess.run(
# No "=" prefix here: exact-match session syntax resolves for
# has-session but hands capture-pane a target it answers with
# nothing at all, silently.
["tmux", "-L", SOCKET, "capture-pane", "-p", "-t", mux(sid)],
capture_output=True,
text=True,
).stdout
def record(sid):
# Straight off disk: /api/state is the sidebar's view and deliberately
# does not carry the resume key, which is the field under test.
rows = json.loads((HOME / "state.json").read_text())["sessions"]
return next(r for r in rows if r["id"] == sid)
res: dict[str, object] = {}
try:
a = api("/api/sessions", "POST", {"cli": "faker", "cwd": "/tmp", "name": "recovers"})["id"]
time.sleep(1.5)
res["first_launch_is_fresh"] = "FRESH START" in pane(a)
api(f"/api/sessions/{a}/kill", "POST")
started = api(f"/api/sessions/{a}/start", "POST")
res["start_reports_a_resume"] = started.get("resumed") is True
time.sleep(SETTLE)
res["recovered_by_itself"] = alive(a)
res["recovered_pane_is_fresh"] = "FRESH START" in pane(a)
res["dead_key_was_dropped"] = not record(a).get("cli_session_id")
# And the other direction: stopping inside the grace window has to win.
b = api("/api/sessions", "POST", {"cli": "faker", "cwd": "/tmp", "name": "stays down"})[
"id"
]
time.sleep(1.5)
api(f"/api/sessions/{b}/kill", "POST")
api(f"/api/sessions/{b}/start", "POST")
api(f"/api/sessions/{b}/kill", "POST") # while the resume is still trying
time.sleep(SETTLE)
res["a_stopped_session_is_left_alone"] = not alive(b)
finally:
proc.terminate()
subprocess.run(
["tmux", "-L", SOCKET, "kill-server"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
ok = all(bool(v) for v in res.values())
for key, value in res.items():
print(f" {'ok ' if value else 'FAIL'} {key}: {value}")
print("resume_check:", "PASS" if ok else "FAIL")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())