260414 EPERM · bond_daily_report · commodity_spike 통합 진단
작성 시각: 2026-04-14 KST
범위: 코드 수정 없음, 워커 재시작 없음, 파괴적 명령 없음
1. 요약
| 항목 | 결론 |
|---|---|
| EPERM 근본 원인 | Ollama/Hermes 장애가 아니라 macOS sandbox/seatbelt가 일부 Python 실행 경로의 network-outbound를 차단한 것 |
| launchd 워커 자체 문제 여부 | 현재 증거로는 낮음. launchctl asuser에서는 11434/18789 모두 접속 성공 |
| 남아있는 위험 | Codex/surface/manual/isolated agentTurn처럼 sandbox가 붙는 경로에서는 여전히 EPERM 재현 가능 |
| bond_daily_report 6일 정지 | 원인 2개: 최신 황대진 원천 노트가 260408에서 멈춤 + 이후 LLM 호출 실패 |
| commodity_spike_analyzer 운영 모드 | cron은 운영 모드(--mode morning/watch)이며 --dry-run, --force 없음. 최근 dry_run/force 실행은 수동/진단 성격 |
2. EPERM 사실 → 검증 → 원인
2.1 확인된 사실
- Ollama:
127.0.0.1:11434LISTEN 확인 - Hermes gateway:
127.0.0.1:18789LISTEN 확인 - 일반 로컬 shell /
launchctl asuser컨텍스트에서는 둘 다 접속 성공 - Codex sandbox 컨텍스트에서는 동일 URL이 둘 다
[Errno 1] Operation not permitted로 실패 - macOS unified log에 실제 sandbox deny 기록 존재
2.2 재현 결과
| 실행 경로 | 11434 Ollama | 18789 Hermes | 판정 |
|---|---|---|---|
| Codex 기본 sandbox | EPERM | EPERM | 재현 성공 |
| 일반 로컬 shell | OK | OK | 서비스 정상 |
launchctl asuser $(id -u) |
OK | OK | launchd/asuser 자체는 정상 |
핵심 재현:
Codex sandbox:
ERR http://127.0.0.1:11434/api/tags URLError(PermissionError(1, 'Operation not permitted'))
ERR http://127.0.0.1:18789/v1/health URLError(PermissionError(1, 'Operation not permitted'))
real/asuser context:
OK http://127.0.0.1:11434/api/tags
OK http://127.0.0.1:18789/v1/health
2.3 시스템 로그 근거
/usr/bin/log show에서 13:31~13:34 사이 다음 deny 확인:
Sandbox: Python(...) deny(1) network-outbound /private/var/run/mDNSResponder
Sandbox: Python(...) deny(1) network-outbound remote:*:18789
Sandbox: Python(...) deny(1) network-outbound remote:*:11434
이 로그는 EPERM이 Python 내부 예외가 아니라 macOS sandbox 정책 차단임을 직접 지지함.
2.4 워커/LaunchAgent 검증
- 현재 agent_queue_worker 8개와 orchestrator는 실행 중 확인
- 워커 재시작은 하지 않음
- LaunchAgent plist /
launchctl print에서sandbox-exec,Seatbelt,Sandbox설정 흔적 없음 launchctl asuser에서 로컬 포트 접속 성공
따라서 현재 근거로는 “launchd 워커가 본질적으로 네트워크 금지 상태”가 아니라, 특정 수동 실행·surface·isolated agentTurn 경로가 sandbox 안에서 실행될 때 발생하는 문제로 보는 것이 맞음.
2.5 방화벽/TCC 검증
| 항목 | 결과 | 해석 |
|---|---|---|
| Application Firewall | enabled | 켜져 있음 |
| Block all | disabled | 전체 차단 아님 |
| 일반/asuser localhost 접속 | OK | 방화벽이 localhost를 전면 차단하는 상황 아님 |
| TCC DB 조회 | authorization denied | SIP/TCC 보호로 직접 확인 제한 |
방화벽은 켜져 있지만 이번 EPERM의 직접 원인으로 볼 증거는 없음. 직접 증거는 sandbox network-outbound deny임.
2.6 LLM 로그 영향 범위
~/.openclaw/logs/llm/20260414.jsonl에서 EPERM 레코드 30건 확인. 대표 패턴:
llm_chat_with_fallback openai-codex/gpt-5.4 <urlopen error [Errno 1] Operation not permitted>
llm_chat_with_fallback ollama/qwen2.5:3b <urlopen error [Errno 1] Operation not permitted>
llm_chat_direct ollama/qwen2.5:3b <urlopen error [Errno 1] Operation not permitted>
13:31~13:34 bond 관련 호출, 12:41 commodity 관련 호출에서 동일 EPERM 확인.
2.7 원인 판정
원인: sandboxed Python 실행 경로의 outbound network 권한 없음.
아님: Ollama down, Hermes down, 단순 launchd/asuser 권한 문제, shared/llm.py 라우팅 버그 단독 문제.
13:30 전후 Hermes gateway bootout/bootstrap과 시간상 겹치지만, 현재 검증으로는 bootout/bootstrap 자체가 EPERM을 만든 증거는 없음. 더 그럴듯한 설명은 그 시점의 복구/검증 명령이 sandboxed surface에서 실행되며 로컬 네트워크가 차단된 것임.
3. EPERM 재발 방지 권고
3.1 운영 규칙
LLM 호출·복구·smoke test는 아래 중 하나로만 실행:
- 일반 로컬 shell 또는 승인된 unsandboxed 실행
- 정상 launchd worker/queue 경로
- 별도 unsandboxed helper runner
피해야 할 경로:
- Codex 기본 sandbox에서 직접
llm_chat_*호출 - surface/manual recovery가 내부적으로 sandboxed Python을 띄우는 경로
- isolated agentTurn에서 네트워크 권한 확인 없이 LLM 호출
3.2 사전 점검 명령
LLM 작업 전 3초짜리 localhost preflight 권장:
python3 - <<'PY'
import urllib.request
for u in ['http://127.0.0.1:11434/api/tags','http://127.0.0.1:18789/v1/health']:
try:
urllib.request.urlopen(u, timeout=3).read()
print('OK', u)
except Exception as e:
raise SystemExit(f'BLOCKED {u}: {e!r}')
PY
EPERM이면 같은 sandbox 안에서 재시도하지 말고 unsandboxed 경로로 reroute.
3.3 retry 정책
Connection refused,timeout: 서비스 기동 중일 수 있으므로 exponential backoff 적합EPERM: 권한/실행환경 문제이므로 1~2회 빠른 확인 후 즉시 reroute- 같은 체인에서 EPERM으로 GPT → Ollama까지 순차 소진하는 것은 의미 없음. 모델 문제가 아니라 실행환경 문제임
3.4 Hermes reload 안전 절차
Hermes plist 재로드 후에는 LLM 작업 투입 전 아래 순서 권장:
lsof -iTCP:18789 -sTCP:LISTENcurl http://127.0.0.1:18789/v1/health- 위 Python preflight
- 그 뒤 worker/queue 작업 실행
4. bond_daily_report 6일 정지 진단
4.1 shared/llm.py 사용 여부
bond_daily_report.py는 자체 LLM 호출이 아니라 shared 모듈 사용:
from shared.llm import llm_chat_with_fallback, DEFAULT_MODEL_CHAIN, PREMIUM_MODEL_CHAIN
BOND_MODEL_CHAIN = PREMIUM_MODEL_CHAIN
insight, used_model, err = llm_chat_with_fallback(..., model_chain=BOND_MODEL_CHAIN, max_tokens=4500)
판정: shared/llm.py 경유 맞음.
4.2 황대진 노트 수집 상태
소스 경로:
~/knowledge/100 수신함/119 크레딧메일/*황대진*.md
현재 최신 황대진 노트:
260408_황대진_전달-일일-48-수-낙찰-및-마감정리-DS증권-황대진.md
260408_황대진_전달-금일-49-목-입찰-및-해외시장-동향-DS증권-황대진.md
260409~260414 신규 황대진 노트는 해당 폴더에서 확인되지 않음.
따라서 “6일 정지”의 1차 원인은 원천 노트가 260408에서 멈춘 것임.
4.3 cron 상태
| job | 상태 | 스케줄 | 실행 내용 |
|---|---|---|---|
bond-daily-report |
enabled | 30 2 * * 2-6 |
bond_daily_report.py --notify |
bond-morning-poll |
enabled | 3,33 7-9 * * 1-5 |
gmail_credit_monitor.py --fixed-income-only |
황대진 노트 수집 cron은 존재하고 enabled. 다만 실제 원천 폴더가 260408 이후 갱신되지 않았으므로, 다음 확인 대상은 Gmail 수집기 로그/메일 검색 결과임. 이번 작업에서는 Gmail 원문 여부까지는 확인하지 않음.
4.4 bond 실행 로그
대표 로그:
2026-04-09 14:44:33 리포트 생성 완료: 2026-04-08 기준
2026-04-13 11:59:51 최신 황대진 노트: 260408...
2026-04-14 02:31:24 최신 황대진 노트: 260408...
2026-04-14 02:33:30 LLM 실패: ollama/qwen3.5:9b-nothinker: HTTP Error 400: Bad Request
2026-04-14 13:31:35 최신 황대진 노트: 260408...
2026-04-14 13:32:26 LLM 실패: ollama/qwen2.5:3b: <urlopen error [Errno 1] Operation not permitted>
정리:
- 260408 이후 새 입력 노트 없음
- Apr 14 새벽 실행은 기존 LLM 라우팅/파라미터 문제로 실패
- Apr 14 오후 수동/복구 실행은 sandbox EPERM으로 실패
현재 보고 시점에서 memory/bond-briefing/latest.json, 2026-04-08.json은 확인되지 않았고, vault 출력 260408_브리핑_2026-04-08.md는 존재함.
4.5 bond 권고
- Gmail 수집기
gmail_credit_monitor.py --fixed-income-only의 최근 로그와 실제 메일 존재 여부 확인 필요 - 황대진 메일이 실제로 왔는데 노트가 없다면 수집기 문제
- 메일이 오지 않았다면 bond 리포트는 정상적으로 새 입력 없음 상태
- LLM 실행은 EPERM 없는 경로에서만 재시도
5. commodity_spike_analyzer 운영 모드 진단
5.1 LLM 호출 경로
commodity_spike_analyzer.py는 shared.llm.llm_chat_direct 사용:
response = llm_chat_direct(
[{"role": "user", "content": prompt}],
PREMIUM_MODEL_CHAIN,
max_tokens=1500,
)
판정: shared/llm.py 경유는 맞지만, fallback 함수가 아니라 direct 호출 경로임.
5.2 dry_run / force 결정 경로
| 옵션 | 적용 위치 | 의미 |
|---|---|---|
--mode morning |
기본값 | 오전 급변 감지 1회 실행 |
--mode watch |
감시 모드 | 장중 반복 감시 |
--dry-run |
morning/watch 공통 | 텔레그램 전송만 생략. LLM 분석은 실행함 |
--force |
morning 전용 | 임계값과 무관하게 급변 분석 강제 |
중요: dry_run=True여도 LLM 호출은 발생함. 따라서 dry-run 진단도 sandbox 안에서 실행하면 EPERM이 날 수 있음.
5.3 cron 운영 설정
| job | 상태 | 스케줄 | 명령 | dry_run | force |
|---|---|---|---|---|---|
commodity-spike-morning |
enabled | 20 7 * * 1-5 |
--mode morning |
없음 | 없음 |
commodity-spike-watch |
enabled | */30 10-23 * * 1-5 |
--mode watch |
없음 | 없음 |
현재 cron 기준 운영 모드는 실운영 모드임. --dry-run, --force는 cron에 없음.
5.4 최근 로그 해석
Apr 14 로그:
10:22:37 === morning_mode 시작 (dry_run=True, force=True) ===
10:23:00 LLM 원인 분석 요청 중...
10:23:09 LLM 분석 완료 (모델: )
[DRY-RUN] 텔레그램 전송 스킵
12:40:38 === morning_mode 시작 (dry_run=True, force=True) ===
12:41:01 LLM 원인 분석 요청 중...
12:41:09 LLM 분석 완료 (모델: )
[DRY-RUN] 텔레그램 전송 스킵
동일 시각 LLM JSONL:
llm_chat_direct ollama/qwen2.5:3b <urlopen error [Errno 1] Operation not permitted>
해석:
- 10:22/12:40 실행은 cron 기본값이 아니라 수동/진단 실행으로 보임 (
dry_run=True, force=True) - LLM은 실제로 실패했지만 스크립트가 빈 모델명으로 “LLM 분석 완료”를 찍고 있음
- 이는 운영상 관측성을 흐리는 버그 후보임. 이번 작업에서는 코드 수정 금지라 변경하지 않음
5.5 commodity 권고
- “LLM 분석 완료 (모델: )”는 성공으로 보지 말 것
llm_chat_direct반환값에서 content/error를 명시 확인하도록 추후 수정 권고- dry-run도 LLM을 호출하므로 sandboxed surface에서 실행 금지
- 운영 cron은 현재 production 모드이므로 실제 알림 전송 가능. 강제 분석이 필요할 때만
--force수동 사용
6. 통합 원인 관계
sandboxed manual/surface/isolated Python
→ macOS seatbelt network-outbound deny
→ localhost 18789/11434 접속 EPERM
→ Hermes/Ollama 모두 실패
→ shared.llm 체인 전체 실패
→ bond_daily_report / commodity_spike_analyzer LLM 단계 실패
bond는 여기에 “황대진 원천 노트 260408 이후 없음”이 추가 원인으로 붙음.
commodity는 “LLM 실패를 완료처럼 기록하는 로그/반환 처리”가 추가 관측성 문제로 붙음.
7. 다음 조치
- EPERM 재발 시 같은 sandbox 안에서 재시도하지 말고 unsandboxed 실행으로 전환
bond-morning-poll의 Gmail 수집 로그와 실제 황대진 메일 존재 여부 확인commodity_spike_analyzer.py는 추후 별도 작업으로 LLM 실패 감지/로그를 수정- Hermes 재로드 후에는 health + Python preflight 통과 전까지 LLM 작업 투입 금지
- cron
sessionTarget: isolated작업이 실제로 어떤 sandbox 권한으로 실행되는지 별도 추적 필요
8. 자체 평가
| 기준 | 점수 | 근거 |
|---|---|---|
| 정확성 | 4.7/5 | 재현, asuser 대조, macOS deny 로그, 코드/cron/log 대조 완료 |
| 완성도 | 4.6/5 | EPERM·bond·commodity 모두 통합 정리. Gmail 원문 존재 여부는 미확인으로 분리 |
| 검증 | 4.7/5 | 실제 포트, 권한, 로그, 코드 경로, cron 설정 확인 |
| 최소 변경 | 5.0/5 | 코드 수정/재시작/파괴 작업 없음. 보고서만 작성 |
종합: 4.75/5