From b8d08f1f4d4256919997ac6eca5f18bbba094a9f Mon Sep 17 00:00:00 2001 From: garrytan Date: Sat, 3 Oct 2026 21:44:06 +0000 Subject: [PATCH] G2/G3/INV-1: skill-start former-bucket notice and visible learnings failure; TMPDIR mktemp in gbrain bins; best-effort reasons --- bin/gstack-brain-enqueue | 2 +- bin/gstack-brain-sync | 6 +++--- bin/gstack-gbrain-mcp-verify | 8 ++++---- bin/gstack-gbrain-source-wireup | 2 +- bin/gstack-session-update | 2 +- bin/gstack-skill-start | 9 ++++++++- 6 files changed, 18 insertions(+), 11 deletions(-) diff --git a/bin/gstack-brain-enqueue b/bin/gstack-brain-enqueue index e0e97fb8b..b2e094e72 100755 --- a/bin/gstack-brain-enqueue +++ b/bin/gstack-brain-enqueue @@ -58,7 +58,7 @@ TS=$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "") # One spool file per record: tmp write + atomic rename. Any failure exits 0 # silently (fire-and-forget contract), cleaning up the tmp file. -mkdir -p "$SPOOL" 2>/dev/null || exit 0 +mkdir -p "$SPOOL" 2>/dev/null || exit 0 # best-effort: enqueue is fire-and-forget; the next sync rescans TMP="$SPOOL/.tmp-$$-$RANDOM" printf '{"file":"%s","ts":"%s"}\n' "$ESC_FILE" "$TS" > "$TMP" 2>/dev/null || { rm -f "$TMP" 2>/dev/null; exit 0; } mv -f "$TMP" "$SPOOL/$(date +%s)-$$-$RANDOM.json" 2>/dev/null || rm -f "$TMP" 2>/dev/null diff --git a/bin/gstack-brain-sync b/bin/gstack-brain-sync index 3c64634c0..332d172aa 100755 --- a/bin/gstack-brain-sync +++ b/bin/gstack-brain-sync @@ -665,9 +665,9 @@ subcmd_once() { fi local paths_file class_file snapshot_file - paths_file=$(mktemp /tmp/brain-sync-paths.XXXXXX) || { rm -rf "$lock_dir" 2>/dev/null; write_status "error" "mktemp failed"; exit 1; } - class_file=$(mktemp /tmp/brain-sync-class.XXXXXX) || { rm -f "$paths_file"; rm -rf "$lock_dir" 2>/dev/null; write_status "error" "mktemp failed"; exit 1; } - snapshot_file=$(mktemp /tmp/brain-sync-snapshot.XXXXXX) || { rm -f "$paths_file" "$class_file"; rm -rf "$lock_dir" 2>/dev/null; write_status "error" "mktemp failed"; exit 1; } + paths_file=$(mktemp "${TMPDIR:-/tmp}/brain-sync-paths.XXXXXX") || { rm -rf "$lock_dir" 2>/dev/null; write_status "error" "mktemp failed"; exit 1; } + class_file=$(mktemp "${TMPDIR:-/tmp}/brain-sync-class.XXXXXX") || { rm -f "$paths_file"; rm -rf "$lock_dir" 2>/dev/null; write_status "error" "mktemp failed"; exit 1; } + snapshot_file=$(mktemp "${TMPDIR:-/tmp}/brain-sync-snapshot.XXXXXX") || { rm -f "$paths_file" "$class_file"; rm -rf "$lock_dir" 2>/dev/null; write_status "error" "mktemp failed"; exit 1; } # Single trap covers all: lock cleanup AND tempfile cleanup. trap 'rm -f "$paths_file" "$class_file" "$snapshot_file" 2>/dev/null; rm -rf "$lock_dir" 2>/dev/null || true' EXIT INT TERM diff --git a/bin/gstack-gbrain-mcp-verify b/bin/gstack-gbrain-mcp-verify index b3459ca8f..011ee6b73 100755 --- a/bin/gstack-gbrain-mcp-verify +++ b/bin/gstack-gbrain-mcp-verify @@ -74,13 +74,13 @@ emit() { INIT_BODY='{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"gstack-mcp-verify","version":"1"}}}' # Capture HTTP code + body in one pass; --max-time 10 caps total wall time. -TMPBODY=$(mktemp -t gstack-mcp-verify.XXXXXX) +TMPBODY=$(mktemp "${TMPDIR:-/tmp}/gstack-mcp-verify.XXXXXX") trap 'rm -f "$TMPBODY"' EXIT # Receipted fail-closed: the payload file is hashed and handed to curl as # the exact wire bytes. A refused receipt never hits the network — it lands # in the NETWORK class below (curl never ran, no HTTP code). -INIT_PAYLOAD=$(mktemp -t gstack-mcp-init.XXXXXX) +INIT_PAYLOAD=$(mktemp "${TMPDIR:-/tmp}/gstack-mcp-init.XXXXXX") printf '%s' "$INIT_BODY" > "$INIT_PAYLOAD" set +e HTTP_CODE=$(_receipted_curl closed gbrain-mcp-verify "$MCP_HOST" mcp-initialize-probe "user-invoked mcp verify" "$INIT_PAYLOAD" \ @@ -156,12 +156,12 @@ fi # mcp__gbrain__sources_add will flip this true and gstack-artifacts-init # will print the one-liner form instead of the clone-then-path form. URL_SUPPORTED=false -TOOLS_BODY_FILE=$(mktemp -t gstack-mcp-tools.XXXXXX) +TOOLS_BODY_FILE=$(mktemp "${TMPDIR:-/tmp}/gstack-mcp-tools.XXXXXX") TOOLS_REQ='{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' # Receipted fail-closed like the initialize probe. A refused receipt skips # the probe (nonzero TOOLS_EXIT) and the field stays false — best-effort. -TOOLS_PAYLOAD=$(mktemp -t gstack-mcp-tools-req.XXXXXX) +TOOLS_PAYLOAD=$(mktemp "${TMPDIR:-/tmp}/gstack-mcp-tools-req.XXXXXX") printf '%s' "$TOOLS_REQ" > "$TOOLS_PAYLOAD" set +e _receipted_curl closed gbrain-mcp-verify "$MCP_HOST" mcp-tools-list-probe "user-invoked mcp verify" "$TOOLS_PAYLOAD" \ diff --git a/bin/gstack-gbrain-source-wireup b/bin/gstack-gbrain-source-wireup index 7b9ace1b6..42fcea288 100755 --- a/bin/gstack-gbrain-source-wireup +++ b/bin/gstack-gbrain-source-wireup @@ -93,7 +93,7 @@ elif [ -f "$GBRAIN_CONFIG" ]; then # Python heredoc reads config.json. On JSON parse failure or any IO error, # we WARN (not silently swallow) so the user knows the URL lock fell back # to gbrain's own loadConfig (which would still read this same file). - _py_err=$(mktemp -t wireup-pyerr 2>/dev/null || mktemp /tmp/wireup-pyerr.XXXXXX) + _py_err=$(mktemp "${TMPDIR:-/tmp}/wireup-pyerr.XXXXXX") _locked_url=$(GBRAIN_CONFIG_PATH="$GBRAIN_CONFIG" python3 -c ' import json, os, sys try: diff --git a/bin/gstack-session-update b/bin/gstack-session-update index e49fc97f4..0fa33bb35 100755 --- a/bin/gstack-session-update +++ b/bin/gstack-session-update @@ -19,7 +19,7 @@ set +e GSTACK_DIR="${GSTACK_DIR:-$HOME/.claude/skills/gstack}" # Hook: source the state-root twin (never spawn gstack-paths); a broken # install exits 0 silently, like every other error here. -. "$(cd "$(dirname "$0")" && pwd)/gstack-state-root.sh" 2>/dev/null || exit 0 +. "$(cd "$(dirname "$0")" && pwd)/gstack-state-root.sh" 2>/dev/null || exit 0 # best-effort: must never block a session start STATE_DIR="$(gstack_state_root; printf x)"; STATE_DIR="${STATE_DIR%x}" # Egress receipt helpers (_receipted_git): fail-open — an update pull must diff --git a/bin/gstack-skill-start b/bin/gstack-skill-start index f58b811f7..64f5605b3 100755 --- a/bin/gstack-skill-start +++ b/bin/gstack-skill-start @@ -190,12 +190,19 @@ for _PF in $(find "$_GH/analytics" -maxdepth 1 -name '.pending-*' 2>/dev/null); break done eval "$("$_BIN/gstack-slug" 2>/dev/null)" 2>/dev/null || true +for _former in $("$_BIN/gstack-slug" --get FORMER_SLUGS 2>/dev/null || true); do + echo "gstack: earlier data for this project is in projects/$_former (a worktree's old bucket). Merge it: gstack-slug --adopt-legacy --from $_former (or stop this reminder: gstack-slug --adopt-legacy --dismiss $_former)" +done _LEARN_FILE="$_GH/projects/${SLUG:-unknown}/learnings.jsonl" if [ -f "$_LEARN_FILE" ]; then _LEARN_COUNT=$(wc -l < "$_LEARN_FILE" 2>/dev/null | tr -d ' ') echo "LEARNINGS: $_LEARN_COUNT entries loaded" if [ "$_LEARN_COUNT" -gt 5 ] 2>/dev/null; then - "$_BIN/gstack-learnings-search" --limit 3 2>/dev/null | _sanitize || true + _LEARN_ERR=$(mktemp "${TMPDIR:-/tmp}/gstack-learn-err.XXXXXX" 2>/dev/null) || _LEARN_ERR=/dev/null + if ! "$_BIN/gstack-learnings-search" --limit 3 2>"$_LEARN_ERR" | _sanitize; then + echo "LEARNINGS: unavailable ($(head -n 1 "$_LEARN_ERR" 2>/dev/null))" + fi + [ "$_LEARN_ERR" = /dev/null ] || rm -f "$_LEARN_ERR" fi else echo "LEARNINGS: 0"