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
+1
View File
@@ -2,6 +2,7 @@
// gstack-context-bill — token bill-of-materials for an installed skills tree.
// All behavior lives in lib/context-bill.ts; this is the CLI shim.
import '../lib/conductor-env-shim'; // --exact needs GSTACK_ANTHROPIC_API_KEY promotion inside Conductor
import { contextBillMain } from '../lib/context-bill';
process.exit(await contextBillMain(process.argv.slice(2)));
+6 -1
View File
@@ -197,8 +197,13 @@ RESULT=$(EVENTS_JSON="$EVENTS_JSON" DISTILL_PROMPT="$DISTILL_PROMPT" \
const INPUT_PER_TOKEN = 1e-6;
const OUTPUT_PER_TOKEN = 5e-6;
// Host-neutral model resolution (mirrors lib/eval-model.ts — this inline
// bun -e script cannot import repo-relative libs from an arbitrary cwd).
const distillModel = process.env.GSTACK_EVAL_MODEL_DISTILL
|| process.env.GSTACK_EVAL_MODEL
|| "claude-haiku-4-5-20251001";
const resp = await client.messages.create({
model: "claude-haiku-4-5-20251001",
model: distillModel,
max_tokens: 4096,
messages: [{ role: "user", content: prompt }],
});
+12
View File
@@ -1884,6 +1884,18 @@ async function ingestPass(args: CliArgs): Promise<BulkResult> {
: ""),
);
}
// Silent-zero pathology detector (#2144's other half): pages were staged
// but NOTHING imported or skipped-as-unchanged. That shape hid the dead
// ingest for months — it must be loud even under --quiet, because a run
// that indexes nothing is otherwise indistinguishable from a healthy one.
const importedCount = (importJson.imported ?? 0) + (importJson.skipped ?? 0);
if (prep.prepared.length > 0 && importedCount === 0 && (importJson.errors ?? 0) === 0) {
console.error(
`[memory-ingest] WARNING: ${prep.prepared.length} page(s) staged but gbrain collected ZERO ` +
`(no imports, no unchanged-skips, no errors). This is the #2144 silent-zero shape — ` +
`check gbrain's import.collect_files log line and your gbrain version.`,
);
}
} finally {
// #1802 D1: in remote-http mode `stagingDir` is the PERSISTENT transcript
// dir (makePersistentTranscriptDir, under ~/.gstack/transcripts/) that
+7
View File
@@ -210,6 +210,13 @@ fi
STEP_FIELD="null"
[ -n "$FAILED_STEP" ] && STEP_FIELD="\"$(json_safe "$FAILED_STEP")\""
# Integrity first: a non-numeric duration would splice raw text into the
# JSON line ("duration_s":%s) and corrupt the whole JSONL stream — the range
# caps below silently no-op on non-integers because both test(1) comparisons
# fail. Reject anything that isn't a plain integer.
case "$DURATION" in
''|*[!0-9]*) DURATION="" ;;
esac
# Cap unreasonable durations
if [ -n "$DURATION" ] && [ "$DURATION" -gt 86400 ] 2>/dev/null; then
DURATION="" # null if > 24h
+21 -9
View File
@@ -81,15 +81,27 @@ while IFS= read -r LINE; do
[ -z "$LINE" ] && continue
echo "$LINE" | grep -q '^{' || continue
# Strip local-only fields (keep v, ts, sessions as-is for edge function)
CLEAN="$(echo "$LINE" | sed \
-e 's/,"_repo_slug":"[^"]*"//g' \
-e 's/,"_branch":"[^"]*"//g' \
-e 's/,"repo":"[^"]*"//g')"
# If anonymous tier, strip installation_id
if [ "$TIER" = "anonymous" ]; then
CLEAN="$(echo "$CLEAN" | sed 's/,"installation_id":"[^"]*"//g; s/,"installation_id":null//g')"
# Strip local-only fields (keep v, ts, sessions as-is for edge function).
# jq del() is structural — a value containing an escaped quote (repo names,
# branch names) can't smuggle the field past a regex or corrupt the strip.
# The sed path stays only as a jq-less fallback.
if command -v jq >/dev/null 2>&1; then
if [ "$TIER" = "anonymous" ]; then
CLEAN="$(printf '%s' "$LINE" | jq -c 'del(._repo_slug, ._branch, .repo, .installation_id)' 2>/dev/null)" || CLEAN=""
else
CLEAN="$(printf '%s' "$LINE" | jq -c 'del(._repo_slug, ._branch, .repo)' 2>/dev/null)" || CLEAN=""
fi
# A line jq can't parse is malformed telemetry — drop it rather than
# forwarding bytes the strip never touched.
[ -z "$CLEAN" ] && continue
else
CLEAN="$(echo "$LINE" | sed \
-e 's/,"_repo_slug":"[^"]*"//g' \
-e 's/,"_branch":"[^"]*"//g' \
-e 's/,"repo":"[^"]*"//g')"
if [ "$TIER" = "anonymous" ]; then
CLEAN="$(echo "$CLEAN" | sed 's/,"installation_id":"[^"]*"//g; s/,"installation_id":null//g')"
fi
fi
if [ "$FIRST" = "true" ]; then