virtual-insanity
← 리포트 목록

Hermes scheduler tick lock + cron status fix

2026-04-14 hermes

Hermes scheduler .tick.lock + hermes cron status 수정 보고

발견

  • 증상 1: 18789 포트는 LISTEN인데 hermes cron status가 게이트웨이 미실행으로 판단했다.
  • 증상 2: .tick.lock을 Hermes gateway 프로세스가 오래 잡아 두 번째 tick/cycle이 lock_busy로 건너뛰었다.
  • 확인 증거:
  • 기존 PID 45753/Users/ron/.hermes/cron/.tick.lock을 잡고 있었음.
  • 기존 로그에 장시간 LLM/tool 작업 중 gateway가 계속 진행 중인 흔적이 있었음.
  • scheduler_state.jsonstatus=skipped_lock_busy, last_tick_error=lock_busy가 기록됨.

원인

1) tick lock

  • cron/scheduler.py의 기존 tick()fcntl lock을 잡은 상태에서 run_job(job)까지 실행했다.
  • try/finally 자체는 있었으므로 “락 해제 누락”이라기보다 락 범위가 너무 넓은 설계였다.
  • LLM/tool 호출이 오래 걸리면, scheduler 예약용 lock이 실제 작업 실행 시간 전체를 막아 다음 tick이 진행되지 않았다.
  • 기존에는 5분 이상 점유 같은 stale timeout/자동 해제 정책이 없었다.

2) cron status

  • hermes_cli/cron.pyfind_gateway_pids() 기반으로만 gateway 실행 여부를 판정했다.
  • 실제 gateway는 127.0.0.1:18789에서 LISTEN 중이었지만, 프로세스명 패턴 탐지에 걸리지 않아 “gateway not running”으로 오판했다.

수정

백업 파일:

  • /Users/ron/.hermes/hermes-agent/cron/scheduler.py.bak-lockfix-20260414143005
  • /Users/ron/.hermes/hermes-agent/hermes_cli/cron.py.bak-statusfix-20260414143005
  • /Users/ron/.hermes/hermes-agent/hermes_cli/gateway.py.bak-statusfix-20260414143005

수정 파일:

  1. /Users/ron/.hermes/hermes-agent/cron/scheduler.py
  2. 66-73: lock/state 파일 상수와 기본 timeout 추가
    • HERMES_CRON_LOCK_STALE_SECONDS=300 기본값
    • HERMES_CRON_SCHEDULER_STALE_SECONDS=180 기본값
  3. 76-166: lock metadata 기록, lock age 측정, stale lock break, unlock helper 추가
  4. 176-246: scheduler health 상태 파일(scheduler_state.json) 저장/조회 추가
  5. 944-1049: tick() 구조 변경

    • lock은 due job 예약(advance_next_run) 동안만 유지
    • 실제 run_job()은 lock 해제 후 실행
    • 모든 경로에서 scheduler state 기록
  6. /Users/ron/.hermes/hermes-agent/hermes_cli/gateway.py

  7. 34-58: socket.create_connection() 기반 포트 health 확인 추가
  8. socket이 sandbox/권한 문제로 실패하는 경우 lsof -iTCP:18789 -sTCP:LISTEN fallback
  9. 63-115: runtime status + HTTP health + scheduler health를 합친 gateway_runtime_status() 추가

  10. /Users/ron/.hermes/hermes-agent/hermes_cli/cron.py

  11. 95-101: cron list 경고를 새 runtime status 기반으로 변경
  12. 110-135: cron status를 PID 패턴 단독 판단에서 HTTP API + scheduler health 기반으로 변경

금지 범위 확인:

  • shared/llm.py 수정 없음.
  • ~/.openclaw/* 수정 없음.

검증

구문 검증

cd /Users/ron/.hermes/hermes-agent
/Users/ron/.hermes/hermes-agent/venv/bin/python -m py_compile cron/scheduler.py hermes_cli/cron.py hermes_cli/gateway.py

결과: py_compile_ok

stale lock 자동 해제 검증

테스트 방식:

  • 별도 프로세스가 .tick.lock을 잡도록 만들고,
  • HERMES_CRON_LOCK_STALE_SECONDS=1로 낮춰 tick() 실행.

결과:

WARNING:Breaking stale cron tick lock: /Users/ron/.hermes/cron/.tick.lock age=2.2s threshold=1s
INFO:14:32:28 - No jobs due
executed 0 elapsed 0.01

의미:

  • stale lock이 timeout 이후 자동으로 끊겼다.
  • 새 tick은 lock 때문에 장시간 멈추지 않았다.

live gateway/status 검증

최종 확인 시각: 2026-04-14 14:38:31 KST.

/Users/ron/.local/bin/hermes cron status

결과:

✓ Gateway is running — cron jobs will fire automatically
  HTTP API: 127.0.0.1:18789 healthy
  Scheduler: tick healthy

  7 active job(s)
  Next run: 2026-04-14T22:10:00+09:00

LISTEN 확인:

Python  63520  ron ... TCP 127.0.0.1:18789 (LISTEN)

lock holder 최종 확인:

lsof /Users/ron/.hermes/cron/.tick.lock

결과: 출력 없음 — 현재 lock을 오래 잡고 있는 프로세스 없음.

scheduler state 최종 확인:

{
  "status": "ok",
  "last_tick_error": null,
  "last_tick_executed": 0,
  "last_tick_reserved": 0,
  "pid": 63520
}

참고:

  • hermes gateway start는 launchctl 권한/입출력 오류로 실패했다. 이후 hermes gateway run을 백그라운드로 수동 실행해 patched gateway를 PID 63520으로 띄웠고, 18789 LISTEN + cron status 정상으로 재확인했다. (nice(5) failed 경고는 sandbox 권한 경고이며 gateway 실행에는 영향 없음.)

현재 상태

  • .tick.lock 장시간 점유 문제는 코드상 두 겹으로 방어됨.
  • lock 범위 축소: job 실행 전 lock 해제
  • stale timeout: 기본 300초 이상 점유 lock 자동 break
  • hermes cron status는 더 이상 프로세스명 스캔만 믿지 않고, HTTP API 포트 + runtime status + scheduler health로 판정한다.
  • 최종 hermes cron status는 정상 응답.

잔존 리스크

  • 실행 중인 gateway 프로세스가 오래된 코드를 물고 있으면 패치가 즉시 반영되지 않는다. 이번에는 최종 PID 63520이 새 gateway 코드로 18789 LISTEN 상태이며, cron status가 patched health 경로로 정상 확인됐다.
  • direct socket connect가 제한된 shell에서 실패할 수 있어 lsof fallback을 넣었다. 이 fallback은 로컬 CLI health 표시용이다.
  • stale lock break는 lock file inode를 교체한다. 기존 holder가 매우 늦게 종료되면 자기 fd만 unlock하므로 새 lock에는 영향이 없게 설계했지만, 동일 job 중복 실행 방지는 advance_next_run() 예약 순서에 의존한다.

자체 평가

  • 정확성: 4.8/5 — 원인 확인 후 lock 범위/timeout/status 판정 모두 수정했고 live status 정상 확인.
  • 완성도: 4.7/5 — 코드 패치, 백업, 구문검사, live 검증 완료.
  • 검증: 4.7/5 — py_compile, stale lock simulation, LISTEN, status, lock holder 확인 완료.
  • 최소 변경: 4.5/5 — Hermes 범위에만 수정. scheduler health state 추가로 변경량은 다소 있지만 status 신뢰성 확보에 필요.

종합: 4.7/5

최종 재확인 추가 로그

2026-04-14 14:38:31 KST 기준:

Python  63520  ron ... TCP 127.0.0.1:18789 (LISTEN)

✓ Gateway is running — cron jobs will fire automatically
  HTTP API: 127.0.0.1:18789 healthy
  Scheduler: tick healthy

  7 active job(s)
  Next run: 2026-04-14T22:10:00+09:00

.tick.lock holder: 없음.