mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-09 19:03:28 +08:00
fix(continuous-learning-v2): add lazy-start observer logic (#508)
* feat(continuous-learning-v2): add lazy-start observer logic Auto-starts observer when observer.enabled: true in config and no .observer.pid exists. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(continuous-learning-v2): address PR review concerns - Use flock for atomic check-then-act to prevent race conditions - Check both project-scoped AND global PID files before starting - Support CLV2_CONFIG override for config file path - Check disabled file in lazy-start logic - Use double-check pattern after acquiring lock Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(observe.sh): address PR review comments - Add stale PID cleanup via _CHECK_OBSERVER_RUNNING function - Add macOS fallback using lockfile when flock unavailable - Fix CLV2_CONFIG override: use EFFECTIVE_CONFIG for both check and read - Use proper Python context manager (with open() as f) - Deduplicate signaled PIDs to avoid duplicate USR1 signals Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(observe.sh): wrap macOS lockfile fallback in subshell with trap - Wrap lockfile block in subshell so exit 0 only terminates that block - Add trap for EXIT to clean up lock file on script interruption - Add -l 30 (30 second expiry) to prevent permanent lock file stuck Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(observe.sh): address remaining PR review comments - Validate PID is a positive integer before kill calls to prevent signaling invalid targets (e.g. -1 could signal all processes) - Pass config path via env var instead of interpolating shell variable into Python -c string to prevent injection/breakage on special paths - Check CLV2_CONFIG-derived directory for disabled file so disable guard respects the same config source as lazy-start Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -83,10 +83,13 @@ fi
|
|||||||
|
|
||||||
CONFIG_DIR="${HOME}/.claude/homunculus"
|
CONFIG_DIR="${HOME}/.claude/homunculus"
|
||||||
|
|
||||||
# Skip if disabled
|
# Skip if disabled (check both default and CLV2_CONFIG-derived locations)
|
||||||
if [ -f "$CONFIG_DIR/disabled" ]; then
|
if [ -f "$CONFIG_DIR/disabled" ]; then
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
if [ -n "${CLV2_CONFIG:-}" ] && [ -f "$(dirname "$CLV2_CONFIG")/disabled" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
# Prevent observe.sh from firing on non-human sessions to avoid:
|
# Prevent observe.sh from firing on non-human sessions to avoid:
|
||||||
# - ECC observing its own Haiku observer sessions (self-loop)
|
# - ECC observing its own Haiku observer sessions (self-loop)
|
||||||
@@ -275,12 +278,109 @@ if parsed["output"] is not None:
|
|||||||
print(json.dumps(observation))
|
print(json.dumps(observation))
|
||||||
' >> "$OBSERVATIONS_FILE"
|
' >> "$OBSERVATIONS_FILE"
|
||||||
|
|
||||||
# Signal observer if running (check both project-scoped and global observer)
|
# Lazy-start observer if enabled but not running (first-time setup)
|
||||||
|
# Use flock for atomic check-then-act to prevent race conditions
|
||||||
|
# Fallback for macOS (no flock): use lockfile or skip
|
||||||
|
LAZY_START_LOCK="${PROJECT_DIR}/.observer-start.lock"
|
||||||
|
_CHECK_OBSERVER_RUNNING() {
|
||||||
|
local pid_file="$1"
|
||||||
|
if [ -f "$pid_file" ]; then
|
||||||
|
local pid
|
||||||
|
pid=$(cat "$pid_file" 2>/dev/null)
|
||||||
|
# Validate PID is a positive integer (>1) to prevent signaling invalid targets
|
||||||
|
case "$pid" in
|
||||||
|
''|*[!0-9]*|0|1)
|
||||||
|
rm -f "$pid_file" 2>/dev/null || true
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
if kill -0 "$pid" 2>/dev/null; then
|
||||||
|
return 0 # Process is alive
|
||||||
|
fi
|
||||||
|
# Stale PID file - remove it
|
||||||
|
rm -f "$pid_file" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
return 1 # No PID file or process dead
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ -f "${CONFIG_DIR}/disabled" ]; then
|
||||||
|
OBSERVER_ENABLED=false
|
||||||
|
else
|
||||||
|
OBSERVER_ENABLED=false
|
||||||
|
CONFIG_FILE="${SKILL_ROOT}/config.json"
|
||||||
|
# Allow CLV2_CONFIG override
|
||||||
|
if [ -n "${CLV2_CONFIG:-}" ]; then
|
||||||
|
CONFIG_FILE="$CLV2_CONFIG"
|
||||||
|
fi
|
||||||
|
# Use effective config path for both existence check and reading
|
||||||
|
EFFECTIVE_CONFIG="$CONFIG_FILE"
|
||||||
|
if [ -f "$EFFECTIVE_CONFIG" ] && [ -n "$PYTHON_CMD" ]; then
|
||||||
|
_enabled=$(CLV2_CONFIG_PATH="$EFFECTIVE_CONFIG" "$PYTHON_CMD" -c "
|
||||||
|
import json, os
|
||||||
|
with open(os.environ['CLV2_CONFIG_PATH']) as f:
|
||||||
|
cfg = json.load(f)
|
||||||
|
print(str(cfg.get('observer', {}).get('enabled', False)).lower())
|
||||||
|
" 2>/dev/null || echo "false")
|
||||||
|
if [ "$_enabled" = "true" ]; then
|
||||||
|
OBSERVER_ENABLED=true
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check both project-scoped AND global PID files (with stale PID recovery)
|
||||||
|
if [ "$OBSERVER_ENABLED" = "true" ]; then
|
||||||
|
# Clean up stale PID files first
|
||||||
|
_CHECK_OBSERVER_RUNNING "${PROJECT_DIR}/.observer.pid" || true
|
||||||
|
_CHECK_OBSERVER_RUNNING "${CONFIG_DIR}/.observer.pid" || true
|
||||||
|
|
||||||
|
# Check if observer is now running after cleanup
|
||||||
|
if [ ! -f "${PROJECT_DIR}/.observer.pid" ] && [ ! -f "${CONFIG_DIR}/.observer.pid" ]; then
|
||||||
|
# Use flock if available (Linux), fallback for macOS
|
||||||
|
if command -v flock >/dev/null 2>&1; then
|
||||||
|
(
|
||||||
|
flock -n 9 || exit 0
|
||||||
|
# Double-check PID files after acquiring lock
|
||||||
|
_CHECK_OBSERVER_RUNNING "${PROJECT_DIR}/.observer.pid" || true
|
||||||
|
_CHECK_OBSERVER_RUNNING "${CONFIG_DIR}/.observer.pid" || true
|
||||||
|
if [ ! -f "${PROJECT_DIR}/.observer.pid" ] && [ ! -f "${CONFIG_DIR}/.observer.pid" ]; then
|
||||||
|
nohup "${SKILL_ROOT}/agents/start-observer.sh" start >/dev/null 2>&1 &
|
||||||
|
fi
|
||||||
|
) 9>"$LAZY_START_LOCK"
|
||||||
|
else
|
||||||
|
# macOS fallback: use lockfile if available, otherwise skip
|
||||||
|
if command -v lockfile >/dev/null 2>&1; then
|
||||||
|
# Use subshell to isolate exit and add trap for cleanup
|
||||||
|
(
|
||||||
|
trap 'rm -f "$LAZY_START_LOCK" 2>/dev/null || true' EXIT
|
||||||
|
lockfile -r 1 -l 30 "$LAZY_START_LOCK" 2>/dev/null || exit 0
|
||||||
|
_CHECK_OBSERVER_RUNNING "${PROJECT_DIR}/.observer.pid" || true
|
||||||
|
_CHECK_OBSERVER_RUNNING "${CONFIG_DIR}/.observer.pid" || true
|
||||||
|
if [ ! -f "${PROJECT_DIR}/.observer.pid" ] && [ ! -f "${CONFIG_DIR}/.observer.pid" ]; then
|
||||||
|
nohup "${SKILL_ROOT}/agents/start-observer.sh" start >/dev/null 2>&1 &
|
||||||
|
fi
|
||||||
|
rm -f "$LAZY_START_LOCK" 2>/dev/null || true
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Signal observer if running (check both project-scoped and global observer, deduplicate)
|
||||||
|
signaled_pids=" "
|
||||||
for pid_file in "${PROJECT_DIR}/.observer.pid" "${CONFIG_DIR}/.observer.pid"; do
|
for pid_file in "${PROJECT_DIR}/.observer.pid" "${CONFIG_DIR}/.observer.pid"; do
|
||||||
if [ -f "$pid_file" ]; then
|
if [ -f "$pid_file" ]; then
|
||||||
observer_pid=$(cat "$pid_file")
|
observer_pid=$(cat "$pid_file" 2>/dev/null || true)
|
||||||
|
# Validate PID is a positive integer (>1)
|
||||||
|
case "$observer_pid" in
|
||||||
|
''|*[!0-9]*|0|1) rm -f "$pid_file" 2>/dev/null || true; continue ;;
|
||||||
|
esac
|
||||||
|
# Deduplicate: skip if already signaled this pass
|
||||||
|
case "$signaled_pids" in
|
||||||
|
*" $observer_pid "*) continue ;;
|
||||||
|
esac
|
||||||
if kill -0 "$observer_pid" 2>/dev/null; then
|
if kill -0 "$observer_pid" 2>/dev/null; then
|
||||||
kill -USR1 "$observer_pid" 2>/dev/null || true
|
kill -USR1 "$observer_pid" 2>/dev/null || true
|
||||||
|
signaled_pids="${signaled_pids}${observer_pid} "
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|||||||
Reference in New Issue
Block a user