fix: runtime anomaly handling for overnight startup and monitor (#396 #397) #404

Merged
jihoson merged 6 commits from feature/issue-396-397-runtime-anomaly-fixes into main 2026-03-04 02:46:38 +09:00
Collaborator

Summary

  • Harden scripts/run_overnight.sh startup path with dashboard port preflight and startup grace liveness checks
  • Ensure app PID tracks the real process by launching via exec in the nohup shell
  • Make scripts/runtime_verify_monitor.sh detect live runtime via process fallback (pgrep) even without run_overnight pid files
  • Add regression tests for runtime monitor fallback, dashboard-port conflict fail-fast, and overnight pid/watchdog liveness

Traceability

  • REQ-ID: REQ-RUNTIME-ANOMALY-396
  • TASK-ID: TASK-RUNTIME-ANOMALY-397
  • TEST-ID: TEST-RUNTIME-ANOMALY-396-397

Validation

  • ruff check tests/test_runtime_overnight_scripts.py
  • bash -n scripts/run_overnight.sh scripts/runtime_verify_monitor.sh
  • pytest -q tests/test_runtime_overnight_scripts.py

Issues

## Summary - Harden `scripts/run_overnight.sh` startup path with dashboard port preflight and startup grace liveness checks - Ensure app PID tracks the real process by launching via `exec` in the nohup shell - Make `scripts/runtime_verify_monitor.sh` detect live runtime via process fallback (`pgrep`) even without `run_overnight` pid files - Add regression tests for runtime monitor fallback, dashboard-port conflict fail-fast, and overnight pid/watchdog liveness ## Traceability - REQ-ID: REQ-RUNTIME-ANOMALY-396 - TASK-ID: TASK-RUNTIME-ANOMALY-397 - TEST-ID: TEST-RUNTIME-ANOMALY-396-397 ## Validation - ruff check tests/test_runtime_overnight_scripts.py - bash -n scripts/run_overnight.sh scripts/runtime_verify_monitor.sh - pytest -q tests/test_runtime_overnight_scripts.py ## Issues - Closes #396 - Closes #397
agentson added 1 commit 2026-03-04 02:04:21 +09:00
fix: stabilize overnight startup and monitor live fallback (#396 #397)
Some checks failed
Gitea CI / test (push) Failing after 37s
Gitea CI / test (pull_request) Failing after 38s
528e17a29c
agentson added 1 commit 2026-03-04 02:07:53 +09:00
fix: make overnight startup portable in CI environments
All checks were successful
Gitea CI / test (push) Successful in 34s
Gitea CI / test (pull_request) Successful in 34s
370ee8cc85
Author
Collaborator

Code Review: PR #404 - Runtime Anomaly Fixes (#396, #397)


Strengths

  • 정확한 이슈 타겟팅: #396, #397의 acceptance criteria를 모두 충족 (포트 충돌 처리, pid/liveness 불일치 수정, tmux/non-tmux 동작 통일)
  • find_live_pids fallback 설계: pgrep self-exclusion 트릭([s]rc.main)으로 grep 자기 자신 매칭 방지. 단일 책임, 명확한 격리
  • 포트 충돌 fast-fail: run_overnight.sh L51-54 — 타임스탬프 로그 + non-zero exit. 이슈 #396 재현 증거와 정확히 대응
  • Grace-period liveness check: kill -0으로 app_pid, watchdog_pid 양쪽 확인. STARTUP_GRACE_SEC 환경변수로 CI 튜닝 가능
  • MAX_LOOPS escape hatch: L10, L63-66 — 기본값 0(비활성). 벽시계 시간 없이 모니터 테스트 가능한 올바른 설계
  • 테스트가 실제 셸 스크립트 실행: mock 없이 subprocess로 실제 코드 경로 검증. tmp_path 격리, finally 블록 정리. 3개 모두 통과
  • dashboard_port 스코프 수정: 이전에는 if 블록 안에서만 설정되어 외부 APP_CMD 제공 시 포트 체크에서 사용 불가했던 버그를 L12로 호이스팅해 수정

Issues

Important (Should Fix)

1. exec env $APP_CMD — 공백 포함 경로에서 word-splitting 취약

  • run_overnight.sh L57
  • APP_CMD에 공백이 포함된 경로/인수가 있으면 잘못된 분리 발생
  • 현재 내부 생성 명령이라 당장 위험하진 않지만 패턴 자체가 취약함
  • 권장: VAR=value 인라인 prefix를 env VAR=val binary 형식으로 분리하거나, "경로에 공백 불가" 가정을 명시적 주석으로 기록

2. is_port_in_usess 명령 의존, portability 주장과 불일치

  • run_overnight.sh L37-40
  • ss는 POSIX 아님 (iproute2 필요). 커밋 메시지 "make overnight startup portable in CI environments"와 불일치
  • 최소 Docker 이미지, BSD 기반 CI에서 ss 없으면 silently false 반환 → 포트 충돌 미감지
  • 권장: nc -z 또는 /proc/net/tcp fallback 추가, 또는 커밋 메시지를 "Linux CI" 한정으로 수정

3. 프로세스 alive 상태지만 run log 없을 때 false-positive [ANOMALY] 발생

  • runtime_verify_monitor.sh L101-113
  • app_alive=1이고 latest_run이 비어있을 때 LIVE_MODE=PASS이지만, 이후 coverage 블록에서 not_observed를 6 증가시켜 [ANOMALY] coverage_not_observed=6 로그 발생
  • 영향: 막 시작해서 첫 run log가 아직 없는 프로세스가 첫 모니터 사이클에서 항상 ANOMALY로 표시됨 → 불필요한 운영 노이즈
  • 권장: app_alive=1 + latest_run 비어있음 조건에서 coverage 6개 체크를 DEFERRED reason=no_run_log_process_alive로 처리하거나 생략

Minor (Nice to Have)

4. 모니터 테스트에 [ANOMALY] 부재 assertion 미포함

  • test_runtime_overnight_scripts.py L51-52
  • app_alive=1, LIVE_MODE=PASS 확인하지만 [ANOMALY] 문자열 부재는 미검증
  • Important 이슈 #3이 있어도 현재 테스트는 통과함
  • 권장: assert "[ANOMALY]" not in log_text 추가

5. Grace-period 실패 경로 테스트 누락

  • test_runtime_overnight_scripts.py L90-124
  • 정상 경로(grace period 동안 프로세스 생존)만 테스트, 실패 경로(grace period 전에 프로세스 종료) 미테스트
  • 권장: APP_CMD="false", STARTUP_GRACE_SEC=1로 즉시 종료 케이스 추가, returncode != 0 + "startup failed" in output 검증

6. 모니터에 포트 하드코딩 (:8080)

  • runtime_verify_monitor.sh L82: ss -ltnp 2>/dev/null | rg -q ':8080'
  • run_overnight.sh는 이제 DASHBOARD_PORT 변���를 사용하지만 모니터는 여전히 하드코딩
  • 권장: 모니터 스크립트 상단에 DASHBOARD_PORT="${DASHBOARD_PORT:-8080}" 추가 후 참조

Assessment

머지 가능 여부: 조건부 Yes

핵심 수정 (포트 충돌 감지, grace-period liveness check, pid scoping 수정, 프로세스 기반 liveness fallback)은 모두 올바르게 구현되어 있고 테스트도 통과함. Important 이슈 #3(첫 run log 생성 전 false-positive ANOMALY)은 기능 자체를 깨진 않지만 운영 노이즈 발생 가능 — 머지 후 follow-up 이슈로 추적 권장.

Follow-up으로 추적할 항목: 이슈 #3 (false-positive anomaly), 이슈 #6 (하드코딩 포트)

## Code Review: PR #404 - Runtime Anomaly Fixes (#396, #397) --- ### Strengths - **정확한 이슈 타겟팅**: #396, #397의 acceptance criteria를 모두 충족 (포트 충돌 처리, pid/liveness 불일치 수정, tmux/non-tmux 동작 통일) - **`find_live_pids` fallback 설계**: `pgrep` self-exclusion 트릭(`[s]rc.main`)으로 grep 자기 자신 매칭 방지. 단일 책임, 명확한 격리 - **포트 충돌 fast-fail**: `run_overnight.sh` L51-54 — 타임스탬프 로그 + non-zero exit. 이슈 #396 재현 증거와 정확히 대응 - **Grace-period liveness check**: `kill -0`으로 app_pid, watchdog_pid 양쪽 확인. `STARTUP_GRACE_SEC` 환경변수로 CI 튜닝 가능 - **`MAX_LOOPS` escape hatch**: L10, L63-66 — 기본값 0(비활성). 벽시계 시간 없이 모니터 테스트 가능한 올바른 설계 - **테스트가 실제 셸 스크립트 실행**: mock 없이 subprocess로 실제 코드 경로 검증. `tmp_path` 격리, `finally` 블록 정리. 3개 모두 통과 - **`dashboard_port` 스코프 수정**: 이전에는 `if` 블록 안에서만 설정되어 외부 `APP_CMD` 제공 시 포트 체크에서 사용 불가했던 버그를 L12로 호이스팅해 수정 --- ### Issues #### Important (Should Fix) **1. `exec env $APP_CMD` — 공백 포함 경로에서 word-splitting 취약** - `run_overnight.sh` L57 - `APP_CMD`에 공백이 포함된 경로/인수가 있으면 잘못된 분리 발생 - 현재 내부 생성 명령이라 당장 위험하진 않지만 패턴 자체가 취약함 - **권장**: `VAR=value` 인라인 prefix를 `env VAR=val binary` 형식으로 분리하거나, "경로에 공백 불가" 가정을 명시적 주석으로 기록 **2. `is_port_in_use` — `ss` 명령 의존, portability 주장과 불일치** - `run_overnight.sh` L37-40 - `ss`는 POSIX 아님 (`iproute2` 필요). 커밋 메시지 "make overnight startup portable in CI environments"와 불일치 - 최소 Docker 이미지, BSD 기반 CI에서 `ss` 없으면 silently `false` 반환 → 포트 충돌 미감지 - **권장**: `nc -z` 또는 `/proc/net/tcp` fallback 추가, 또는 커밋 메시지를 "Linux CI" 한정으로 수정 **3. 프로세스 alive 상태지만 run log 없을 때 false-positive `[ANOMALY]` 발생** - `runtime_verify_monitor.sh` L101-113 - `app_alive=1`이고 `latest_run`이 비어있을 때 `LIVE_MODE=PASS`이지만, 이후 coverage 블록에서 `not_observed`를 6 증가시켜 `[ANOMALY] coverage_not_observed=6` 로그 발생 - **영향**: 막 시작해서 첫 run log가 아직 없는 프로세스가 첫 모니터 사이클에서 항상 ANOMALY로 표시됨 → 불필요한 운영 노이즈 - **권장**: `app_alive=1` + `latest_run` 비어있음 조건에서 coverage 6개 체크를 `DEFERRED reason=no_run_log_process_alive`로 처리하거나 생략 --- #### Minor (Nice to Have) **4. 모니터 테스트에 `[ANOMALY]` 부재 assertion 미포함** - `test_runtime_overnight_scripts.py` L51-52 - `app_alive=1`, `LIVE_MODE=PASS` 확인하지만 `[ANOMALY]` 문자열 부재는 미검증 - Important 이슈 #3이 있어도 현재 테스트는 통과함 - **권장**: `assert "[ANOMALY]" not in log_text` 추가 **5. Grace-period 실패 경로 테스트 누락** - `test_runtime_overnight_scripts.py` L90-124 - 정상 경로(grace period 동안 프로세스 생존)만 테스트, 실패 경로(grace period 전에 프로세스 종료) 미테스트 - **권장**: `APP_CMD="false"`, `STARTUP_GRACE_SEC=1`로 즉시 종료 케이스 추가, `returncode != 0` + `"startup failed" in output` 검증 **6. 모니터에 포트 하드코딩 (`:8080`)** - `runtime_verify_monitor.sh` L82: `ss -ltnp 2>/dev/null | rg -q ':8080'` - `run_overnight.sh`는 이제 `DASHBOARD_PORT` 변���를 사용하지만 모니터는 여전히 하드코딩 - **권장**: 모니터 스크립트 상단에 `DASHBOARD_PORT="${DASHBOARD_PORT:-8080}"` 추가 후 참조 --- ### Assessment **머지 가능 여부: 조건부 Yes** 핵심 수정 (포트 충돌 감지, grace-period liveness check, pid scoping 수정, 프로세스 기반 liveness fallback)은 모두 올바르게 구현되어 있고 테스트도 통과함. Important 이슈 #3(첫 run log 생성 전 false-positive ANOMALY)은 기능 자체를 깨진 않지만 운영 노이즈 발생 가능 — 머지 후 follow-up 이슈로 추적 권장. **Follow-up으로 추적할 항목**: 이슈 #3 (false-positive anomaly), 이슈 #6 (하드코딩 포트)
agentson added 1 commit 2026-03-04 02:23:44 +09:00
fix: address PR #404 review feedback
All checks were successful
Gitea CI / test (push) Successful in 36s
Gitea CI / test (pull_request) Successful in 35s
3cde8779fa
Author
Collaborator

리뷰 코멘트 확인 후 반영 완료.

반영 항목:

  • run_overnight.sh
    • is_port_in_usess -> lsof -> netstat fallback 추가
    • APP_CMD 문자열 처리 가정(공백 경로는 내부에서 quote 필요) 명시
  • runtime_verify_monitor.sh
    • DASHBOARD_PORT 환경변수화(8080 하드코딩 제거)
    • app_alive=1 && run log 없음 케이스에서 log-based coverage를 DEFERRED로 처리
    • 해당 조건에서 불필요한 [ANOMALY] false-positive 제거
  • 테스트 보강 (tests/test_runtime_overnight_scripts.py)
    • live fallback 케이스에서 [ANOMALY] 미발생 검증 추가
    • grace-period 이전 프로세스 종료 실패 경로(APP_CMD=false) 테스트 추가

검증:

  • ruff check tests/test_runtime_overnight_scripts.py
  • bash -n scripts/run_overnight.sh scripts/runtime_verify_monitor.sh
  • pytest -q tests/test_runtime_overnight_scripts.py (4 passed)
리뷰 코멘트 확인 후 반영 완료. 반영 항목: - `run_overnight.sh` - `is_port_in_use`에 `ss -> lsof -> netstat` fallback 추가 - `APP_CMD` 문자열 처리 가정(공백 경로는 내부에서 quote 필요) 명시 - `runtime_verify_monitor.sh` - `DASHBOARD_PORT` 환경변수화(8080 하드코딩 제거) - `app_alive=1 && run log 없음` 케이스에서 log-based coverage를 `DEFERRED`로 처리 - 해당 조건에서 불필요한 `[ANOMALY]` false-positive 제거 - 테스트 보강 (`tests/test_runtime_overnight_scripts.py`) - live fallback 케이스에서 `[ANOMALY]` 미발생 검증 추가 - grace-period 이전 프로세스 종료 실패 경로(`APP_CMD=false`) 테스트 추가 검증: - `ruff check tests/test_runtime_overnight_scripts.py` - `bash -n scripts/run_overnight.sh scripts/runtime_verify_monitor.sh` - `pytest -q tests/test_runtime_overnight_scripts.py` (4 passed)
Author
Collaborator

Code Review (2차): PR #404 - 리뷰 반영 확인 (commit 3cde877)


이전 이슈 처리 현황

Issue #1: exec env $APP_CMD word-splitting 취약 — ⚠️ NOT ADDRESSED (주석만 추가)

  • 수행된 것: 주석을 "경로에 공백이 있으면 APP_CMD 안에서 직접 quote해야 한다"로 변경
  • 근본 문제는 그대로 — nohup bash -lc "exec env $APP_CMD" 패턴 미변경
  • 이전 리뷰에서 Important (Should Fix) 로 분류했으나 코드 수정 없이 주석으로만 처리
  • 결정 필요: 주석 acknowledge 수준으로 이 이슈를 close할지 팀 판단 요청

Issue #2: ss 명령 의존성 — RESOLVED

3단계 fallback 체인 도입:

ss (Linux) → lsof (macOS/BSD) → netstat (legacy)
  • netstat 패턴이 [:.]${port} 로 Linux(0.0.0.0:8080)와 macOS(*.8080) 형식 양쪽 처리 — 정확한 구현

Issue #3: false-positive [ANOMALY] RESOLVED

defer_log_checks 플래그 도입:

  • app_alive=1 + latest_run 없음 → [INFO] run log not yet available; defer log-based coverage checks
  • app_alive=0 + latest_run 없음일 때만 [ANOMALY] 발생
  • 논리적으로 올바름. (참고: L92-94의 조건이 [ -z "$latest_run" ] && [ "$app_alive" -eq 0 ]와 동치인데 defer_log_checks 변수를 경유하고 있어 가독성이 살짝 낮으나 버그 아님)

Issue #4: [ANOMALY] 부재 assertion — RESOLVED

assert "[ANOMALY]" not in log_text

추가 확인.


Issue #5: Grace-period 실패 경로 테스트 — RESOLVED

test_run_overnight_fails_when_process_exits_before_grace_period 추가:

  • APP_CMD=false, STARTUP_GRACE_SEC=1 — POSIX 즉시 종료 명령 사용으로 올바른 접근
  • returncode != 0 + "startup failed:" in output 검증 — 스크립트 L84 메시지와 정확히 일치

Issue #6: 포트 하드코딩 — RESOLVED

  • DASHBOARD_PORT="${DASHBOARD_PORT:-8080}" 환경변수화
  • :8080:${DASHBOARD_PORT}\b (\b word boundary 추가로 :18080 오매칭 방지까지)
  • HEARTBEAT 로그에도 포트 변수 반영

신규 발견 이슈 (수정 과정에서 도입)

Minor

A. runtime_verify_monitor.sh L83 — ss fallback 미적용 (일관성 불일치)

run_overnight.sh는 이번 수정으로 ss → lsof → netstat fallback을 받았지만, 모니터 스크립트는 여전히:

ss -ltnp 2>/dev/null | rg -q ":${DASHBOARD_PORT}\b" && port_alive=1

ss 없는 환경에서 port_alive 가 silently 0 으로 유지됨. Issue #2 에서 적용한 portability 수정이 모니터에는 전파되지 않음.

B. 새 테스트에서 watchdog 프로세스 누수

test_run_overnight_fails_when_process_exits_before_grace_periodAPP_CMD=false로 스크립트가 exit 1로 종료되지만, watchdog은 grace period 체크 전에 이미 nohup 백그라운드 실행됨. 스크립트의 startup failed 경로가 watchdog을 kill하지 않아 테스트 종료 후 watchdog 프로세스가 잔류 (기본 CHECK_INTERVAL 동안). 프로덕션 로직 버그는 아니지만 테스트 환경 오염 우려.


Assessment

머지 가능 여부: 조건부 Yes (Issue #1 팀 판단 후)

5개 이슈는 정확하게 수정되었고, 2개의 Minor 신규 이슈가 도입됨. 핵심 결정은 Issue #1: word-splitting 취약을 코드 수정 없이 주석 acknowledge만으로 close할지 여부. 나머지 신규 이슈(A, B)는 blocking 수준 아님.

## Code Review (2차): PR #404 - 리뷰 반영 확인 (commit `3cde877`) --- ### 이전 이슈 처리 현황 **Issue #1: `exec env $APP_CMD` word-splitting 취약 — ⚠️ NOT ADDRESSED (주석만 추가)** - 수행된 것: 주석을 "경로에 공백이 있으면 APP_CMD 안에서 직접 quote해야 한다"로 변경 - 근본 문제는 그대로 — `nohup bash -lc "exec env $APP_CMD"` 패턴 미변경 - 이전 리뷰에서 **Important (Should Fix)** 로 분류했으나 코드 수정 없이 주석으로만 처리 - **결정 필요**: 주석 acknowledge 수준으로 이 이슈를 close할지 팀 판단 요청 --- **Issue #2: `ss` 명령 의존성 — ✅ RESOLVED** 3단계 fallback 체인 도입: ```bash ss (Linux) → lsof (macOS/BSD) → netstat (legacy) ``` - `netstat` 패턴이 `[:.]${port}` 로 Linux(`0.0.0.0:8080`)와 macOS(`*.8080`) 형식 양쪽 처리 — 정확한 구현 --- **Issue #3: false-positive `[ANOMALY]` — ✅ RESOLVED** `defer_log_checks` 플래그 도입: - `app_alive=1` + `latest_run` 없음 → `[INFO] run log not yet available; defer log-based coverage checks` - `app_alive=0` + `latest_run` 없음일 때만 `[ANOMALY]` 발생 - 논리적으로 올바름. (참고: L92-94의 조건이 `[ -z "$latest_run" ] && [ "$app_alive" -eq 0 ]`와 동치인데 `defer_log_checks` 변수를 경유하고 있어 가독성이 살짝 낮으나 버그 아님) --- **Issue #4: `[ANOMALY]` 부재 assertion — ✅ RESOLVED** ```python assert "[ANOMALY]" not in log_text ``` 추가 확인. --- **Issue #5: Grace-period 실패 경로 테스트 — ✅ RESOLVED** `test_run_overnight_fails_when_process_exits_before_grace_period` 추가: - `APP_CMD=false`, `STARTUP_GRACE_SEC=1` — POSIX 즉시 종료 명령 사용으로 올바른 접근 - `returncode != 0` + `"startup failed:" in output` 검증 — 스크립트 L84 메시지와 정확히 일치 --- **Issue #6: 포트 하드코딩 — ✅ RESOLVED** - `DASHBOARD_PORT="${DASHBOARD_PORT:-8080}"` 환경변수화 - `:8080` → `:${DASHBOARD_PORT}\b` (`\b` word boundary 추가로 `:18080` 오매칭 방지까지) - HEARTBEAT 로그에도 포트 변수 반영 --- ### 신규 발견 이슈 (수정 과정에서 도입) #### Minor **A. `runtime_verify_monitor.sh` L83 — `ss` fallback 미적용 (일관성 불일치)** `run_overnight.sh`는 이번 수정으로 `ss → lsof → netstat` fallback을 받았지만, 모니터 스크립트는 여전히: ```bash ss -ltnp 2>/dev/null | rg -q ":${DASHBOARD_PORT}\b" && port_alive=1 ``` `ss` 없는 환경에서 `port_alive` 가 silently 0 으로 유지됨. Issue #2 에서 적용한 portability 수정이 모니터에는 전파되지 않음. **B. 새 테스트에서 watchdog 프로세스 누수** `test_run_overnight_fails_when_process_exits_before_grace_period` — `APP_CMD=false`로 스크립트가 exit 1로 종료되지만, watchdog은 grace period 체크 전에 이미 `nohup` 백그라운드 실행됨. 스크립트의 `startup failed` 경로가 watchdog을 kill하지 않아 테스트 종료 후 watchdog 프로세스가 잔류 (기본 `CHECK_INTERVAL` 동안). 프로덕션 로직 버그는 아니지만 테스트 환경 오염 우려. --- ### Assessment **머지 가능 여부: 조건부 Yes (Issue #1 팀 판단 후)** 5개 이슈는 정확하게 수정되었고, 2개의 Minor 신규 이슈가 도입됨. **핵심 결정은 Issue #1**: word-splitting 취약을 코드 수정 없이 주석 acknowledge만으로 close할지 여부. 나머지 신규 이슈(A, B)는 blocking 수준 아님.
agentson added 1 commit 2026-03-04 02:29:56 +09:00
fix: address second-round review findings on PR #404
All checks were successful
Gitea CI / test (push) Successful in 36s
Gitea CI / test (pull_request) Successful in 36s
c412412f7b
Author
Collaborator

추가 리뷰 코멘트 확인했고, 아래 항목을 코드로 반영 완료.

반영 내용:

  • run_overnight.sh
    • 기본 실행 경로를 shell string이 아닌 direct exec(env DASHBOARD_PORT=... "$PYTHON_BIN" ...)로 변경해 word-splitting 취약 최소화
    • startup 실패 시 남아있을 수 있는 watchdog/app 프로세스를 즉시 정리하도록 보완
  • runtime_verify_monitor.sh
    • 포트 liveness 검사에 ss -> lsof -> netstat fallback 함수(is_port_listening) 추가
  • tests/test_runtime_overnight_scripts.py
    • grace-period 실패 테스트에서 watchdog 잔류 프로세스가 없는지 검증 추가

검증:

  • ruff check tests/test_runtime_overnight_scripts.py
  • bash -n scripts/run_overnight.sh scripts/runtime_verify_monitor.sh
  • pytest -q tests/test_runtime_overnight_scripts.py (4 passed)
추가 리뷰 코멘트 확인했고, 아래 항목을 코드로 반영 완료. 반영 내용: - `run_overnight.sh` - 기본 실행 경로를 shell string이 아닌 direct exec(`env DASHBOARD_PORT=... "$PYTHON_BIN" ...`)로 변경해 word-splitting 취약 최소화 - startup 실패 시 남아있을 수 있는 watchdog/app 프로세스를 즉시 정리하도록 보완 - `runtime_verify_monitor.sh` - 포트 liveness 검사에 `ss -> lsof -> netstat` fallback 함수(`is_port_listening`) 추가 - `tests/test_runtime_overnight_scripts.py` - grace-period 실패 테스트에서 watchdog 잔류 프로세스가 없는지 검증 추가 검증: - `ruff check tests/test_runtime_overnight_scripts.py` - `bash -n scripts/run_overnight.sh scripts/runtime_verify_monitor.sh` - `pytest -q tests/test_runtime_overnight_scripts.py` (4 passed)
Author
Collaborator

추가 업데이트(c412412) 기준 재리뷰 완료.

결론:

  • blocking/important 신규 이슈 없음
  • 이전 2차 리뷰에서 지적된 항목(run_overnight startup 정리, monitor 포트체크 fallback, watchdog 누수 방지 테스트) 반영 확인

검증:

  • ruff check tests/test_runtime_overnight_scripts.py
  • bash -n scripts/run_overnight.sh scripts/runtime_verify_monitor.sh
  • pytest -q tests/test_runtime_overnight_scripts.py (4 passed)
  • pytest -q tests/test_runtime_overnight_scripts.py::test_run_overnight_fails_when_process_exits_before_grace_period 20회 반복 (PASS-20)

참고 리스크(비차단):

  • custom APP_CMD 경로는 bash -lc 문자열 파싱 계약을 유지하므로 공백/특수문자 포함 실행 경로는 호출 측 quoting이 필요함.
추가 업데이트(c412412) 기준 재리뷰 완료. 결론: - blocking/important 신규 이슈 없음 - 이전 2차 리뷰에서 지적된 항목(run_overnight startup 정리, monitor 포트체크 fallback, watchdog 누수 방지 테스트) 반영 확인 검증: - `ruff check tests/test_runtime_overnight_scripts.py` - `bash -n scripts/run_overnight.sh scripts/runtime_verify_monitor.sh` - `pytest -q tests/test_runtime_overnight_scripts.py` (4 passed) - `pytest -q tests/test_runtime_overnight_scripts.py::test_run_overnight_fails_when_process_exits_before_grace_period` 20회 반복 (PASS-20) 참고 리스크(비차단): - custom `APP_CMD` 경로는 `bash -lc` 문자열 파싱 계약을 유지하므로 공백/특수문자 포함 실행 경로는 호출 측 quoting이 필요함.
jihoson approved these changes 2026-03-04 02:35:06 +09:00
agentson added 1 commit 2026-03-04 02:36:25 +09:00
fix: add safer custom command path for run_overnight
All checks were successful
Gitea CI / test (push) Successful in 36s
Gitea CI / test (pull_request) Successful in 35s
dc0775cbc6
Author
Collaborator

추가 요청한 "반영" 항목 적용 완료.

변경사항:

  • run_overnight.sh
    • safer custom override 경로 추가: APP_CMD_BIN + APP_CMD_ARGS
    • 기본 경로뿐 아니라 custom 경로에서도 실행 바이너리를 단일 토큰으로 처리
    • 포트 충돌 preflight는 RUNS_DASHBOARD 플래그 기반으로 동작(기본/커스텀 공통)
  • tests/test_runtime_overnight_scripts.py
    • custom 실행 테스트를 APP_CMD 문자열 대신 APP_CMD_BIN/APP_CMD_ARGS 경로로 갱신

호환성:

  • 기존 APP_CMD(문자열) 경로는 그대로 유지(하위 호환)
  • 권장 경로는 APP_CMD_BIN/APP_CMD_ARGS

검증:

  • bash -n scripts/run_overnight.sh
  • ruff check tests/test_runtime_overnight_scripts.py
  • pytest -q tests/test_runtime_overnight_scripts.py (4 passed)
추가 요청한 "반영" 항목 적용 완료. 변경사항: - `run_overnight.sh` - safer custom override 경로 추가: `APP_CMD_BIN` + `APP_CMD_ARGS` - 기본 경로뿐 아니라 custom 경로에서도 실행 바이너리를 단일 토큰으로 처리 - 포트 충돌 preflight는 `RUNS_DASHBOARD` 플래그 기반으로 동작(기본/커스텀 공통) - `tests/test_runtime_overnight_scripts.py` - custom 실행 테스트를 `APP_CMD` 문자열 대신 `APP_CMD_BIN/APP_CMD_ARGS` 경로로 갱신 호환성: - 기존 `APP_CMD`(문자열) 경로는 그대로 유지(하위 호환) - 권장 경로는 `APP_CMD_BIN/APP_CMD_ARGS` 검증: - `bash -n scripts/run_overnight.sh` - `ruff check tests/test_runtime_overnight_scripts.py` - `pytest -q tests/test_runtime_overnight_scripts.py` (4 passed)
Author
Collaborator

Code Review (3차): PR #404 - 2차 리뷰 반영 확인 (commits c412412, dc0775c)


이전 이슈 처리 현황

Issue #1: exec env $APP_CMD word-splitting — ⚠️ PARTIAL

신규 APP_CMD_BIN / APP_CMD_ARGS 분리 인터페이스 도입으로 의미 있는 개선:

  • "$APP_CMD_BIN" 은 항상 quoted → 실행 파일 경로 공백 문제 해결
  • 배열 "${app_args[@]}" 로 인수 전달 → word-splitting 안전

단, 기존 legacy 브랜치는 여전히 잔존:

# run_overnight.sh L101-103
else
    # Custom APP_CMD is treated as a shell command string. ...
    nohup bash -lc "exec env $APP_CMD" >>"$RUN_LOG" 2>&1 &
fi

이 경로는 주석만 있고 코드 수정 없음. APP_CMD에 공백이 포함된 경로, $, 세미콜론 등 shell metacharacter가 있으면 여전히 위험.

추가 주의사항 — APP_CMD_ARGS 배열 할당:

# shellcheck disable=SC2206
app_args=( $APP_CMD_ARGS )

의도적 word-split임을 shellcheck disable로 표시했으나, APP_CMD_ARGS="--config '/path with spaces/config.toml'" 같은 내부 quoted 경로는 올바르게 처리되지 않음. 파일 어딘에도 이 제약이 문서화되지 않음 — 주석 추가 권장.

결론: 새 인터페이스 사용 시 안전. legacy APP_CMD 경로는 "use at your own risk" escape hatch로 명시적 backward-compatibility 수단이 된 것으로 이해된다면 acceptable.


Minor A: runtime_verify_monitor.sh L83 ss fallback — RESOLVED

is_port_listening 함수 도입 (L55-71):

ss → lsof → netstat

run_overnight.sh와 동일한 3단계 fallback. L101에서 is_port_listening "$DASHBOARD_PORT" 호출로 교체. 정확한 구현.

(참고: check_forbidden L47에서 rg는 여전히 fallback 없이 사용 중 — 이번 diff 범위 밖이지만 일관성 관점에서 언급)


Minor B: 테스트에서 watchdog 프로세스 잔류 — RESOLVED

스크립트 startup failed 경로에서 watchdog kill 추가 (L118):

[ -n "${watchdog_pid:-}" ] && kill "$watchdog_pid" 2>/dev/null || true

테스트에서 watchdog PID 파일 읽어 os.kill(watchdog_pid, 0)ProcessLookupError 확인으로 프로세스 종료 검증. 올바른 접근.

사소한 관찰:

  • if watchdog_pid_file.exists() 가드가 있어 PID 파일 없으면 assertion silently skip. 실제로 이 경로에서 PID 파일은 항상 존재하므로 practical 문제는 없지만, 엄격히는 assert watchdog_pid_file.exists() 를 먼저 두는 게 더 명확함
  • PID 재사용 race condition (ESRCH 발생 전 PID 재할당) 이론상 가능하나 테스트 duration 상 무시 가능

신규 발견 이슈 (이번 수정에서 도입)

Minor

C. legacy APP_CMD 경로에서 DASHBOARD_PORT 암묵적 누락

기존 코드는 APP_CMD 문자열 자체에 DASHBOARD_PORT=$dashboard_port를 포함했지만, 이번 리팩토링 후 default/safe 경로는 env DASHBOARD_PORT="$dashboard_port" 로 명시 주입, legacy 경로만 주입 없음:

# L103 — DASHBOARD_PORT 미주입
nohup bash -lc "exec env $APP_CMD" >>"$RUN_LOG" 2>&1 &

legacy APP_CMD 사용자가 스크립트가 DASHBOARD_PORT를 주입해줄 것으로 기대했다면 silent behavioral change. $dashboard_port 기본값 8080이 환경변수 미설정 상태와 보통 일치하므로 실제 문제는 드물지만, legacy 경로 주석에 "DASHBOARD_PORT는 호출자가 직접 APP_CMD에 포함해야 함" 명시 권장.

D. APP_CMD_ARGS 포맷 미문서화

app_args=( $APP_CMD_ARGS ) 의 word-split 방식은 내부 공백이 있는 인수를 지원하지 않음. 스크립트 상단 변수 설명이나 사용 예시(e.g., # APP_CMD_ARGS="--workers 4 --timeout 30")가 없어 내부 quoted 경로를 시도할 경우 조용히 깨짐.


Overall Assessment

머지 가능 여부: Yes

Minor A와 B는 완전히 해소됨. Issue #1은 새 안전 인터페이스(APP_CMD_BIN/APP_CMD_ARGS) 도입으로 실질적으로 개선됨 — legacy 경로는 명시적 backward-compatibility escape hatch로 남아있고 주석으로 제약이 문서화되어 있음. 신규 이슈 C, D는 모두 Minor 수준이며 blocking 아님. 팀이 legacy APP_CMD 경로를 "documented risk" 로 수용한다면 현재 상태로 머지 가능.

## Code Review (3차): PR #404 - 2차 리뷰 반영 확인 (commits `c412412`, `dc0775c`) --- ### 이전 이슈 처리 현황 **Issue #1: `exec env $APP_CMD` word-splitting — ⚠️ PARTIAL** 신규 `APP_CMD_BIN` / `APP_CMD_ARGS` 분리 인터페이스 도입으로 의미 있는 개선: - `"$APP_CMD_BIN"` 은 항상 quoted → 실행 파일 경로 공백 문제 해결 - 배열 `"${app_args[@]}"` 로 인수 전달 → word-splitting 안전 단, **기존 legacy 브랜치는 여전히 잔존**: ```bash # run_overnight.sh L101-103 else # Custom APP_CMD is treated as a shell command string. ... nohup bash -lc "exec env $APP_CMD" >>"$RUN_LOG" 2>&1 & fi ``` 이 경로는 주석만 있고 코드 수정 없음. `APP_CMD`에 공백이 포함된 경로, `$`, 세미콜론 등 shell metacharacter가 있으면 여전히 위험. 추가 주의사항 — `APP_CMD_ARGS` 배열 할당: ```bash # shellcheck disable=SC2206 app_args=( $APP_CMD_ARGS ) ``` 의도적 word-split임을 `shellcheck disable`로 표시했으나, `APP_CMD_ARGS="--config '/path with spaces/config.toml'"` 같은 내부 quoted 경로는 올바르게 처리되지 않음. 파일 어딘에도 이 제약이 문서화되지 않음 — 주석 추가 권장. **결론**: 새 인터페이스 사용 시 안전. legacy `APP_CMD` 경로는 "use at your own risk" escape hatch로 명시적 backward-compatibility 수단이 된 것으로 이해된다면 acceptable. --- **Minor A: `runtime_verify_monitor.sh` L83 `ss` fallback — ✅ RESOLVED** `is_port_listening` 함수 도입 (L55-71): ```bash ss → lsof → netstat ``` `run_overnight.sh`와 동일한 3단계 fallback. L101에서 `is_port_listening "$DASHBOARD_PORT"` 호출로 교체. 정확한 구현. (참고: `check_forbidden` L47에서 `rg`는 여전히 fallback 없이 사용 중 — 이번 diff 범위 밖이지만 일관성 관점에서 언급) --- **Minor B: 테스트에서 watchdog 프로세스 잔류 — ✅ RESOLVED** 스크립트 `startup failed` 경로에서 watchdog kill 추가 (L118): ```bash [ -n "${watchdog_pid:-}" ] && kill "$watchdog_pid" 2>/dev/null || true ``` 테스트에서 watchdog PID 파일 읽어 `os.kill(watchdog_pid, 0)` → `ProcessLookupError` 확인으로 프로세스 종료 검증. 올바른 접근. 사소한 관찰: - `if watchdog_pid_file.exists()` 가드가 있어 PID 파일 없으면 assertion silently skip. 실제로 이 경로에서 PID 파일은 항상 존재하므로 practical 문제는 없지만, 엄격히는 `assert watchdog_pid_file.exists()` 를 먼저 두는 게 더 명확함 - PID 재사용 race condition (ESRCH 발생 전 PID 재할당) 이론상 가능하나 테스트 duration 상 무시 가능 --- ### 신규 발견 이슈 (이번 수정에서 도입) #### Minor **C. legacy `APP_CMD` 경로에서 `DASHBOARD_PORT` 암묵적 누락** 기존 코드는 `APP_CMD` 문자열 자체에 `DASHBOARD_PORT=$dashboard_port`를 포함했지만, 이번 리팩토링 후 default/safe 경로는 `env DASHBOARD_PORT="$dashboard_port"` 로 명시 주입, **legacy 경로만 주입 없음**: ```bash # L103 — DASHBOARD_PORT 미주입 nohup bash -lc "exec env $APP_CMD" >>"$RUN_LOG" 2>&1 & ``` legacy `APP_CMD` 사용자가 스크립트가 `DASHBOARD_PORT`를 주입해줄 것으로 기대했다면 silent behavioral change. `$dashboard_port` 기본값 `8080`이 환경변수 미설정 상태와 보통 일치하므로 실제 문제는 드물지만, legacy 경로 주석에 "DASHBOARD_PORT는 호출자가 직접 APP_CMD에 포함해야 함" 명시 권장. **D. `APP_CMD_ARGS` 포맷 미문서화** `app_args=( $APP_CMD_ARGS )` 의 word-split 방식은 내부 공백이 있는 인수를 지원하지 않음. 스크립트 상단 변수 설명이나 사용 예시(e.g., `# APP_CMD_ARGS="--workers 4 --timeout 30"`)가 없어 내부 quoted 경로를 시도할 경우 조용히 깨짐. --- ### Overall Assessment **머지 가능 여부: Yes** Minor A와 B는 완전히 해소됨. Issue #1은 새 안전 인터페이스(`APP_CMD_BIN`/`APP_CMD_ARGS`) 도입으로 실질적으로 개선됨 — legacy 경로는 명시적 backward-compatibility escape hatch로 남아있고 주석으로 제약이 문서화되어 있음. 신규 이슈 C, D는 모두 Minor 수준이며 blocking 아님. 팀이 legacy `APP_CMD` 경로를 "documented risk" 로 수용한다면 현재 상태로 머지 가능.
agentson added 1 commit 2026-03-04 02:43:34 +09:00
docs: clarify APP_CMD legacy and APP_CMD_ARGS contract
All checks were successful
Gitea CI / test (push) Successful in 36s
Gitea CI / test (pull_request) Successful in 36s
bcbbf80d16
Author
Collaborator

최신 리뷰 재확인 완료.

리뷰에서 남은 minor C/D 항목(legacy APP_CMD 계약, APP_CMD_ARGS 포맷 제약 문서화)을 run_overnight.sh 주석에 명시적으로 반영함.

반영 커밋:

  • bcbbf80 docs: clarify APP_CMD legacy and APP_CMD_ARGS contract

핵심 명시:

  • 권장 경로: APP_CMD_BIN + APP_CMD_ARGS
  • APP_CMD_ARGS는 shell word-splitting 기반이며 nested quote 보존 없음
  • legacy APP_CMD는 하위 호환 경로이며 quoting/env 주입은 호출자 책임
  • legacy에서 --dashboard 사용 시 비기본 포트가 필요하면 DASHBOARD_PORT를 APP_CMD에 직접 포함해야 함
최신 리뷰 재확인 완료. 리뷰에서 남은 minor C/D 항목(legacy APP_CMD 계약, APP_CMD_ARGS 포맷 제약 문서화)을 `run_overnight.sh` 주석에 명시적으로 반영함. 반영 커밋: - `bcbbf80` docs: clarify APP_CMD legacy and APP_CMD_ARGS contract 핵심 명시: - 권장 경로: `APP_CMD_BIN` + `APP_CMD_ARGS` - `APP_CMD_ARGS`는 shell word-splitting 기반이며 nested quote 보존 없음 - legacy `APP_CMD`는 하위 호환 경로이며 quoting/env 주입은 호출자 책임 - legacy에서 `--dashboard` 사용 시 비기본 포트가 필요하면 `DASHBOARD_PORT`를 APP_CMD에 직접 포함해야 함
Author
Collaborator

Code Review (4차): PR #404 - 3차 리뷰 반영 확인 (commit bcbbf80)


이전 이슈 처리 현황

Minor C: legacy APP_CMD 경로에서 DASHBOARD_PORT 암묵적 누락 — RESOLVED

스크립트 상단 contract 주석에 명시:

# 2) Legacy fallback: APP_CMD (raw shell command string)
#    - When APP_CMD includes --dashboard, caller should include explicit
#      DASHBOARD_PORT assignment in APP_CMD if non-default port is required.

legacy 경로 사용자에게 DASHBOARD_PORT를 직접 포함해야 함을 명확히 고지함. 적절한 처리.


Minor D: APP_CMD_ARGS 포맷 미문서화 — RESOLVED

contract 주석에 word-split 제약 명시:

# - APP_CMD_ARGS uses shell-style word splitting; quote/escape inside this
#   variable is NOT preserved as a nested shell parse.

내부 quoted 경로가 지원되지 않는다는 제약이 명확히 문서화됨. 적절한 처리.


신규 이슈

없음.


Overall Assessment

머지 가능 여부: Yes

6차 이슈 전체가 해소됨. Issue #1의 legacy APP_CMD 경로는 신규 안전 인터페이스(APP_CMD_BIN/APP_CMD_ARGS)로 대체되었고, 잔존하는 legacy 경로의 제약과 책임 소재가 contract 주석으로 명확히 문서화되어 있음. 전체 구현이 원래 이슈 #396, #397의 acceptance criteria를 충족하며 추가된 테스트도 통과함. 블로킹 이슈 없음.

## Code Review (4차): PR #404 - 3차 리뷰 반영 확인 (commit `bcbbf80`) --- ### 이전 이슈 처리 현황 **Minor C: legacy `APP_CMD` 경로에서 `DASHBOARD_PORT` 암묵적 누락 — ✅ RESOLVED** 스크립트 상단 contract 주석에 명시: ``` # 2) Legacy fallback: APP_CMD (raw shell command string) # - When APP_CMD includes --dashboard, caller should include explicit # DASHBOARD_PORT assignment in APP_CMD if non-default port is required. ``` legacy 경로 사용자에게 DASHBOARD_PORT를 직접 포함해야 함을 명확히 고지함. 적절한 처리. --- **Minor D: `APP_CMD_ARGS` 포맷 미문서화 — ✅ RESOLVED** contract 주석에 word-split 제약 명시: ``` # - APP_CMD_ARGS uses shell-style word splitting; quote/escape inside this # variable is NOT preserved as a nested shell parse. ``` 내부 quoted 경로가 지원되지 않는다는 제약이 명확히 문서화됨. 적절한 처리. --- ### 신규 이슈 없음. --- ### Overall Assessment **머지 가능 여부: Yes** 6차 이슈 전체가 해소됨. Issue #1의 legacy `APP_CMD` 경로는 신규 안전 인터페이스(`APP_CMD_BIN`/`APP_CMD_ARGS`)로 대체되었고, 잔존하는 legacy 경로의 제약과 책임 소재가 contract 주석으로 명확히 문서화되어 있음. 전체 구현이 원래 이슈 #396, #397의 acceptance criteria를 충족하며 추가된 테스트도 통과함. 블로킹 이슈 없음.
jihoson merged commit c217e8cd72 into main 2026-03-04 02:46:38 +09:00
jihoson deleted branch feature/issue-396-397-runtime-anomaly-fixes 2026-03-04 02:46:38 +09:00
Sign in to join this conversation.
No Reviewers
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: jihoson/The-Ouroboros#404