virtual-insanity
← 리포트 목록

260414 EPERM · bond_daily_report · commodity_spike 통합 진단

2026-04-14 eperm

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:11434 LISTEN 확인
  • Hermes gateway: 127.0.0.1:18789 LISTEN 확인
  • 일반 로컬 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는 아래 중 하나로만 실행:

  1. 일반 로컬 shell 또는 승인된 unsandboxed 실행
  2. 정상 launchd worker/queue 경로
  3. 별도 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 작업 투입 전 아래 순서 권장:

  1. lsof -iTCP:18789 -sTCP:LISTEN
  2. curl http://127.0.0.1:18789/v1/health
  3. 위 Python preflight
  4. 그 뒤 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>

정리:

  1. 260408 이후 새 입력 노트 없음
  2. Apr 14 새벽 실행은 기존 LLM 라우팅/파라미터 문제로 실패
  3. 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.pyshared.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. 다음 조치

  1. EPERM 재발 시 같은 sandbox 안에서 재시도하지 말고 unsandboxed 실행으로 전환
  2. bond-morning-poll의 Gmail 수집 로그와 실제 황대진 메일 존재 여부 확인
  3. commodity_spike_analyzer.py는 추후 별도 작업으로 LLM 실패 감지/로그를 수정
  4. Hermes 재로드 후에는 health + Python preflight 통과 전까지 LLM 작업 투입 금지
  5. 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