mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-03-30 13:43:26 +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"
|
||||
|
||||
# Skip if disabled
|
||||
# Skip if disabled (check both default and CLV2_CONFIG-derived locations)
|
||||
if [ -f "$CONFIG_DIR/disabled" ]; then
|
||||
exit 0
|
||||
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:
|
||||
# - ECC observing its own Haiku observer sessions (self-loop)
|
||||
@@ -275,12 +278,109 @@ if parsed["output"] is not None:
|
||||
print(json.dumps(observation))
|
||||
' >> "$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
|
||||
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
|
||||
kill -USR1 "$observer_pid" 2>/dev/null || true
|
||||
signaled_pids="${signaled_pids}${observer_pid} "
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
Reference in New Issue
Block a user