24h+ stale 크론 복구 REVISE 보완 조사
결론: 조사·문서화만 수행했다. 크론 수정/재실행/rollback 없음.
Critic 지적은 유효하다.
daily-context-snapshot은 실제 stale 전체를 저장하지 않고stale[:40]으로 잘라서, 보고서에는 40개만 노출됐다.- 현재 직접 계산한 활성 24h+ stale은 146개다.
pipeline-self-healer수동 실행은 범위 밖 job 14건의 상태/배송 필드를 변경했다.- 즉시 전체 rollback은 권장하지 않는다. 현재
jobs.json에는 이후 다른 작업의 변경도 섞여 있어, 필요 시 선택적 rollback만 해야 한다. - 플레이북과 회귀 경보 샘플을 새로 저장했다.
1. 참조한 원본/비평 보고서
- 원본 daily snapshot 보고서:
~/knowledge-agent/400-reports/260414_pipeline_daily-context-snapshot.md - 원본 stale 복구 보고서:
~/knowledge-agent/400-reports/260414_stale_cron_audit.md - Critic 보고서:
~/knowledge-agent/400-reports/260414_critic_stale_by_A.md - self-healer 실행 로그:
~/.openclaw/workspace/memory/cron-runs/stale-cron-audit-260414/pipeline-self-healer.log - jobs patch report:
~/.openclaw/workspace/memory/cron-runs/stale-cron-audit-260414/jobs_patch_report.json
2. 40개로 숨겨진 이유
확인 코드: ~/.openclaw/workspace/scripts/pipeline/daily_context_snapshot.py
문제 지점:
daily_context_snapshot.py:57:stale = []에 전체 stale 누적daily_context_snapshot.py:69: enabled이고lastRunAtMs가 없거나 24h 초과면 stale 추가daily_context_snapshot.py:85:"enabled_stale_over_24h": stale[:40]daily_context_snapshot.py:157: markdown에서len(cron['enabled_stale_over_24h'])를 출력
즉, JSON에는 전체 개수가 아니라 앞 40개 샘플만 저장하고, markdown은 그 샘플 길이 40을 전체 수처럼 출력한다.
정확한 구조는 아래처럼 분리해야 한다.
enabled_stale_over_24h_total: 전체 개수enabled_stale_over_24h_sample: 잘린 샘플- markdown에는
total을 출력하고 sample cap을 명시
이번 작업에서는 코드 수정 금지 조건 때문에 수정하지 않았다.
3. 숨긴 stale 146개 전수 추출 결과
전수 산출물:
- CSV:
~/knowledge-agent/400-reports/260414_stale_cron_revise_full_stale_146.csv - JSON:
~/knowledge-agent/400-reports/260414_stale_cron_revise_full_stale_146.json - Markdown 전체표:
~/knowledge-agent/400-reports/260414_stale_cron_revise_full_stale_146.md
검증:
- CSV line count: 147줄 = header 1 + stale 146
- Markdown 표: 146개 전체 행 저장
- 계산 기준:
enabled=true이고lastRunAtMs없음 또는 마지막 실행 후 24h 초과
요약 카운트
| 구분 | 개수 |
|---|---|
| 전체 jobs | 295 |
| 활성 jobs | 157 |
| 활성 24h+ stale | 146 |
nextRunAtMs가 이미 과거인 실제 overdue |
140 |
| 다음 실행이 미래라 단순 24h 기준에만 걸린 것 | 4 |
| nextRun 없음 | 2 |
기간별 분류
| 기간 | 개수 |
|---|---|
| 1일 이상 ~ 1주일 미만 | 128 |
| 1주일 이상 | 11 |
| never-run | 7 |
원인별 분류
요구된 4종(never-run, paused, error, killed)만으로는 전체를 정직하게 설명할 수 없었다. 146개 중 136개는 마지막 상태가 ok인데도 다음 실행이 밀린 상태라 ok-but-stale로 별도 분류했다. 이를 강제로 error나 killed로 넣으면 거짓 분류가 된다.
| 원인 | 개수 | 설명 |
|---|---|---|
ok-but-stale |
136 | 마지막 실행은 ok. 그러나 24h+ 미실행이고 대부분 nextRunAtMs도 과거. scheduler/ticker 정지, due-run 누락, agentTurn 실행 경로 문제 가능성이 큼 |
never-run |
7 | 활성인데 lastRunAtMs 없음 |
schema-skipped |
2 | payload/sessionTarget 규격 불일치로 skip |
error |
1 | 마지막 상태가 error |
paused |
0 | 146개는 활성 stale 기준이라 paused 없음 |
killed |
0 | jobs 상태만으로 OOM/SIGKILL/timeout 확정 흔적 없음 |
payload/sessionTarget 분포
| 항목 | 개수 |
|---|---|
agentTurn |
124 |
systemEvent |
19 |
command |
1 |
shell |
1 |
script |
1 |
isolated target |
126 |
main target |
20 |
4. 146개 중 즉시 주의할 stale 유형
never-run 7개
전체 목록은 full list 파일에 있다. 대표적으로:
- id 없음 —
방법론 피드백 수신 - id 없음 —
GitNexus 재인덱싱 urea-price-trackerblueprint-updatermorning-briefingmemory-weekly-reportchroma-mcp-recycle
schema-skipped 2개
cron-alert:sessionTarget=isolated,payload.kind=script, lastError=isolated job requires payload.kind=agentTurndomain-wiki-compiler:sessionTarget=main,payload.kind=shell, lastError=main job requires payload.kind="systemEvent"
error 1개
price-history-collector 가격 이력 수집 (3회/일): lastError=Delivering to Telegram requires target <chatId>
ok-but-stale 136개
대부분 여기에 몰려 있다. 마지막 실행은 성공이지만 nextRunAtMs가 과거인 job이 많다. 이는 개별 스크립트 실패보다 scheduler/ticker due-run 경로가 멈췄거나, agentTurn 정규 실행이 밀린 문제일 가능성이 크다.
5. 범위 외 14개 self-healer 변경 diff
산출물:
~/knowledge-agent/400-reports/260414_stale_cron_self_healer_out_of_scope_14.csv
비교 기준:
- before:
~/.openclaw/cron/jobs.json.healer-bak.1776128971 - after 근거:
~/.openclaw/cron/jobs.json.bak및 self-healer stdout - 현재 파일:
~/.openclaw/cron/jobs.json
14건 요약:
| index | id | 변경 | 현재 판단 |
|---|---|---|---|
| 50 | technical-data-collector |
delivery chatId/to 보정 | 기능상 유해해 보이지 않음. rollback 낮음 |
| 85 | goal_alignment_daily |
delivery chatId/to 보정 | rollback 낮음 |
| 99 | analyst-quality-tracker |
delivery chatId/to 보정 | rollback 낮음 |
| 109 | notion-analyst-sync |
delivery chatId/to 보정 | rollback 낮음 |
| 140 | commodity-spike-watch |
delivery chatId/to 보정 | rollback 낮음 |
| 153 | hypothesis_lifecycle |
delivery chatId/to 보정 | rollback 낮음 |
| 177 | economic-calendar-collect |
delivery chatId/to 보정 | rollback 낮음 |
| 236 | price-history-collector |
state error → idle, consecutiveErrors 3→0, lastError 제거 | audit/alert 의미상 선택적 rollback 검토 |
| 237 | evening-briefing |
delivery chatId 보정 | rollback 낮음 |
| 244 | 6h-content-summary |
delivery chatId 보정 | rollback 낮음 |
| 269 | technical-stat-models 기술통계모델 |
state error → idle, consecutiveErrors 3→0, lastError 제거 | 선택적 rollback 검토 |
| 273 | technical-stat-models |
state error → idle, consecutiveErrors 3→0, lastError 제거 | 선택적 rollback 검토 |
| 274 | technical-stat-models 기술통계모델 |
state error → idle, consecutiveErrors 3→0, lastError 제거 | 선택적 rollback 검토 |
| 285 | price-history-collector |
delivery chatId 보정 | rollback 낮음 |
6. 원상복구 가능 여부 판단
즉시 전체 rollback: 비권장
이유:
jobs.json.healer-bak.1776128971로 통째로 되돌리면 10:09 이후의 다른 변경까지 같이 날아간다.- 현재
jobs.json은 10:32 이후 oil cron 관련 변경도 반영된 상태다. - 이번 작업 지시는 크론 수정 금지이므로 실제 rollback은 하지 않았다.
선택적 rollback: 가능
가능한 대상:
- index
236,269,273,274의state필드
이 4건은 self-healer가 consecutiveErrors를 0으로 만들고 lastError를 지워서, 실패 이력/경보 의미가 흐려졌다. 운영상 “이미 ok라 놔둬도 됨”은 아니다. 다만 현재도 일부는 lastRunStatus=error가 남아 있으므로, 무조건 긴급 rollback보다는 승인 후 선택적 state 복구가 맞다.
권장 판단:
- delivery 보정 10건: 유지 가능. Telegram 대상 보정이라 기능상 유해 가능성 낮음.
- state reset 4건: 감사/경보 정확성을 중시하면 선택적 rollback 권장. 단, 해리 승인 전에는 실행 금지.
수동 rollback 절차는 플레이북에 저장했다.
7. 플레이북 저장
저장 완료:
~/knowledge-agent/500-signals/stale-cron-recovery-playbook.md
포함 내용:
- 조사 전 백업 순서
- 40개 cap 우회 방식
- schema drift 검사
- self-healer 실행 금지/격리
- 복구 우선순위
- 선택적 rollback 절차
- 회귀 경보 샘플 사용법
8. 회귀 테스트 샘플
저장 완료:
~/knowledge-agent/500-signals/stale-cron-regression-check.py
검증 실행:
python3 ~/knowledge-agent/500-signals/stale-cron-regression-check.py --threshold 20 --hours 24 --json
결과:
- exit code:
2— threshold 초과 경보 - active stale count:
146 - cause counts:
ok-but-stale=136,never-run=7,schema-skipped=2,error=1
이 스크립트는 read-only다. jobs.json 수정, 크론 실행, 재시작을 하지 않는다.
9. 다음 수정이 승인될 경우 우선순위
이번 작업에서는 금지 조건 때문에 실행하지 않았다. 실제 복구가 승인되면 순서는 다음이 맞다.
daily_context_snapshot.py의 cap 버그 수정: total/sample 분리.- schema-skipped 2개 우선 수정.
- never-run 7개 중 id 없는 2개와 nextRun 없는 2개 정리.
ok-but-stale136개는 개별 job 문제가 아니라 scheduler/ticker 경로 문제로 보고, Hermes cron due-run 로그를 먼저 확인.- self-healer state reset 4건은 승인 후 선택적 rollback 여부 결정.
- 회귀 경보 스크립트를 운영화하려면 먼저 기존 동일 목적 cron 존재 여부를
~/.openclaw/cron/jobs.json에서 확인.
10. 자체 평가
- 정확성: 4.8/5 — 원본/Critic/코드/jobs 상태를 직접 대조했고 146개 전수 파일을 남김.
- 완성도: 4.8/5 — 플레이북, 회귀 샘플, diff CSV, full stale CSV/JSON/MD까지 저장.
- 검증: 4.8/5 — 회귀 샘플을 실제 실행해 threshold 초과 exit code 2 확인.
- 최소 변경: 5.0/5 — 크론/코드 수정 없음. knowledge-agent 문서와 샘플만 작성.
종합: 4.85/5