@rulemetric/hooks 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulemetric/hooks",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/scripts/_post.sh CHANGED
@@ -73,3 +73,65 @@ _rulemetric_post_event() {
73
73
  [ -n "$hdr_file" ] && rm -f "$hdr_file"
74
74
  return 1
75
75
  }
76
+
77
+ # _rulemetric_send <method> <url> <auth_header> <payload> <log_tag> [max_time_s]
78
+ # Method-aware sibling of _rulemetric_post_event for the session-END writes that
79
+ # previously used a bare `curl -sf` and SILENTLY DROPPED on any failure — enrich
80
+ # (PATCH), reimport (POST), derive-enrichment (POST). When the API fast-503s under
81
+ # transient DB-pool load, those calls dropped, leaving sessions un-enriched / empty
82
+ # (the "insights stuck at 06/30" cause). Same bounded 503/429/network retry +
83
+ # capped Retry-After as _rulemetric_post_event; 4xx permanent; logs final failure.
84
+ # Returns 0 on 2xx. `max_time_s` defaults to 4 (reimport passes 8 for large bodies).
85
+ # `max_attempts` defaults to $_RM_POST_MAX_ATTEMPTS; the FOREGROUND reimport passes
86
+ # 2 to cap its worst-case session-end tail-latency (it blocks the hook — see below).
87
+ _rulemetric_send() {
88
+ local method="$1" url="$2" auth="$3" payload="$4" tag="$5" max_time="${6:-4}" max_attempts="${7:-$_RM_POST_MAX_ATTEMPTS}"
89
+ local attempt=1 http_code="000" body="" hdr_file retry_after sleep_s
90
+
91
+ hdr_file=$(mktemp "${TMPDIR:-/tmp}/rm-send.XXXXXX" 2>/dev/null || true)
92
+
93
+ while [ "$attempt" -le "$max_attempts" ]; do
94
+ if [ -n "$hdr_file" ]; then
95
+ body=$(curl -s --noproxy '*' --max-time "$max_time" -D "$hdr_file" -w '\n%{http_code}' \
96
+ -X "$method" "$url" -H "$auth" -H "Content-Type: application/json" \
97
+ -d "$payload" 2>&1) || true
98
+ else
99
+ body=$(curl -s --noproxy '*' --max-time "$max_time" -w '\n%{http_code}' \
100
+ -X "$method" "$url" -H "$auth" -H "Content-Type: application/json" \
101
+ -d "$payload" 2>&1) || true
102
+ fi
103
+ http_code=$(printf '%s' "$body" | tail -1)
104
+ body=$(printf '%s' "$body" | sed '$d')
105
+
106
+ if [ "$http_code" = "200" ] || [ "$http_code" = "201" ]; then
107
+ [ -n "$hdr_file" ] && rm -f "$hdr_file"
108
+ return 0
109
+ fi
110
+
111
+ if [ "$http_code" = "503" ] || [ "$http_code" = "429" ] || [ "$http_code" = "000" ]; then
112
+ if [ "$attempt" -lt "$max_attempts" ]; then
113
+ retry_after=""
114
+ if [ -n "$hdr_file" ]; then
115
+ retry_after=$(grep -i '^Retry-After:' "$hdr_file" 2>/dev/null | tail -1 | tr -dc '0-9' || true)
116
+ fi
117
+ if [ -n "$retry_after" ] && [ "$retry_after" -gt 0 ] 2>/dev/null; then
118
+ sleep_s="$retry_after"
119
+ else
120
+ sleep_s="$attempt"
121
+ fi
122
+ if [ "$sleep_s" -gt "$_RM_POST_RETRY_CAP_S" ] 2>/dev/null; then
123
+ sleep_s="$_RM_POST_RETRY_CAP_S"
124
+ fi
125
+ sleep "$sleep_s" 2>/dev/null || true
126
+ attempt=$((attempt + 1))
127
+ continue
128
+ fi
129
+ fi
130
+
131
+ break
132
+ done
133
+
134
+ _rulemetric_log "$tag" "error" "http_${http_code}_attempt_${attempt}: ${body}"
135
+ [ -n "$hdr_file" ] && rm -f "$hdr_file"
136
+ return 1
137
+ }
@@ -2,6 +2,11 @@
2
2
  # RuleMetric: Finalize session with full transcript import
3
3
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
4
4
  source "$SCRIPT_DIR/_log.sh" 2>/dev/null || _rulemetric_log() { :; }
5
+ # Bounded retry-on-503 for the session-end writes (reimport/enrich/derive) so a
6
+ # transient DB-pool wedge doesn't SILENTLY drop them (the "insights stuck" cause).
7
+ # Log if the helper can't load (packaging regression) — otherwise the writes below
8
+ # would hit `command not found` and degrade silently (backgrounded ones with NO log).
9
+ source "$SCRIPT_DIR/_post.sh" 2>/dev/null || _rulemetric_log "session-end" "warn" "post_helper_unavailable"
5
10
 
6
11
  set -euo pipefail
7
12
  trap 'echo "rulemetric session-end failed at line $LINENO: $BASH_COMMAND" >&2; _rulemetric_log "session-end" "error" "line $LINENO: $BASH_COMMAND"; exit 1' ERR
@@ -41,10 +46,13 @@ fi
41
46
  # Try to reimport with full transcript, fall back to close
42
47
  if [ -n "$HOOK_TRANSCRIPT_PATH" ] && [ -f "$HOOK_TRANSCRIPT_PATH" ]; then
43
48
  CONTENT=$(cat "$HOOK_TRANSCRIPT_PATH")
44
- curl -sf --noproxy '*' --max-time 8 -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/reimport" \
45
- -H "$AUTH_HEADER" \
46
- -H "Content-Type: application/json" \
47
- -d "{\"content\":$(echo "$CONTENT" | jq -Rs .),\"tool\":\"$HOOK_TOOL\"}" \
49
+ REIMPORT_PAYLOAD="{\"content\":$(echo "$CONTENT" | jq -Rs .),\"tool\":\"$HOOK_TOOL\"}"
50
+ # Retry-on-503 (max-time 8 for the large transcript body); this is FOREGROUND and
51
+ # blocks the hook, so cap at 2 attempts (worst-case tail ~18s on a hard API hang,
52
+ # vs ~32s at 3) — the common fast-503 wedge still recovers in <1s. Fall back to
53
+ # close if it still fails after retries — a dropped reimport leaves the session EMPTY.
54
+ _rulemetric_send POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/reimport" \
55
+ "$AUTH_HEADER" "$REIMPORT_PAYLOAD" "session-end-reimport" 8 2 \
48
56
  > /dev/null 2>&1 || {
49
57
  _rulemetric_log "session-end" "fallback" "reimport_failed"
50
58
  curl -sf --noproxy '*' --max-time 4 -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/close" \
@@ -127,18 +135,16 @@ except:
127
135
  " 2>/dev/null)
128
136
 
129
137
  if [ -n "$ENRICH_PAYLOAD" ]; then
130
- (curl -sf --noproxy '*' --max-time 4 -X PATCH "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/enrich" \
131
- -H "$AUTH_HEADER" \
132
- -H "Content-Type: application/json" \
133
- -d "$ENRICH_PAYLOAD" > /dev/null 2>&1) &
138
+ # Backgrounded retry-on-503 so a transient wedge doesn't drop enrichment
139
+ # (leaving insights stuck at the last-enriched session).
140
+ (_rulemetric_send PATCH "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/enrich" \
141
+ "$AUTH_HEADER" "$ENRICH_PAYLOAD" "session-end-enrich" > /dev/null 2>&1) &
134
142
  fi
135
143
  fi
136
144
  else
137
145
  # Non-Claude tools: derive enrichment from stored session events
138
- (curl -sf --noproxy '*' --max-time 8 -X POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/derive-enrichment" \
139
- -H "$AUTH_HEADER" \
140
- -H "Content-Type: application/json" \
141
- -d '{}' > /dev/null 2>&1) &
146
+ (_rulemetric_send POST "${RULEMETRIC_API_URL}/api/sessions/${RW_SESSION_ID}/derive-enrichment" \
147
+ "$AUTH_HEADER" '{}' "session-end-derive" 8 > /dev/null 2>&1) &
142
148
  fi
143
149
 
144
150
  # ── Tail transcript for 429 rate-limit events ──