diff --git a/skills/continuous-learning-v2/agents/observer-loop.sh b/skills/continuous-learning-v2/agents/observer-loop.sh index 247d60b7a..bce0d2d7c 100755 --- a/skills/continuous-learning-v2/agents/observer-loop.sh +++ b/skills/continuous-learning-v2/agents/observer-loop.sh @@ -13,6 +13,7 @@ CLAUDE_PID="" CLAUDE_PROCESS_GROUP=0 WATCHDOG_PID="" ACTIVE_ANALYSIS_FILE="" +ACTIVE_PROMPT_FILE="" ACTIVE_RESULT_FILE="" RESULT_FDS_OPEN=0 USR1_FIRED=0 @@ -99,8 +100,10 @@ cleanup_analysis_resources() { RESULT_FDS_OPEN=0 fi [ -n "$ACTIVE_ANALYSIS_FILE" ] && rm -f "$ACTIVE_ANALYSIS_FILE" + [ -n "$ACTIVE_PROMPT_FILE" ] && rm -f "$ACTIVE_PROMPT_FILE" [ -n "$ACTIVE_RESULT_FILE" ] && rm -f "$ACTIVE_RESULT_FILE" ACTIVE_ANALYSIS_FILE="" + ACTIVE_PROMPT_FILE="" ACTIVE_RESULT_FILE="" } @@ -256,6 +259,12 @@ analyze_observations() { fi prompt_file="$(mktemp "${observer_tmp_dir}/ecc-observer-prompt.XXXXXX")" + if [ -z "$prompt_file" ] || [ ! -f "$prompt_file" ]; then + echo "[$(date)] Failed to create observer prompt file; retaining observations for retry" >> "$LOG_FILE" + cleanup_analysis_resources + return + fi + ACTIVE_PROMPT_FILE="$prompt_file" cat > "$prompt_file" </dev/null || true)" rm -f "$prompt_file" + ACTIVE_PROMPT_FILE="" if [ -z "$prompt_content" ]; then echo "[$(date)] Failed to load observer prompt content, skipping analysis" >> "$LOG_FILE" cleanup_analysis_resources @@ -407,7 +418,7 @@ PROMPT signal_claude_process "$CLAUDE_PID" KILL fi fi - ) /dev/null 2>&1 & + ) /dev/null 2>&1 7<&- 8>&- 9<&- & WATCHDOG_PID=$! wait_for_claude_analysis "$CLAUDE_PID" diff --git a/tests/hooks/observer-loop-archive.test.js b/tests/hooks/observer-loop-archive.test.js index c63cc6a75..f35089282 100644 --- a/tests/hooks/observer-loop-archive.test.js +++ b/tests/hooks/observer-loop-archive.test.js @@ -112,7 +112,7 @@ function runAnalyzeOnce(options = {}) { ' [ -e "$candidate" ] || [ -L "$candidate" ] || continue', " printf 'found\n' > \"${CLAUDE_STUB_ATTACK_FILE}\"", ' rm -f "$candidate"', - " printf '%s\n' '${ANALYSIS_COMPLETE_RECORD}' > \"$candidate\"", + ` printf '%s\n' '${ANALYSIS_COMPLETE_RECORD}' > "$candidate"`, ' done', 'fi', 'if [ "${CLAUDE_STUB_DELAY_SECONDS:-0}" != "0" ]; then', @@ -148,7 +148,9 @@ function runAnalyzeOnce(options = {}) { if (existingLog) fs.writeFileSync(logFile, existingLog); const inheritedEnv = Object.fromEntries( - Object.entries(process.env).filter(([key]) => key !== 'CLAUDE_PLUGIN_ROOT') + Object.entries(process.env).filter( + ([key]) => key !== 'CLAUDE_PLUGIN_ROOT' && !key.startsWith('ECC_OBSERVER_') + ) ); const childEnv = { ...inheritedEnv, @@ -173,6 +175,10 @@ function runAnalyzeOnce(options = {}) { CONFIG_DIR: projectDir, CLV2_IS_WINDOWS: 'false', ECC_OBSERVER_TIMEOUT_SECONDS: String(observerTimeoutSeconds), + ECC_OBSERVER_MAX_ANALYSIS_LINES: '500', + ECC_OBSERVER_MAX_TURNS: '20', + ECC_OBSERVER_MODEL: 'haiku', + ECC_OBSERVER_ALLOW_WINDOWS: 'false', }; const startedAt = Date.now();