Skip to content

Commit 1ef0375

Browse files
committed
chore(scripts): add verify-install-simulation.sh pre-release gate (#1490)
Pre-release regression script that simulates a fresh user receiving cache 5.6.x and starting Claude Code for the first time. Runs the real _install_statusline against the in-tree hooks/ source in an isolated tmpdir, then executes the installed script as a subprocess and asserts the output is the full status line — not the bare '◕‿◕ CodingBuddy' fallback face. Why this complements pytest: - The pytest E2E gate runs inside the test runner's process and fixtures. This shell script exercises the exact code path a user hits, including filesystem layout, chmod, and a fresh subprocess. - It can be run by maintainers locally before pushing release branches without needing to set up a Python venv with pytest. - It is invoked from canary CI in the next commit so PRs cannot merge with a broken installer. Exit codes: 0 — full status line rendered, all assertions passed 1 — fallback face detected or required tokens missing 2 — install crashed before render Local verification at commit time: $ bash packages/claude-code-plugin/scripts/verify-install-simulation.sh [verify-install-simulation] PASS: full status line rendered exit=0 Output: '◕‿◕ CB v5.6.1 | Ready 🟢 | $0.42🔥$0.21/m | 2m | 0% | Opus 4.6' Refs #1490
1 parent 5b43401 commit 1ef0375

1 file changed

Lines changed: 142 additions & 0 deletions

File tree

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
#!/usr/bin/env bash
2+
# verify-install-simulation.sh — pre-release regression gate for #1490
3+
#
4+
# Simulates a fresh user receiving cache 5.6.x and starting Claude Code
5+
# for the first time. Runs _install_statusline against the real plugin
6+
# hooks/ directory in an isolated tmpdir, then executes the installed
7+
# script as a real subprocess and asserts the output is the full status
8+
# line — not the bare '◕‿◕ CodingBuddy' fallback face.
9+
#
10+
# This script complements pytest unit/E2E tests by exercising the
11+
# exact code path a user hits (including the file system, chmod, and
12+
# subprocess invocation). Run it manually before pushing release
13+
# branches and from CI on every PR.
14+
#
15+
# Exit codes:
16+
# 0 — full status line rendered, all assertions passed
17+
# 1 — fallback face detected or required tokens missing
18+
# 2 — install crashed before render
19+
20+
set -euo pipefail
21+
22+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
23+
PLUGIN_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
24+
HOOKS_DIR="$PLUGIN_ROOT/hooks"
25+
26+
if [ ! -f "$HOOKS_DIR/codingbuddy-hud.py" ]; then
27+
echo "[verify-install-simulation] FAIL: HUD source not found at $HOOKS_DIR/codingbuddy-hud.py" >&2
28+
exit 2
29+
fi
30+
31+
TMPHOME="$(mktemp -d)"
32+
trap 'rm -rf "$TMPHOME"' EXIT
33+
34+
echo "[verify-install-simulation] simulating user install in $TMPHOME"
35+
36+
PLUGIN_ROOT="$PLUGIN_ROOT" HOOKS_DIR="$HOOKS_DIR" TMPHOME="$TMPHOME" \
37+
python3 - <<'PYEOF'
38+
import json
39+
import os
40+
import subprocess
41+
import sys
42+
from pathlib import Path
43+
44+
plugin_root = os.environ["PLUGIN_ROOT"]
45+
hooks_dir = os.environ["HOOKS_DIR"]
46+
tmphome = os.environ["TMPHOME"]
47+
48+
# Make session-start.py importable (hyphenated filename)
49+
sys.path.insert(0, hooks_dir)
50+
import importlib.util as importutil
51+
spec = importutil.spec_from_file_location(
52+
"session_start", os.path.join(hooks_dir, "session-start.py")
53+
)
54+
session_start = importutil.module_from_spec(spec)
55+
spec.loader.exec_module(session_start)
56+
57+
# Force the installer to use the in-tree source rather than any cached
58+
# copy on the developer's machine. This guarantees the script under
59+
# test is exactly what's about to ship.
60+
hud_source = Path(hooks_dir) / "codingbuddy-hud.py"
61+
session_start._find_hud_source = lambda: hud_source
62+
63+
home = Path(tmphome)
64+
settings_file = home / ".claude" / "settings.json"
65+
settings_file.parent.mkdir(parents=True, exist_ok=True)
66+
settings_file.write_text("{}")
67+
68+
# Run the installer the same way session-start hook would
69+
try:
70+
session_start._install_statusline(home, settings_file)
71+
except Exception as exc:
72+
print(f"[verify-install-simulation] FAIL: installer crashed: {exc}", file=sys.stderr)
73+
sys.exit(2)
74+
75+
installed_script = home / ".claude" / "hud" / "codingbuddy-hud.py"
76+
installed_lib = home / ".claude" / "hud" / "lib"
77+
78+
if not installed_script.exists():
79+
print("[verify-install-simulation] FAIL: script not installed", file=sys.stderr)
80+
sys.exit(2)
81+
if not installed_lib.is_dir():
82+
print("[verify-install-simulation] FAIL: lib/ not synced", file=sys.stderr)
83+
sys.exit(1)
84+
85+
# Verify the 12 critical modules are present
86+
required = [
87+
"hud_buddy.py", "hud_cache_savings.py", "hud_context_bar.py",
88+
"hud_helpers.py", "hud_layout.py", "hud_rainbow.py",
89+
"hud_rate_limits.py", "hud_session.py", "hud_state.py",
90+
"hud_velocity.py", "hud_version.py", "tiny_actor_presets.py",
91+
]
92+
missing = [m for m in required if not (installed_lib / m).is_file()]
93+
if missing:
94+
print(
95+
f"[verify-install-simulation] FAIL: missing lib modules: {missing}",
96+
file=sys.stderr,
97+
)
98+
sys.exit(1)
99+
100+
# Render via subprocess — exactly how Claude Code invokes the statusLine
101+
payload = json.dumps({
102+
"session_id": "verify-install-simulation",
103+
"model": {"display_name": "Opus 4.6"},
104+
"cost": {"total_cost_usd": 0.42, "total_duration_ms": 120000},
105+
})
106+
result = subprocess.run(
107+
["python3", str(installed_script)],
108+
input=payload,
109+
capture_output=True,
110+
text=True,
111+
timeout=10,
112+
)
113+
out = result.stdout
114+
print(f"[verify-install-simulation] stdout={out!r}")
115+
print(f"[verify-install-simulation] stderr={result.stderr!r}")
116+
117+
if result.returncode != 0:
118+
print(
119+
f"[verify-install-simulation] FAIL: render exited {result.returncode}",
120+
file=sys.stderr,
121+
)
122+
sys.exit(1)
123+
124+
if out.strip() == "\u25d5\u203f\u25d5 CodingBuddy":
125+
print(
126+
"[verify-install-simulation] FAIL: fallback face only — "
127+
"installer did not produce a working HUD (#1490 regression)",
128+
file=sys.stderr,
129+
)
130+
sys.exit(1)
131+
132+
required_tokens = ["CB v", "Opus 4.6", "$0.42"]
133+
for token in required_tokens:
134+
if token not in out:
135+
print(
136+
f"[verify-install-simulation] FAIL: missing token {token!r} in stdout",
137+
file=sys.stderr,
138+
)
139+
sys.exit(1)
140+
141+
print("[verify-install-simulation] PASS: full status line rendered")
142+
PYEOF

0 commit comments

Comments
 (0)