fix: housekeeping sweep — telemetry integrity, persistent opt-out, context-bill accuracy, setup hang, dev-server discovery, model resolution (#2136 + v1.63 polish)

Seven small fixes, one theme (claims matching code):
- telemetry-sync strips local-only fields with jq del() (structural) instead
  of quote-fragile sed regexes; unparseable lines are dropped, never
  forwarded unstripped. Sed survives only as a jq-less fallback.
- telemetry-log rejects non-integer durations BEFORE the range caps, whose
  test(1) comparisons silently no-op on non-numerics — a malformed duration
  spliced raw text into the JSONL stream.
- browse's local telemetry honors the persistent tier (config.yaml
  telemetry: off), not just the preamble's env hint — direct $B use and
  embedders now respect the opt-out.
- gstack-context-bill --exact sees GSTACK_-promoted keys inside Conductor
  (conductor-env-shim wired at the CLI entry), and the TOTAL line no longer
  double-counts every nested skill through the root skill's walk (v1.63
  deferred polish; the telemetry-sync HTTP-status outcome deferred alongside
  it turned out already shipped).
- setup's Chromium probe is deadline-bounded (90s, background + poll-kill —
  macOS has no GNU timeout) and prefers Node for the launch probe everywhere
  (the bun --eval hang family behind #2136); the install is single-flight
  behind a lock dir with an actionable stale-lock message. Probe verified
  live on this Mac.
- the review resolver's dev-server check reads CLAUDE.md and the plan file
  before falling back to an expanded port probe, and says how to make
  itself smarter next time.
- eval/harness model IDs resolve through lib/eval-model.ts
  (GSTACK_EVAL_MODEL[_KIND] env overrides, per-kind defaults, tested) at the
  SDK-capture and PTY-warmup sites; the bash-embedded distill snippet
  mirrors the resolution inline.
- memory-ingest's silent-zero shape (staged>0, imported+unchanged==0,
  errors==0) warns even under --quiet — a run that indexes nothing must
  never look healthy again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-08-14 13:26:58 -07:00
co-authored by Claude Fable 5
parent 56d83c684c
commit b9cd094981
13 changed files with 210 additions and 36 deletions
+51 -15
View File
@@ -249,20 +249,45 @@ if [ "$INSTALL_CODEX" -eq 1 ]; then
migrate_direct_codex_install "$SOURCE_GSTACK_DIR" "$CODEX_GSTACK"
fi
# Deadline-bounded wait for a background probe. macOS ships no GNU timeout;
# poll the PID and SIGKILL past the deadline. Returns the probe's exit code,
# or 124 on timeout.
_wait_with_deadline() {
local pid="$1" deadline_s="$2" waited=0
while kill -0 "$pid" 2>/dev/null; do
if [ "$waited" -ge "$deadline_s" ]; then
kill -9 "$pid" 2>/dev/null || true
wait "$pid" 2>/dev/null || true
return 124
fi
sleep 1
waited=$((waited + 1))
done
wait "$pid"
}
ensure_playwright_browser() {
if [ "$IS_WINDOWS" -eq 1 ]; then
# On Windows, Bun can't launch Chromium due to broken pipe handling
# (oven-sh/bun#4253). Use Node.js to verify Chromium works instead.
(
cd "$SOURCE_GSTACK_DIR"
node -e "const { chromium } = require('playwright'); (async () => { const b = await chromium.launch(); await b.close(); })()" 2>/dev/null
)
# #2136: fresh installs hung forever at this probe (macOS arm64) and
# re-runs stacked stuck process trees, so skills never got linked. Two
# fixes: prefer Node for the launch probe everywhere it exists (the
# bun --eval launch is the same pipe-bug family already worked around on
# Windows), and bound the probe with a 90s deadline — a wedged probe now
# reports failure (which routes to the install path) instead of hanging
# setup.
local probe_cmd
if command -v node >/dev/null 2>&1; then
probe_cmd='node -e "const { chromium } = require((process.cwd()) + \"/node_modules/playwright\"); (async () => { const b = await chromium.launch(); await b.close(); })().then(() => process.exit(0), () => process.exit(1))"'
elif [ "$IS_WINDOWS" -eq 1 ]; then
echo "gstack setup failed: Node.js is required on Windows" >&2
return 1
else
(
cd "$SOURCE_GSTACK_DIR"
bun --eval 'import { chromium } from "playwright"; const browser = await chromium.launch(); await browser.close();'
) >/dev/null 2>&1
probe_cmd="bun --eval 'import { chromium } from \"playwright\"; const browser = await chromium.launch(); await browser.close();'"
fi
(
cd "$SOURCE_GSTACK_DIR"
eval "$probe_cmd"
) >/dev/null 2>&1 &
_wait_with_deadline $! 90
}
# Ensure a color-emoji font is installed (Linux only).
@@ -478,10 +503,21 @@ fi
# 2. Ensure Playwright's Chromium is available
if ! ensure_playwright_browser; then
echo "Installing Playwright Chromium..."
(
cd "$SOURCE_GSTACK_DIR"
bunx playwright install chromium
)
_PW_LOCK="${TMPDIR:-/tmp}/gstack-playwright-install.lock"
if mkdir "$_PW_LOCK" 2>/dev/null; then
trap 'rmdir "$_PW_LOCK" 2>/dev/null || true' EXIT
(
cd "$SOURCE_GSTACK_DIR"
bunx playwright install chromium
)
rmdir "$_PW_LOCK" 2>/dev/null || true
trap - EXIT
else
echo " another gstack setup is already installing Chromium (lock: $_PW_LOCK)." >&2
echo " Wait for it to finish, then re-run ./setup. If no other setup is running," >&2
echo " remove the stale lock: rmdir \"$_PW_LOCK\"" >&2
exit 1
fi
if [ "$IS_WINDOWS" -eq 1 ]; then
# On Windows, Node.js launches Chromium (not Bun — see oven-sh/bun#4253).