virtual-insanity
← 리포트 목록

Phase 17 REVISE — webapp repair part2 Round 3

2026-04-15 revise [phase17, revise, webapp, round3]

결론

Round 3에서 확인/조치한 상태는 다음과 같다.

  1. live webapp 재시작은 실패했다.
  2. launchctl 기준 webapp PID는 73943으로 확인됨.
  3. 재시작 명령과 TERM fallback 모두 sandbox 권한에 막힘.
  4. live curl도 sandbox localhost 제한으로 실패.
  5. 따라서 Critic이 요구한 “live PID 재시작 후 curl 400/200” 증거는 이 세션에서 확보 불가.
  6. source/app factory 기준 endpoint는 여전히 통과한다.
  7. days=abc → 400
  8. days=0 → 400
  9. days=30 → 200
  10. days=90 → 200
  11. price-history job의 LLM 의존 문제는 구조적으로 우회했다.
  12. 현재 Hermes job ocPH-SPY-price-history-refreshtype/job_type/kind = shell, model/provider = null, prompt = ""이다.
  13. scheduler의 _is_shell_job()_run_shell_job() 경로로 직접 실행되는 것을 검증했다.
  14. request dump 수는 실행 전/후 동일해서 새 Copilot request dump가 생성되지 않았다.
  15. SPY 데이터는 stale 50행 → fresh 51행으로 갱신했다.
  16. 로컬 DNS가 막혀 yfinance와 StockAnalysis fallback의 로컬 fetch는 실패했다.
  17. 대신 현재 세션의 웹 조회로 StockAnalysis의 2026-04-14 SPY 행을 확인한 뒤, 해당 행을 SPY.json에 반영했다.
  18. source: https://stockanalysis.com/etf/spy/history/
  19. 마지막 row: 2026-04-14, close 694.46, volume 63,480,529.
  20. 연쇄 영향
  21. Copilot 403 request dump는 총 25개 발견.
  22. cron job으로 식별된 것은 ocPH-SPY-price-history-refresh 1건과 41c2736f0527 5건.
  23. 활성 Hermes jobs.json 자체에는 copilot/gpt-5-mini 명시 job은 0건이다. 즉 기본 provider 경로가 Copilot으로 떨어지는 LLM cron이 영향권이다.

1. live webapp PID + 재시작 시도 원문

=== 2026-04-15 10:49:19 KST round3 initial webapp/gateway state ===
--- run_webapp ps ---
zsh:7: operation not permitted: ps
--- launchctl webapp ---
gui/501/com.openclaw.sihwang-webapp = {
    active count = 1
    path = /Users/ron/Library/LaunchAgents/com.openclaw.sihwang-webapp.plist
    type = LaunchAgent
    state = running

    program = /usr/bin/python3
    arguments = {
        /usr/bin/python3
        /Users/ron/.openclaw/workspace/scripts/pipeline/run_webapp.py
    }

    working directory = /Users/ron/.openclaw/workspace/scripts/pipeline
    runs = 4
    pid = 73943
    last terminating signal = Terminated: 15
}
--- listen ports ---
COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  73943  ron    4u  IPv4 0x9fdead4aa19f042c      0t0  TCP *:8080 (LISTEN)
COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  41269  ron   21u  IPv4 0x49eb4a93c543c1be      0t0  TCP 127.0.0.1:18789 (LISTEN)
=== 2026-04-15 10:49:28 KST webapp restart attempt ===
before:
    state = running
    runs = 4
    pid = 73943
    last terminating signal = Terminated: 15
cmd: launchctl kickstart -k gui/501/com.openclaw.sihwang-webapp
Could not kickstart service "com.openclaw.sihwang-webapp": 1: Operation not permitted
after:
    state = running
    runs = 4
    pid = 73943
    last terminating signal = Terminated: 15
listen:
COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  73943  ron    4u  IPv4 0x9fdead4aa19f042c      0t0  TCP *:8080 (LISTEN)
=== 2026-04-15 10:49:36 KST webapp TERM fallback attempt ===
cmd: /bin/kill -TERM 73943
kill: 73943: Operation not permitted
    state = running
    runs = 4
    pid = 73943
    last terminating signal = Terminated: 15
COMMAND   PID USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
Python  73943  ron    4u  IPv4 0x9fdead4aa19f042c      0t0  TCP *:8080 (LISTEN)

run_webapp.py는 debug off이므로 재시작 없이는 자동 반영을 기대하면 안 된다.

if __name__ == "__main__":
    if app is None:
        app = build_app()
    app.run(host="0.0.0.0", port=8080, debug=False)

2. live curl 원문

재시작 실패 후에도 curl을 시도했지만 sandbox localhost 접근이 막혔다.

=== 2026-04-15 10:49:52 KST live curl retry with absolute sed ===
--- /api/chart/SPY?days=abc ---
curl: (7) Failed to connect to 127.0.0.1 port 8080 after 0 ms: Couldn't connect to server
--- /api/chart/SPY?days=0 ---
curl: (7) Failed to connect to 127.0.0.1 port 8080 after 0 ms: Couldn't connect to server
--- /api/chart/SPY?days=30 ---
curl: (7) Failed to connect to 127.0.0.1 port 8080 after 0 ms: Couldn't connect to server

이 세션에서 live 증거를 닫으려면 비-sandbox shell에서 아래 4줄이 필요하다.

launchctl kickstart -k gui/$(id -u)/com.openclaw.sihwang-webapp
curl -i 'http://127.0.0.1:8080/api/chart/SPY?days=abc'
curl -i 'http://127.0.0.1:8080/api/chart/SPY?days=0'
curl -i 'http://127.0.0.1:8080/api/chart/SPY?days=30'

3. source/test_client endpoint 확인 원문

=== 2026-04-15 10:54:07 KST source endpoint test_client after fresh SPY row ===
/api/chart/SPY?days=abc -> 400 application/json bytes= 69 body= {"error":"invalid days","message":"days must be a positive integer"} 
/api/chart/SPY?days=0 -> 400 application/json bytes= 69 body= {"error":"invalid days","message":"days must be a positive integer"} 
/api/chart/SPY?days=30 -> 200 application/json bytes= 3523 body= {"data":[{"close":680.33,"high":682.61,"low":669.66,"ma20":686.65,"ma200":null,"ma60":null,"open":675.06,"time":"2026-03-03"},{"close":685.13,"high":687.09,"low":679.62,"ma20":686.43,"ma200":null,"ma60":null,"open":681.6
  parsed SPY 30 2026-03-03 {'close': 694.46, 'high': 694.58, 'low': 687.66, 'ma20': 660.63, 'ma200': None, 'ma60': None, 'open': 687.69, 'time': '2026-04-14'}
/api/chart/SPY?days=90 -> 200 application/json bytes= 5934 body= {"data":[{"close":691.97,"high":694.21,"low":687.12,"ma20":null,"ma200":null,"ma60":null,"open":691.79,"time":"2026-01-30"},{"close":695.41,"high":696.93,"low":689.42,"ma20":null,"ma200":null,"ma60":null,"open":689.58,"t
  parsed SPY 51 2026-01-30 {'close': 694.46, 'high': 694.58, 'low': 687.66, 'ma20': 660.63, 'ma200': None, 'ma60': None, 'open': 687.69, 'time': '2026-04-14'}

4. price-history job 정의 위치 + LLM 의존 원인

정의 파일:

  • /Users/ron/.hermes/cron/jobs.json

현재 정의 핵심:

{
  "id": "ocPH-SPY-price-history-refresh",
  "name": "price-history SPY refresh",
  "job_type": "shell",
  "type": "shell",
  "kind": "shell",
  "command": "python3 /Users/ron/.openclaw/workspace/scripts/pipeline/price_history_collector.py --ticker SPY",
  "cwd": "/Users/ron/.openclaw/workspace",
  "timeout_seconds": 600,
  "prompt": "",
  "model": null,
  "provider": null,
  "enabled": true,
  "next_run_at": "2026-04-15T12:00:00+09:00",
  "last_status": "ok"
}

Copilot 403 dump:

request.url : 'https://api.githubcopilot.com/chat/completions'
request.body.model : 'gpt-5-mini'
error : {'type': 'PermissionDeniedError', 'message': 'Access to this endpoint is forbidden. Please review our [Terms of Service](https://docs.github.com/en/site-policy/github-terms/github-terms-of-service).', 'status_code': 403, ...}

해석:

  • 09:15의 실패 dump는 이 job이 shell로 실행되지 않고 일반 prompt-cron처럼 AIAgent 경로를 탔다는 증거다.
  • 당시 요청 본문에는 price_history_collector.py command가 없고 빈 prompt + SOUL/system prompt만 들어갔다.
  • 정상 아키텍처가 아니다. price-history 수집은 deterministic script이며 LLM 호출이 필요 없다.
  • 현재 scheduler에는 shell job 분기가 존재한다.

관련 scheduler 코드 위치:

296:def _is_shell_job(job: dict) -> bool:
311:def _run_shell_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
344:        proc = subprocess.run(
408:def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
    if _is_shell_job(job):
        return _run_shell_job(job)

    from run_agent import AIAgent

5. 모델 폴백/우회 조치

이번 job에는 모델 폴백보다 LLM 완전 우회가 맞다.

조치:

  • ocPH-SPY-price-history-refreshshell job으로 유지.
  • model/provider/base_url = null.
  • prompt = "".
  • scheduler 직접 호출로 _is_shell_job=True와 request dump 미증가를 확인했다.

검증 원문:

=== 2026-04-15 10:52:43 KST direct scheduler.run_job no-LLM check with hermes venv ===
request_dumps_before=1
is_shell_job= True
success= True
error= None
# Script Cron Job: price-history SPY refresh

**Job ID:** ocPH-SPY-price-history-refresh
**Kind:** shell
**Run Time:** 2026-04-15 10:52:43
**Schedule:** 0 7,12,18 * * 1-5
**Command:** `python3 /Users/ron/.openclaw/workspace/scripts/pipeline/price_history_collector.py --ticker SPY`
**CWD:** `/Users/ron/.openclaw/workspace`
**Timeout:** 600s
**Exit Code:** 0
**Duration:** 0.36s
...
request_dumps_after=1

6. 신규 fetch / fresh 데이터 조치

6-1. collector fallback 추가

수정 파일:

  • /Users/ron/.openclaw/workspace/scripts/pipeline/price_history_collector.py

백업:

  • /Users/ron/.openclaw/workspace/scripts/pipeline/price_history_collector.py.bak-round3-20260415T105321

추가 내용:

  • yfinance 실패 시 StockAnalysis fallback 시도.
  • fallback 함수:
  • fetch_stockanalysis_history(ticker)
  • fetch_stockanalysis_since(ticker, start_date)

구문 검사 및 로컬 실행:

=== 2026-04-15 10:53:45 KST collector StockAnalysis fallback py_compile/run ===
py_compile exit=0
=== 가격 이력 수집 시작 [증분] — 1개 티커 ===
저장 경로: /Users/ron/.openclaw/workspace/memory/price-history

  [SPY] 증분 수집 (마지막: 2026-04-13)...
  [WARN] SPY yfinance 수집 실패: 'NoneType' object is not subscriptable
  [SPY] StockAnalysis fallback 시도...
  [WARN] SPY fallback 실패 — 기존 50일치 유지: <urlopen error [Errno 8] nodename nor servname provided, or not known>

=== 완료 — 성공 1/1, 실패 0 ===
collector exit=0
SPY file: last_updated= 2026-04-14 records= 50 last= {'date': '2026-04-13', 'open': 677.41, 'high': 686.3, 'low': 676.58, 'close': 686.1, 'volume': 54185819, 'adj_close': 686.1}

로컬 네트워크/DNS 제한 증거:

=== 2026-04-15 10:50:36 KST external price data probe ===
--- https://stooq.com/q/d/l/?s=spy.us&i=d&d1=20260401&d2=20260415 ---
curl: (6) Could not resolve host: stooq.com

--- https://query1.finance.yahoo.com/v8/finance/chart/SPY?period1=1775001600&period2=1776211200&interval=1d ---
curl: (6) Could not resolve host: query1.finance.yahoo.com

6-2. fresh row 반영

현재 세션의 웹 조회로 StockAnalysis 페이지에서 2026-04-14 행을 확인했다.

반영 원문:

=== 2026-04-15 10:53:58 KST apply StockAnalysis Apr 14 row from fetched source ===
before= (50, {'date': '2026-04-13', 'open': 677.41, 'high': 686.3, 'low': 676.58, 'close': 686.1, 'volume': 54185819, 'adj_close': 686.1}, '2026-04-14')
applied_row= {'date': '2026-04-14', 'open': 687.69, 'high': 694.58, 'low': 687.66, 'close': 694.46, 'volume': 63480529, 'adj_close': 694.46}
after= (51, {'date': '2026-04-14', 'open': 687.69, 'high': 694.58, 'low': 687.66, 'close': 694.46, 'volume': 63480529, 'adj_close': 694.46}, '2026-04-15')
verify records= 51 last_updated= 2026-04-15 last= {'date': '2026-04-14', 'open': 687.69, 'high': 694.58, 'low': 687.66, 'close': 694.46, 'volume': 63480529, 'adj_close': 694.46}

주의: 로컬 collector가 직접 네트워크 fetch에 성공한 것은 아니다. 이 세션의 shell DNS가 막혀 있어, 현재 데이터 갱신은 Codex 웹 조회로 확인한 외부 source row를 반영한 것이다. 다만 collector에는 같은 source를 직접 읽는 fallback 코드를 추가했으므로, DNS가 정상인 runtime에서는 yfinance 실패 시 StockAnalysis fallback으로 자동 보강된다.

7. copilot 403 연쇄 영향 cron 목록

스캔 원문:

=== 2026-04-15 10:52:09 KST copilot 403 impact scan ===
hit_count= 25
job= ocPH-SPY-price-history-refresh | file= /Users/ron/.hermes/sessions/request_dump_cron_ocPH-SPY-price-history-refresh_20260415_091559_20260415_091605_477567.json
job= 41c2736f0527 | file= /Users/ron/.hermes/sessions/request_dump_cron_41c2736f0527_20260415_090044_20260415_090051_113628.json
job= 41c2736f0527 | file= /Users/ron/.hermes/sessions/request_dump_cron_41c2736f0527_20260409_090025_20260409_090031_661584.json
job= 41c2736f0527 | file= /Users/ron/.hermes/sessions/request_dump_cron_41c2736f0527_20260408_090027_20260408_090038_712157.json
job= 41c2736f0527 | file= /Users/ron/.hermes/sessions/request_dump_cron_41c2736f0527_20260406_090054_20260406_090208_792467.json
job= 41c2736f0527 | file= /Users/ron/.hermes/sessions/request_dump_cron_41c2736f0527_20260407_090011_20260407_090024_337102.json
--- jobs referencing copilot/gpt-5-mini ---
FILE /Users/ron/.hermes/cron/jobs.json
count= 0
FILE /Users/ron/.openclaw/cron/jobs.json
cowork-lite | 코워크 라이트 (3시간 주기) | enabled= False | model= None
cowork-full | 코워크 풀 (새벽 심층 분석) | enabled= False | model= None
1abfbacd-02a9-424f-ad39-1bce30a21112 | technical-stat-models 기술통계모델 | enabled= False | model= github-copilot/gpt-5-mini
755caea6-4f80-4a24-89d1-27ea059d79ec | technical-stat-models | enabled= False | model= github-copilot/gpt-5-mini
e27b3e01-7e58-440f-98aa-849f767d89f9 | price-history-collector 가격 이력 수집 | enabled= False | model= github-copilot/gpt-5-mini
0969c044-8afd-40a7-af21-b4eb3819d55c | technical-stat-models | enabled= False | model= github-copilot/gpt-5-mini
count= 6

영향 정리:

job 상태 영향
ocPH-SPY-price-history-refresh 현재 shell 우회 완료 09:15 dump 1건만 잔존. 현재 직접 실행은 request dump 증가 없음.
41c2736f0527 (vault-analyst-feedback) active LLM cron 04-06, 04-07, 04-08, 04-09, 04-15에 Copilot 403 dump 존재. 별도 provider 우회 필요.
OpenClaw technical-stat-models 계열 disabled 지금 당장 운영 영향 없음.
OpenClaw duplicate price-history collector disabled 지금 당장 운영 영향 없음.

8. 남은 리스크 / 다음 한 단계

  1. live webapp reload는 아직 미완료
  2. 권한 문제로 PID 73943 재시작 불가.
  3. Critic의 live curl 조건은 이 세션에서 충족 못 했다.
  4. 비-sandbox shell에서 launchctl kickstart -k gui/$(id -u)/com.openclaw.sihwang-webapp 실행 필요.
  5. collector의 직접 네트워크 fetch는 현재 shell에서 실패
  6. yfinance와 StockAnalysis fallback 모두 DNS 실패.
  7. DNS 정상 runtime에서는 새 fallback이 작동할 수 있지만, 이 sandbox에서는 검증 불가.
  8. Hermes gateway 상태 표시 불일치
  9. lsof에는 PID 41269가 18789 LISTEN.
  10. launchctl print gui/501/ai.hermes.gateway는 not running으로 나왔다.
  11. gateway를 죽이지 말라는 지시가 있어 추가 재시작은 하지 않았다.
  12. 41c2736f0527는 별도 수정 필요
  13. active LLM cron이 Copilot 403을 반복 중이다.
  14. 이번 범위에서는 목록화만 하고 수정하지 않았다.

자체평가

  • 정확성: 4.0/5 — source/test_client와 SPY 데이터 freshness는 해결했지만 live restart/curl은 권한상 미완료.
  • 완성도: 4.0/5 — price-history LLM 우회와 fresh row 반영 완료. 단, collector의 직접 network fetch는 DNS 때문에 실패.
  • 검증: 4.0/5 — PID/listen, restart failure, curl failure, test_client, scheduler no-LLM, data row update 모두 원문 기록. live curl 성공은 없음.
  • 최소 변경: 4.5/5 — chart guard는 건드리지 않았고, collector fallback과 SPY 데이터 보강만 수행.

종합: 4.1/5.