Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .agents/scripts/supervisor/_common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,47 @@ log_cmd() {
[[ $rc -ne 0 ]] && echo "[$ts] [$context] exit=$rc" >>"${SUPERVISOR_LOG:-/dev/null}" 2>/dev/null || true
return $rc
}

#######################################
# Portable timeout — works on macOS (no GNU coreutils) and Linux
# Uses background process + kill pattern when `timeout` is unavailable.
# Arguments:
# $1 - timeout in seconds
# $@ - command to run
# Returns:
# Command exit code, or 124 on timeout (matches GNU timeout convention)
#######################################
portable_timeout() {
local secs="$1"
shift

# If GNU timeout is available, use it (faster, handles signals better)
if command -v timeout &>/dev/null; then
timeout "$secs" "$@"
return $?
fi

# Fallback: background the command, sleep, kill if still running
"$@" &
local cmd_pid=$!

(
sleep "$secs"
kill "$cmd_pid" 2>/dev/null
) &
local watchdog_pid=$!

wait "$cmd_pid" 2>/dev/null
local exit_code=$?

# Clean up watchdog if command finished before timeout
kill "$watchdog_pid" 2>/dev/null
wait "$watchdog_pid" 2>/dev/null

# If killed by our watchdog, return 124 (GNU timeout convention)
if [[ $exit_code -eq 137 || $exit_code -eq 143 ]]; then
return 124
Comment on lines +128 to +130

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The portable_timeout function returns 124 for SIGKILL (137) and SIGTERM (143). While 124 is the GNU timeout convention for timeout, it's important to note that SIGKILL and SIGTERM are distinct signals. Returning 124 for both might obscure the specific signal that terminated the process. Consider distinguishing between SIGKILL and SIGTERM if there's a need for more granular error handling in consuming scripts, or add a comment explaining why both map to 124.

Suggested change
# If killed by our watchdog, return 124 (GNU timeout convention)
if [[ $exit_code -eq 137 || $exit_code -eq 143 ]]; then
return 124
if [[ $exit_code -eq 137 || $exit_code -eq 143 ]]; then
# Both SIGKILL (137) and SIGTERM (143) are mapped to 124 for GNU timeout compatibility.
return 124
fi

fi

return "$exit_code"
}
32 changes: 30 additions & 2 deletions .agents/scripts/supervisor/ai-reason.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,34 @@
# AI reasoning log directory
AI_REASON_LOG_DIR="${AI_REASON_LOG_DIR:-$HOME/.aidevops/logs/ai-supervisor}"

# Portable timeout alias — uses portable_timeout from _common.sh when sourced,
# or defines a local fallback for standalone execution.
if ! declare -f portable_timeout &>/dev/null; then
portable_timeout() {
local secs="$1"
shift
if command -v timeout &>/dev/null; then
timeout "$secs" "$@"
return $?
fi
"$@" &
local cmd_pid=$!
(
sleep "$secs"
kill "$cmd_pid" 2>/dev/null
) &
local watchdog_pid=$!
wait "$cmd_pid" 2>/dev/null
local exit_code=$?
kill "$watchdog_pid" 2>/dev/null
wait "$watchdog_pid" 2>/dev/null
if [[ $exit_code -eq 137 || $exit_code -eq 143 ]]; then
return 124
fi
return "$exit_code"
}
fi

#######################################
# Check if there is actionable work worth reasoning about
# Avoids spawning an expensive opus session when nothing needs attention.
Expand Down Expand Up @@ -219,14 +247,14 @@ PROMPT
${user_prompt}"

if [[ "$ai_cli" == "opencode" ]]; then
ai_result=$(timeout "$ai_timeout" opencode run \
ai_result=$(portable_timeout "$ai_timeout" opencode run \
-m "$ai_model" \
--format text \
--title "ai-supervisor-${timestamp}" \
"$full_prompt" 2>/dev/null || echo "")
else
local claude_model="${ai_model#*/}"
ai_result=$(timeout "$ai_timeout" claude \
ai_result=$(portable_timeout "$ai_timeout" claude \
-p "$full_prompt" \
--model "$claude_model" \
--output-format text 2>/dev/null || echo "")
Expand Down
Loading