diff --git a/SKILL.md b/SKILL.md index 0903d36e..2f588cc0 100644 --- a/SKILL.md +++ b/SKILL.md @@ -39,9 +39,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -167,7 +165,7 @@ interrupted). Run this bash: ```bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/bin/gstack-telemetry-log b/bin/gstack-telemetry-log index d8ea84d6..edcbdbab 100755 --- a/bin/gstack-telemetry-log +++ b/bin/gstack-telemetry-log @@ -21,7 +21,7 @@ GSTACK_DIR="${GSTACK_DIR:-$(cd "$(dirname "$0")/.." && pwd)}" STATE_DIR="${GSTACK_STATE_DIR:-$HOME/.gstack}" ANALYTICS_DIR="$STATE_DIR/analytics" JSONL_FILE="$ANALYTICS_DIR/skill-usage.jsonl" -PENDING_FILE="$ANALYTICS_DIR/.pending" +PENDING_DIR="$ANALYTICS_DIR" # .pending-* files live here CONFIG_CMD="$GSTACK_DIR/bin/gstack-config" VERSION_FILE="$GSTACK_DIR/VERSION" @@ -58,16 +58,23 @@ case "$TIER" in esac if [ "$TIER" = "off" ]; then - # Still clear any pending marker even if telemetry is off - rm -f "$PENDING_FILE" 2>/dev/null || true + # Still clear pending markers for this session even if telemetry is off + [ -n "$SESSION_ID" ] && rm -f "$PENDING_DIR/.pending-$SESSION_ID" 2>/dev/null || true exit 0 fi -# ─── Finalize stale .pending marker ───────────────────────── -if [ -f "$PENDING_FILE" ]; then - # .pending contains a JSON fragment: {"skill":"X","ts":"Y","session_id":"Z","gstack_version":"V"} - PENDING_DATA="$(cat "$PENDING_FILE" 2>/dev/null || true)" - rm -f "$PENDING_FILE" 2>/dev/null || true +# ─── Finalize stale .pending markers ──────────────────────── +# Each session gets its own .pending-$SESSION_ID file to avoid races +# between concurrent sessions. Finalize any that don't match our session. +for PFILE in "$PENDING_DIR"/.pending-*; do + [ -f "$PFILE" ] || continue + # Skip our own session's marker (it's still in-flight) + PFILE_BASE="$(basename "$PFILE")" + PFILE_SID="${PFILE_BASE#.pending-}" + [ "$PFILE_SID" = "$SESSION_ID" ] && continue + + PENDING_DATA="$(cat "$PFILE" 2>/dev/null || true)" + rm -f "$PFILE" 2>/dev/null || true if [ -n "$PENDING_DATA" ]; then # Extract fields from pending marker using grep -o + awk P_SKILL="$(echo "$PENDING_DATA" | grep -o '"skill":"[^"]*"' | head -1 | awk -F'"' '{print $4}')" @@ -82,7 +89,10 @@ if [ -f "$PENDING_FILE" ]; then printf '{"v":1,"ts":"%s","event_type":"skill_run","skill":"%s","session_id":"%s","gstack_version":"%s","os":"%s","arch":"%s","duration_s":null,"outcome":"unknown","error_class":null,"used_browse":false,"sessions":1}\n' \ "$P_TS" "$P_SKILL" "$P_SID" "$P_VER" "$P_OS" "$P_ARCH" >> "$JSONL_FILE" 2>/dev/null || true fi -fi +done + +# Clear our own session's pending marker (we're about to log the real event) +[ -n "$SESSION_ID" ] && rm -f "$PENDING_DIR/.pending-$SESSION_ID" 2>/dev/null || true # ─── Collect metadata ──────────────────────────────────────── TS="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -u +%Y-%m-%dT%H:%M:%S 2>/dev/null || echo "")" diff --git a/browse/SKILL.md b/browse/SKILL.md index 3c61abef..306b2fe6 100644 --- a/browse/SKILL.md +++ b/browse/SKILL.md @@ -40,9 +40,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -168,7 +166,7 @@ interrupted). Run this bash: ```bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/design-consultation/SKILL.md b/design-consultation/SKILL.md index aa21fba4..792ad553 100644 --- a/design-consultation/SKILL.md +++ b/design-consultation/SKILL.md @@ -43,9 +43,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -171,7 +169,7 @@ interrupted). Run this bash: ```bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/design-review/SKILL.md b/design-review/SKILL.md index 52ea3773..a86663d1 100644 --- a/design-review/SKILL.md +++ b/design-review/SKILL.md @@ -43,9 +43,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -171,7 +169,7 @@ interrupted). Run this bash: ```bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/document-release/SKILL.md b/document-release/SKILL.md index 12f1d239..e252c86b 100644 --- a/document-release/SKILL.md +++ b/document-release/SKILL.md @@ -41,9 +41,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -169,7 +167,7 @@ interrupted). Run this bash: ```bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/plan-ceo-review/SKILL.md b/plan-ceo-review/SKILL.md index 9b749407..b0aebbb5 100644 --- a/plan-ceo-review/SKILL.md +++ b/plan-ceo-review/SKILL.md @@ -41,9 +41,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -169,7 +167,7 @@ interrupted). Run this bash: ```bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/plan-design-review/SKILL.md b/plan-design-review/SKILL.md index 8a46b277..00d34731 100644 --- a/plan-design-review/SKILL.md +++ b/plan-design-review/SKILL.md @@ -41,9 +41,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -169,7 +167,7 @@ interrupted). Run this bash: ```bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/plan-eng-review/SKILL.md b/plan-eng-review/SKILL.md index b10c2b56..3eb94197 100644 --- a/plan-eng-review/SKILL.md +++ b/plan-eng-review/SKILL.md @@ -40,9 +40,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -168,7 +166,7 @@ interrupted). Run this bash: ```bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/qa-only/SKILL.md b/qa-only/SKILL.md index 628eef03..5cb39530 100644 --- a/qa-only/SKILL.md +++ b/qa-only/SKILL.md @@ -38,9 +38,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -166,7 +164,7 @@ interrupted). Run this bash: ```bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/qa/SKILL.md b/qa/SKILL.md index 21405b3b..051f00d9 100644 --- a/qa/SKILL.md +++ b/qa/SKILL.md @@ -44,9 +44,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -172,7 +170,7 @@ interrupted). Run this bash: ```bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/retro/SKILL.md b/retro/SKILL.md index fd26249b..8f8a7957 100644 --- a/retro/SKILL.md +++ b/retro/SKILL.md @@ -39,9 +39,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -167,7 +165,7 @@ interrupted). Run this bash: ```bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/review/SKILL.md b/review/SKILL.md index 0fae854e..e7f0b6b4 100644 --- a/review/SKILL.md +++ b/review/SKILL.md @@ -40,9 +40,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -168,7 +166,7 @@ interrupted). Run this bash: ```bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/scripts/gen-skill-docs.ts b/scripts/gen-skill-docs.ts index 6055a890..5024c9e8 100644 --- a/scripts/gen-skill-docs.ts +++ b/scripts/gen-skill-docs.ts @@ -118,9 +118,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: \${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done \`\`\` If \`PROACTIVE\` is \`"false"\`, do not proactively suggest gstack skills — only invoke @@ -246,7 +244,7 @@ interrupted). Run this bash: \`\`\`bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \\ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \\ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/setup-browser-cookies/SKILL.md b/setup-browser-cookies/SKILL.md index bd497f99..9f54786d 100644 --- a/setup-browser-cookies/SKILL.md +++ b/setup-browser-cookies/SKILL.md @@ -37,9 +37,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -165,7 +163,7 @@ interrupted). Run this bash: ```bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/ship/SKILL.md b/ship/SKILL.md index dc18ef84..b9c12887 100644 --- a/ship/SKILL.md +++ b/ship/SKILL.md @@ -39,9 +39,7 @@ _SESSION_ID="$$-$(date +%s)" echo "TELEMETRY: ${_TEL:-off}" echo "TEL_PROMPTED: $_TEL_PROMPTED" mkdir -p ~/.gstack/analytics -if [ -f ~/.gstack/analytics/.pending ]; then - ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown 2>/dev/null || true -fi +for _PF in ~/.gstack/analytics/.pending-* 2>/dev/null; do [ -f "$_PF" ] && ~/.claude/skills/gstack/bin/gstack-telemetry-log --event-type skill_run --skill _pending_finalize --outcome unknown --session-id "$_SESSION_ID" 2>/dev/null || true; break; done ``` If `PROACTIVE` is `"false"`, do not proactively suggest gstack skills — only invoke @@ -167,7 +165,7 @@ interrupted). Run this bash: ```bash _TEL_END=$(date +%s) _TEL_DUR=$(( _TEL_END - _TEL_START )) -rm -f ~/.gstack/analytics/.pending 2>/dev/null || true +rm -f ~/.gstack/analytics/.pending-"$_SESSION_ID" 2>/dev/null || true ~/.claude/skills/gstack/bin/gstack-telemetry-log \ --skill "SKILL_NAME" --duration "$_TEL_DUR" --outcome "OUTCOME" \ --used-browse "USED_BROWSE" --session-id "$_SESSION_ID" 2>/dev/null & diff --git a/supabase/migrations/001_telemetry.sql b/supabase/migrations/001_telemetry.sql index b41275f1..2febf4ed 100644 --- a/supabase/migrations/001_telemetry.sql +++ b/supabase/migrations/001_telemetry.sql @@ -60,14 +60,15 @@ CREATE VIEW crash_clusters AS SELECT error_class, gstack_version, - COUNT(*) as count, - COUNT(DISTINCT installation_id) as unique_users, + COUNT(*) as total_occurrences, + COUNT(DISTINCT installation_id) as identified_users, -- community tier only + COUNT(*) - COUNT(installation_id) as anonymous_occurrences, -- events without installation_id MIN(event_timestamp) as first_seen, MAX(event_timestamp) as last_seen FROM telemetry_events WHERE outcome = 'error' AND error_class IS NOT NULL GROUP BY error_class, gstack_version -ORDER BY count DESC; +ORDER BY total_occurrences DESC; -- Skill sequence co-occurrence view CREATE VIEW skill_sequences AS diff --git a/test/telemetry.test.ts b/test/telemetry.test.ts index 8fadfdd4..ec516b83 100644 --- a/test/telemetry.test.ts +++ b/test/telemetry.test.ts @@ -139,18 +139,18 @@ describe('gstack-telemetry-log', () => { }); describe('.pending marker', () => { - test('finalizes stale .pending as outcome:unknown', () => { + test('finalizes stale .pending from another session as outcome:unknown', () => { setConfig('telemetry', 'anonymous'); - // Write a fake .pending marker + // Write a fake .pending marker from a different session const analyticsDir = path.join(tmpDir, 'analytics'); fs.mkdirSync(analyticsDir, { recursive: true }); fs.writeFileSync( - path.join(analyticsDir, '.pending'), + path.join(analyticsDir, '.pending-old-123'), '{"skill":"old-skill","ts":"2026-03-18T00:00:00Z","session_id":"old-123","gstack_version":"0.6.4"}' ); - // Run telemetry-log — should finalize the pending marker first + // Run telemetry-log with a DIFFERENT session — should finalize the old pending marker run(`${BIN}/gstack-telemetry-log --skill qa --duration 50 --outcome success --session-id new-456`); const events = parseJsonl(); @@ -166,26 +166,43 @@ describe('.pending marker', () => { expect(events[1].outcome).toBe('success'); }); - test('.pending file is removed after finalization', () => { + test('.pending-SESSION file is removed after finalization', () => { setConfig('telemetry', 'anonymous'); const analyticsDir = path.join(tmpDir, 'analytics'); fs.mkdirSync(analyticsDir, { recursive: true }); - const pendingPath = path.join(analyticsDir, '.pending'); - fs.writeFileSync(pendingPath, '{"skill":"stale","ts":"2026-03-18T00:00:00Z","session_id":"s","gstack_version":"v"}'); + const pendingPath = path.join(analyticsDir, '.pending-stale-session'); + fs.writeFileSync(pendingPath, '{"skill":"stale","ts":"2026-03-18T00:00:00Z","session_id":"stale-session","gstack_version":"v"}'); run(`${BIN}/gstack-telemetry-log --skill qa --duration 50 --outcome success --session-id new-456`); expect(fs.existsSync(pendingPath)).toBe(false); }); - test('tier=off still clears .pending', () => { + test('does not finalize own session pending marker', () => { + setConfig('telemetry', 'anonymous'); + + const analyticsDir = path.join(tmpDir, 'analytics'); + fs.mkdirSync(analyticsDir, { recursive: true }); + // Create pending for same session ID we'll use + const pendingPath = path.join(analyticsDir, '.pending-same-session'); + fs.writeFileSync(pendingPath, '{"skill":"in-flight","ts":"2026-03-18T00:00:00Z","session_id":"same-session","gstack_version":"v"}'); + + run(`${BIN}/gstack-telemetry-log --skill qa --duration 50 --outcome success --session-id same-session`); + + // Should only have 1 event (the new one), not finalize own pending + const events = parseJsonl(); + expect(events).toHaveLength(1); + expect(events[0].skill).toBe('qa'); + }); + + test('tier=off still clears own session pending', () => { setConfig('telemetry', 'off'); const analyticsDir = path.join(tmpDir, 'analytics'); fs.mkdirSync(analyticsDir, { recursive: true }); - const pendingPath = path.join(analyticsDir, '.pending'); - fs.writeFileSync(pendingPath, '{"skill":"stale","ts":"2026-03-18T00:00:00Z","session_id":"s","gstack_version":"v"}'); + const pendingPath = path.join(analyticsDir, '.pending-off-123'); + fs.writeFileSync(pendingPath, '{"skill":"stale","ts":"2026-03-18T00:00:00Z","session_id":"off-123","gstack_version":"v"}'); run(`${BIN}/gstack-telemetry-log --skill qa --duration 50 --outcome success --session-id off-123`);