Skip to content

Commit b470c5a

Browse files
Antigravity Agentclaude
andcommitted
fix(cloud): heartbeat subshell + pipefail + Telegram ordering + HTML escape
- Heartbeat now reads status from temp file (fixes subshell var isolation) - report_status() writes current state to /tmp/agent_heartbeat_state - Telegram notification fires BEFORE LAST_STATUS update (was dead code) - send_telegram() uses temp file for JSON to avoid escaping issues - Added escape_html() for safe Telegram HTML content - Shebang: /bin/sh → /bin/bash (we use bash features) - Added set -o pipefail for proper error propagation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent fbfe0e9 commit b470c5a

1 file changed

Lines changed: 40 additions & 14 deletions

File tree

deploy/agent-entrypoint.sh

Lines changed: 40 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
#!/bin/sh
1+
#!/bin/bash
22
# Trinity Cloud Agent Entrypoint
33
# Solves a single GitHub issue using Claude Code
44
# Required env: ISSUE_NUMBER, GITHUB_TOKEN, ANTHROPIC_API_KEY
55
#
66
# P0 hardened: timeout, SIGTERM handler, heartbeat loop, retry wrapper
77

8-
set -e
8+
set -eo pipefail
99

1010
REPO_URL="${REPO_URL:-https://github.com/gHashTag/trinity.git}"
1111
ISSUE="${ISSUE_NUMBER:?ISSUE_NUMBER is required}"
@@ -54,7 +54,10 @@ report_status() {
5454

5555
log "Status: ${CURRENT_STATUS}${CURRENT_DETAIL}"
5656

57-
# 1. HTTP POST to monitor (existing)
57+
# Update heartbeat file so background heartbeat reads current state
58+
echo "${CURRENT_STATUS}|${CURRENT_DETAIL}" > "${HEARTBEAT_FILE}"
59+
60+
# 1. HTTP POST to monitor
5861
if [ -n "${WS_MONITOR_URL}" ]; then
5962
curl -s -X POST "${WS_MONITOR_URL}/api/status" \
6063
-H "Content-Type: application/json" \
@@ -64,7 +67,14 @@ report_status() {
6467
2>/dev/null || log "Warning: monitor unreachable"
6568
fi
6669

67-
# 2. GitHub issue comment on status change (skip duplicates)
70+
# 2. Telegram notification on status change (BEFORE updating LAST_STATUS)
71+
if [ "${CURRENT_STATUS}" != "${LAST_STATUS}" ] || echo "${CURRENT_STATUS}" | grep -qE "STUCK|ERROR|FAILED|KILLED|DONE"; then
72+
send_telegram "${EMOJI} <b>Agent #${ISSUE}</b>: ${CURRENT_STATUS}
73+
<i>${ISSUE_TITLE:-issue #${ISSUE}}</i>
74+
${CURRENT_DETAIL} (${ELAPSED}s)"
75+
fi
76+
77+
# 3. GitHub issue comment on status change (skip duplicates)
6878
if [ "${CURRENT_STATUS}" != "${LAST_STATUS}" ]; then
6979
gh issue comment "${ISSUE}" --body "${EMOJI} **Trinity Agent** | ${TIMESTAMP}
7080
📋 **Step**: ${STEP_NUM}/${TOTAL_STEPS}${CURRENT_DETAIL}
@@ -73,7 +83,7 @@ report_status() {
7383
fi
7484
LAST_STATUS="${CURRENT_STATUS}"
7585

76-
# 3. Dashboard comment (create or update)
86+
# 4. Dashboard comment (create or update)
7787
DASHBOARD_BODY="${EMOJI} **Trinity Agent Dashboard** — Issue #${ISSUE}
7888
7989
| Field | Value |
@@ -87,30 +97,32 @@ report_status() {
8797

8898
if [ -z "${DASHBOARD_COMMENT_ID}" ]; then
8999
DASHBOARD_COMMENT_ID=$(gh issue comment "${ISSUE}" --body "${DASHBOARD_BODY}" 2>/dev/null | grep -o '/[0-9]*$' | tr -d '/' || true)
90-
# Fallback: fetch last comment ID
91100
if [ -z "${DASHBOARD_COMMENT_ID}" ]; then
92101
DASHBOARD_COMMENT_ID=$(gh api "repos/{owner}/{repo}/issues/${ISSUE}/comments" --jq '.[-1].id' 2>/dev/null || true)
93102
fi
94103
elif [ -n "${DASHBOARD_COMMENT_ID}" ]; then
95104
gh api "repos/{owner}/{repo}/issues/comments/${DASHBOARD_COMMENT_ID}" \
96105
-X PATCH -f body="${DASHBOARD_BODY}" 2>/dev/null || log "Warning: Dashboard update failed"
97106
fi
107+
}
98108

99-
# 4. Telegram notification on status change (with issue title for context)
100-
if [ "${CURRENT_STATUS}" != "${LAST_STATUS}" ] || echo "${CURRENT_STATUS}" | grep -qE "STUCK|ERROR|FAILED|KILLED|DONE"; then
101-
send_telegram "${EMOJI} <b>Agent #${ISSUE}</b>: ${CURRENT_STATUS}
102-
<i>${ISSUE_TITLE:-issue #${ISSUE}}</i>
103-
${CURRENT_DETAIL} (${ELAPSED}s)"
104-
fi
109+
escape_html() {
110+
echo "$1" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g'
105111
}
106112

107113
send_telegram() {
108114
if [ -n "${TELEGRAM_BOT_TOKEN}" ] && [ -n "${TELEGRAM_CHAT_ID}" ]; then
115+
# Write message to temp file to avoid JSON escaping issues with special chars
116+
local msg_file="/tmp/tg_msg_$$.json"
117+
printf '{"chat_id":"%s","text":"%s","parse_mode":"HTML"}' \
118+
"${TELEGRAM_CHAT_ID}" "$(echo "$1" | sed 's/"/\\"/g; s/$/\\n/' | tr -d '\n' | sed 's/\\n$//')" \
119+
> "${msg_file}"
109120
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
110121
-H "Content-Type: application/json" \
111-
-d "{\"chat_id\":\"${TELEGRAM_CHAT_ID}\",\"text\":\"$1\",\"parse_mode\":\"HTML\"}" \
122+
-d "@${msg_file}" \
112123
--connect-timeout 5 --max-time 10 \
113124
2>/dev/null || log "Warning: Telegram send failed"
125+
rm -f "${msg_file}"
114126
fi
115127
}
116128

@@ -155,11 +167,25 @@ emit_event() {
155167
# HEARTBEAT LOOP (P0.6) — background process sends status every 30s
156168
# ═══════════════════════════════════════════════════════════════════════════════
157169

170+
HEARTBEAT_FILE="/tmp/agent_heartbeat_state"
171+
158172
start_heartbeat() {
173+
echo "STARTING|Initializing" > "${HEARTBEAT_FILE}"
159174
(
160175
while true; do
161176
sleep "${HEARTBEAT_INTERVAL}"
162-
report_status "${CURRENT_STATUS}" "${CURRENT_DETAIL}"
177+
if [ -f "${HEARTBEAT_FILE}" ]; then
178+
HB_STATUS=$(cut -d'|' -f1 "${HEARTBEAT_FILE}")
179+
HB_DETAIL=$(cut -d'|' -f2 "${HEARTBEAT_FILE}")
180+
ELAPSED=$(( $(date +%s) - ${START_TIME:-0} ))
181+
if [ -n "${WS_MONITOR_URL}" ]; then
182+
curl -s -X POST "${WS_MONITOR_URL}/api/status" \
183+
-H "Content-Type: application/json" \
184+
-H "Authorization: Bearer ${MONITOR_TOKEN:-trinity}" \
185+
-d "{\"issue\":${ISSUE},\"status\":\"${HB_STATUS}\",\"detail\":\"heartbeat: ${HB_DETAIL} (${ELAPSED}s)\"}" \
186+
--connect-timeout 5 --max-time 10 2>/dev/null || true
187+
fi
188+
fi
163189
done
164190
) &
165191
HEARTBEAT_PID=$!

0 commit comments

Comments
 (0)