virtual-insanity
← 리포트 목록

Critic 리뷰 — stale 크론 복구 작업

2026-04-14 critic [openclaw, cron, critic, stale-jobs]

Critic 리뷰 — stale 크론 복구 작업

승인 여부: REVISE

핵심 원인 진단(isolated 크론은 agentTurn만 실행 가능)은 코드와 맞다. 그러나 완료 보고의 검증은 수동 스크립트 실행 성공정규 Hermes cron 경로 성공으로 과대 해석했고, 같은 패턴/스키마 드리프트/부작용을 충분히 닫지 못했다. 원 담당(Codex C)은 아래 결함을 고친 뒤 재검증해야 한다.

검토 대상: - /Users/ron/knowledge-agent/400-reports/260414_stale_cron_audit.md - /Users/ron/knowledge-agent/400-reports/260414_pipeline_daily-context-snapshot.md - /Users/ron/.openclaw/cron/jobs.json - /Users/ron/.openclaw/workspace/src/cron/** - /Users/ron/.openclaw/workspace/scripts/pipeline/** - /Users/ron/.openclaw/workspace/memory/cron-runs/stale-cron-audit-260414/**


A~E 전수 점검

항목 판정 근거
A1. 원인과 코드 일치 보고서의 원인 주장: 260414_stale_cron_audit.md:12. 실제 코드도 src/cron/service/jobs.ts:77-83에서 isolatedagentTurn만 허용하고, 런타임은 src/cron/service/timer.ts:773-775에서 아니면 skipped를 반환한다. 도구 설명도 src/agents/tools/cron-tool.ts:258-260와 일치한다.
A2. 같은 패턴 누락 여부 grep -rn 및 JSON 파싱 결과, 여전히 enabled + isolated + non-agentTurn가 남아 있다: cron-alertjobs.json:6529, payload scriptjobs.json:6542; memory-weekly-reportjobs.json:9956, payload commandjobs.json:9971. 비활성까지 포함하면 ops-auto-healerjobs.json:6079, payload shelljobs.json:6088. 중복 id도 blueprint-updaterjobs.json:9389jobs.json:9422에 남아 있다.
A3. 과거 유사 버그 검색 git log --grep에서 e3a059a0f7 feat: add cron_validator for payload schema enforcement, 4e93941b63 fix: check agentTurn and shell kinds..., 53973f06b4 fix: isolated 세션 크론잡의 agentTurn→shell 잘못된 변환 방지 확인. 메모리/로그에도 2026-04-12 vault_architect silent failure detected: enabled cron not executing for 15 days, Root cause found: vault_architect cron jobs explicitly disabled... 기록이 있다. 다만 보고서에는 이 과거 패턴 반영이 약하다.
B4. 근본 해결 vs 증상 ⚠️ 대상 11개는 agentTurn으로 맞췄지만 전체 cron 품질 문제는 남았다. 현재 파싱 기준 enabled + isolated + non-agentTurn 2개가 남아 있고, agentTurn인데 message가 없는 활성 isolated 잡이 87개다. src/cron/types.ts:57-62message 필수, timer.ts:780-783job.payload.message만 넘긴다.
B5. 엣지 케이스 빈/None id, 대량 stale, 동시 쓰기, 네트워크 단절, 의미적 실패(rc=0 but data fail) 처리가 불충분하다. 상세 엣지 케이스는 아래 별도 섹션.
B6. 실패 모드/새 실패 경로 china-macro-collector는 로그상 [PMI] FAIL - no data, [Housing] FAIL - no data인데 보고서와 상태는 ok로 처리했다(china-macro-collector.log:27-36). pipeline-self-healer 수동 실행은 수정 완료: 14건으로 범위 밖 상태를 변경했다(pipeline-self-healer.log:4-22). 정규 cron 경로가 아니라 “agent에게 쉘 명령 실행을 요청하는 프롬프트”라 명령 실행 보장이 약하다.
B7. 롤백 ⚠️ 백업은 있다: /Users/ron/.openclaw/cron/jobs.json.bak.260414-stale-cron-1776129106, self-healer 백업도 /Users/ron/.openclaw/cron/jobs.json.healer-bak.1776128971. 그러나 롤백 절차/검증은 보고서에 없다.
C8. 다른 호출자 영향 웹 관리자 수동 실행 경로는 payload.command를 요구한다: scripts/pipeline/webapp/blueprints/admin.py:461-483, :692-696, :726-730. Codex C가 변환한 대상 예: daily-kpi-evaljobs.json:664-667message만 있고 command가 없다. 즉 Hermes cron 기준은 맞췄지만 웹 관리자 수동 실행은 깨질 수 있다.
C9. 성능 영향 ⚠️ 직접 shell 실행이 아니라 isolated agentTurn이므로 새 에이전트 세션/LLM 호출 비용이 추가된다. 기존 보고서 중 /Users/ron/knowledge-agent/400-reports/260405_claude_token_efficiency.md:31도 isolated agentTurn 133회를 비용 이슈로 기록한다. 이번 11개 전환의 latency/토큰/동시성 영향 측정은 없다.
C10. 보안 ⚠️ 새 payload는 “명령을 실행하라”는 자연어 프롬프트다. 외부 입력을 직접 넣은 정황은 없지만, shell command를 deterministic runner가 아니라 agent에게 맡기는 공격면/오동작면이 생긴다. 또한 web admin 경로는 여전히 command를 실행하는 별도 schema를 기대해 schema 혼재가 남았다.
D11. 수정 코드 실제 실행 ⚠️ 대상 스크립트들은 수동 실행됐다(summary.json, 각 .log). 하지만 변경된 cron spec이 Hermes ticker에서 실제 due-run으로 실행됐다는 증거는 없다. daily-context-snapshotlastManualRunLog가 실행 로그가 아니라 결과 JSON(jobs.json:5908-5915)이고, lastDurationMs가 0이다. Gateway pid 43519 생존은 memory_guardian.log:11153-11186에서 보이나, 내가 확인한 범위에서는 Cron ticker started 문자열은 찾지 못했다.
D12. Before/After 메트릭 보고서는 “대상 11개 ok”만 제시한다. daily-context-snapshot.py:85가 stale 목록을 stale[:40]로 자르고, markdown도 len(cron['enabled_stale_over_24h'])만 출력한다(daily_context_snapshot.py:157). 현재 직접 계산한 활성 stale 실제값은 146개인데 스냅샷은 40개로 보인다. 전체 stale 개선 메트릭이 없다.
D13. 회귀 테스트 script/shell + isolated로 의도적으로 되돌렸을 때 skip이 재현되는 테스트, 또는 변환 후 due-run 성공을 잡는 테스트가 없다. 기존 TS 테스트는 있으나 이번 jobs.json 데이터 회귀를 검증하지 않았다.
E14. 재현성 ⚠️ 보고서에 증거 파일 경로는 있다(260414_stale_cron_audit.md:65-70). 하지만 적용 diff, 실행 명령, 검증 명령, “정규 cron 재실행을 어떻게 확인했는지”가 부족해 다음 사람이 동일 결론을 재현하기 어렵다.
E15. 접근 근거/대안 ⚠️ isolated + agentTurn 선택 근거는 코드로 뒷받침된다. 그러나 대안(직접 shell runner 유지, main + systemEvent, web admin schema와 통합, validator 우선 수리)은 검토되지 않았다.
E16. 플레이북 저장 find /Users/ron/knowledge-agent/500-signals -type f \( -iname '*cron*' -o -iname '*stale*' -o -iname '*playbook*' \) 결과 없음. 체크리스트 요구 미충족.

결함 목록 — Codex C에게 넘길 수정 요구사항

  1. 남은 활성 스펙 불일치 크론 2개 처리 필요
  2. 파일/라인: /Users/ron/.openclaw/cron/jobs.json:6529, :6542 (cron-alert, payload.kind=script, sessionTarget=isolated)
  3. 파일/라인: /Users/ron/.openclaw/cron/jobs.json:9956, :9971 (memory-weekly-report, payload.kind=command, sessionTarget=isolated)
  4. 재현: bash python3 - <<'PY' import json jobs=json.load(open('/Users/ron/.openclaw/cron/jobs.json'))['jobs'] print([(j.get('id'), (j.get('payload') or {}).get('kind')) for j in jobs if j.get('enabled', True) and j.get('sessionTarget')=='isolated' and (j.get('payload') or {}).get('kind')!='agentTurn']) PY
  5. 기대: 빈 리스트.

  6. agentTurn 스키마 drift: message 필수 vs 다수 잡의 command 사용

  7. 코어 타입/런타임: src/cron/types.ts:57-62, src/cron/service/timer.ts:780-783.
  8. 현재 데이터 예: /Users/ron/.openclaw/cron/jobs.json:4 (vault-note-atomizer) 등 활성 isolated agentTurn인데 message 없는 잡 87개.
  9. 관련 호출자 충돌: 웹 관리자 수동 실행은 command 필수(scripts/pipeline/webapp/blueprints/admin.py:461-483, :692-696, :726-730). 반대로 이번 변환 대상 daily-kpi-evalmessage만 있음(jobs.json:664-667).
  10. 재현: bash python3 - <<'PY' import json jobs=json.load(open('/Users/ron/.openclaw/cron/jobs.json'))['jobs'] print(sum(1 for j in jobs if j.get('enabled', True) and j.get('sessionTarget')=='isolated' and (j.get('payload') or {}).get('kind')=='agentTurn' and not (j.get('payload') or {}).get('message'))) PY
  11. 요구: Hermes cron schema와 web admin schema를 하나로 정리하거나, 양쪽 호환 변환/validator를 명시적으로 적용.

  12. china-macro-collector 성공 판정이 거짓 양성

  13. 파일/라인: /Users/ron/.openclaw/workspace/memory/cron-runs/stale-cron-audit-260414/china-macro-collector.log:27-36.
  14. 증거: [PMI] FAIL - no data, [Housing] FAIL - no data, china_pmi: {}, china_housing: {}.
  15. 재현: 해당 로그 확인 또는 summary.jsonstdout_tail 확인.
  16. 요구: rc=0만 성공으로 보지 말고 핵심 산출물 필드/데이터 개수를 성공 조건에 포함.

  17. pipeline-self-healer 수동 실행이 범위 밖 상태를 변경함

  18. 파일/라인: 로그 /Users/ron/.openclaw/workspace/memory/cron-runs/stale-cron-audit-260414/pipeline-self-healer.log:4-22.
  19. 증거: 수정 완료: 14건, delivery chatId 보정, 여러 failure count 리셋.
  20. 코드상 동작: scripts/pipeline/pipeline_self_healer.py:44-69이 상태 리셋/배송 필드 보정을 수행, :104-111이 jobs.json을 저장.
  21. 요구: stale 복구 검증 대상과 별도 부작용으로 분리 기록하고, before/after 오염을 제거한 재검증 필요.

  22. daily-context-snapshot 메트릭이 전체 stale 수를 숨김

  23. 파일/라인: scripts/pipeline/daily_context_snapshot.py:85에서 stale[:40], :157에서 그 길이를 “24h 이상 미실행 활성 크론”으로 출력.
  24. 현재 직접 계산: 활성 stale 실제값 146개, 스냅샷 보고값 40개.
  25. 재현: 위 Python stale 계산 또는 latest.jsoncron.enabled_stale_over_24h 길이 확인.
  26. 요구: stale_totalstale_sample을 분리.

  27. 정규 Hermes cron 경로 검증 부족

  28. 보고서 주장: 260414_stale_cron_audit.md:56 “Cron ticker started 로그 확인”.
  29. 내가 확인한 증거: memory_guardian.log:11153-11186에서 pid 43519 생존은 확인. 그러나 targeted grep에서 Cron ticker started 문자열은 확인하지 못함.
  30. 요구: 다음 due time을 강제로 앞당긴 1개 테스트 또는 cron service API/로그로 실제 agentTurn due-run이 완료되는 증거를 남길 것.

  31. 중복 id가 남아 있음

  32. 파일/라인: blueprint-updater/Users/ron/.openclaw/cron/jobs.json:9389:9422에 동시에 존재.
  33. 재현: bash python3 - <<'PY' import json, collections ids=[j.get('id') for j in json.load(open('/Users/ron/.openclaw/cron/jobs.json'))['jobs']] print({k:v for k,v in collections.Counter(ids).items() if v>1}) PY
  34. 요구: signal-synthesizer만 고치고 중복 id 검사를 끝내지 말 것. None id 3개도 별도 정리 대상.

  35. 회귀 테스트와 플레이북 누락

  36. 체크리스트 D13/E16 실패.
  37. 재현: knowledge-agent/500-signals에서 *cron*, *stale*, *playbook* 검색 결과 없음.
  38. 요구: jobs.json validator 또는 최소 재현 스크립트를 500-signals/playbook으로 저장하고, isolated + script/shell/command가 실패하는 테스트를 남길 것.

엣지 케이스 최소 5개

  1. 빈/잘못된 jobs 구조: daily_context_snapshot.py:48-52는 빈 리스트로 떨어지지만, 실제 복구 스크립트가 jobs 구조 오류를 어떻게 롤백하는지 보고서에 없다.
  2. id=None: 현재 jobs.json에 id가 없는 잡 3개가 중복 카운트된다. dedup/상태 추적/수동 실행에서 식별자가 사라진다.
  3. 대량 stale: daily_context_snapshot.py:85가 40개까지만 저장하고 :157은 그 길이를 전체값처럼 출력한다. 현재 실제 stale 146개를 40개로 축소 표시한다.
  4. 동시성/쓰기 경합: Codex C 패치, self-healer, Hermes cron이 모두 같은 jobs.json을 만질 수 있다. self-healer는 atomic replace를 쓰지만, 전체 복구 작업은 락/mtime 검증이 보고되지 않았다. 현재 jobs.json mtime도 보고서 이후로 갱신되어 상태가 흔들릴 수 있다.
  5. 네트워크 단절: Telegram/DNS 실패가 여러 로그에 있는데 rc=0이라 성공 처리된다. china-macro-collector는 데이터 수집 자체도 모두 실패했다.
  6. 의미적 성공 조건: generated=0, 빈 dict, 업데이트 0개 같은 “정상일 수도 실패일 수도 있는” 상태가 rc=0만으로 성공 처리된다.
  7. web admin 수동 실행 호환성: Hermes cron은 message, web admin은 command를 요구한다. 이번 변환 대상은 web admin 수동 실행 버튼에서 “실행할 수 없는 타입”으로 떨어질 가능성이 높다.
  8. agentTurn 프롬프트 실행: shell 명령이 deterministic runner가 아니라 agent 지시문 안에 들어가므로, agent가 설명만 하고 실행하지 않는 경우가 생길 수 있다.

같은 패턴 grep 결과 요약

수행한 필수 grep/파싱:

grep -rn "isolated job requires payload.kind=agentTurn\|isolated cron jobs require\|sessionTarget=\"isolated\" REQUIRES\|payload.kind=agentTurn" \
  /Users/ron/.openclaw/workspace/src /Users/ron/.openclaw/workspace/scripts

주요 결과: - src/agents/tools/cron-tool.ts:260 - src/cron/service/jobs.ts:82 - src/cron/service/timer.ts:774 - src/cron/service.runs-one-shot-main-job-disables-it.test.ts:742

추가 파싱 결과: - 변경 전 활성 isolated non-agentTurn: 3개 → 변경 후 2개. - 변경 전 활성 isolated agentTurn missing message: 95개 → 변경 후 87개. - 변경 전 중복 id: {None: 3, signal-synthesizer: 2, blueprint-updater: 2} → 변경 후 {None: 3, blueprint-updater: 2}.


REVISE 기준 완료 조건

Codex C가 다시 제출하려면 최소 아래를 만족해야 한다.

  1. 활성 isolated 잡 중 payload.kind != agentTurn가 0개임을 파싱 결과로 제시.
  2. agentTurn payload의 message/command schema 충돌을 정책으로 결정하고, Hermes cron과 web admin 양쪽에서 수동/정규 실행이 되는지 검증.
  3. china-macro-collector 같은 데이터 실패를 rc=0 성공으로 포장하지 않도록 성공 조건을 강화.
  4. daily-context-snapshot의 stale 총수/샘플 분리 또는 보고서에서 cap을 명시.
  5. 다음 due-run 또는 테스트 잡으로 실제 Hermes cron path 실행 로그를 제시.
  6. rollback 절차와 500-signals 플레이북을 저장.