Skip to content

fix(subprocess): stop the whole process group and bound every wait after a timeout (#2533) - #2534

Merged
seoseo-ai merged 2 commits into
mainfrom
fix/graceful-timeout-process-group-2533
Oct 26, 2026
Merged

seoseo-ai merged 2 commits into
mainfrom
fix/graceful-timeout-process-group-2533

Conversation

@seoseo-ai

Copy link
Copy Markdown
Collaborator

Closes #2533

문제

run_with_graceful_timeout(#2397)은 시간 제한 시 직계 자식에게만 SIGTERM과 SIGKILL을 보냈다. 그리고 SIGKILL 뒤 proc.communicate()를 시간 제한 없이 호출했다. claude CLI의 자손(MCP 서버 등)이 stdout/stderr를 물려받아 쥐고 있으면 호출자는 무기한 멈춘다. daegyo의 10-26 주간 bench가 q23에서 멈춰 판정 없이 끝난 사례가 이 경로로 추정된다(정황 근거, 세부 내용은 #2533).

변경 (3개 사본 동일하게 — nunchi.py, judge-batch.py, ccc-skill-promotion.py)

  • 자식을 start_new_session=True로 실행한다(호출자가 False를 넘기면 기존처럼 직계 자식에게만 보낸다).
  • 시간 제한 시 os.killpg로 그룹 전체에 SIGTERM을 보내고, 유예 시간(GRACEFUL_STOP_SECONDS, 기본 10초) 뒤 SIGKILL을 보낸다. bug(auth): /login refresh 실패 시 CLI가 .credentials.json 토큰을 빈 값으로 덮어써 노드 전체가 조용히 인증 불능 — 10월 3노드(곽가·소교·대교), refresh 방식 7노드 노출 #2397 순서(TERM 먼저, 유예 후 KILL)는 그대로이고, 이제 자손도 TERM을 먼저 받는다.
  • SIGKILL 뒤 출력 회수는 유예 시간으로 상한을 둔다. 자손이 setsid로 그룹을 벗어나 파이프를 계속 쥐면 파이프를 닫고 TimeoutExpired를 올린다(이 경우 output=None).
  • BaseException 경로도 그룹 단위로 TERM, 유예, KILL 순으로 정리한다.
  • 호출부 3곳은 모두 TimeoutExpired를 실패 사유로만 쓰고 .output을 읽지 않으므로 동작이 바뀌지 않는다.
  • scripts/auto-distill/auto-distill.py는 평가 영수증 대상이라 제외했다(#2397과 동일).

테스트 (scripts/graceful_timeout_copies_test.py, CI는 validate-harness.sh에서 실행)

  • 새 테스트 4개
    • 그룹 안 자손이 파이프를 쥔 경우
    • setsid로 그룹을 벗어난 자손이 있는 경우
    • 자손도 SIGTERM을 먼저 받는지
    • start_new_session=False 선택 시 기존 동작
  • 사본 일치 검사는 proc.terminate() 대신 os.killpg(와 start_new_session을 확인하도록 바꿨다.

검증 (daegyo, Termux)

검증 결과
새 코드: python3 scripts/graceful_timeout_copies_test.py 11/11 OK (11.7초)
origin/main 원본 + 새 테스트(회귀 증명) 자손 테스트 3개 FAIL/ERROR, 호출자가 60.3초 붙잡힘(자손 sleep 종료까지)
py_compile(변경 파일 4개) OK
scripts/ccc-skill-promotion.test.sh 변경 전과 후 모두 같은 80건 실패(diff 없음). 이미 알려진 Termux 전용 실패라 최종 판정은 CI에 맡김

영향과 위험

  • 자식이 새 세션의 리더가 되므로 터미널 Ctrl-C(SIGINT)가 자식에게 직접 가지 않는다. 대신 호출자의 KeyboardInterrupt가 BaseException 경로로 그룹 전체를 정리한다. 이 함수의 호출자는 cron과 hook 같은 비대화형 경로다.
  • #2532로 nunchi 합성 경로는 이미 MCP 서버를 띄우지 않는다. 이 PR은 남은 자손 경로와 judge-batch, skill-promotion까지 덮는 방어선이다.

🤖 Generated with Claude Code

…ter a timeout (#2533)

run_with_graceful_timeout (#2397) signalled only the direct child and, after
SIGKILL, called communicate() with no timeout. A descendant of the claude CLI
(an MCP server) that inherited stdout/stderr kept the pipes open, so the
caller waited forever — daegyo's 2026-10-26 weekly bench stopped at q23 and
stayed pinned until the phone rebooted 35 minutes later.

- Start the child in its own session (callers may pass start_new_session=False)
  and send SIGTERM, then SIGKILL after the grace, to that whole group via
  os.killpg. The #2397 order is unchanged: TERM first, KILL only after the grace.
- Bound the drain after SIGKILL by the grace; if a descendant escaped the group
  and still holds the pipes, close them and raise TimeoutExpired.
- The BaseException path stops the group the same way.
- All three byte-identical copies updated (nunchi.py, judge-batch.py,
  ccc-skill-promotion.py); auto-distill stays out of scope (receipt surface).

Tests (scripts/graceful_timeout_copies_test.py): in-group descendant holding the
pipes, descendant escaped via setsid, SIGTERM reaching descendants first, and
the start_new_session opt-out. Against origin/main the three descendant tests
fail with the caller pinned 60.3 s; with this change all 11 pass in 11.7 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@seoseo-ai
seoseo-ai requested a review from jinon86 as a code owner October 26, 2026 05:47
…non node-name ratchet)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@jinon86 jinon86 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved after exact-head validation and fresh operator authorization using the relay-held jinon86 credential.

@seoseo-ai
seoseo-ai added this pull request to the merge queue Oct 26, 2026
Merged via the queue into main with commit 6520d6d Oct 26, 2026
14 checks passed
@seoseo-ai
seoseo-ai deleted the fix/graceful-timeout-process-group-2533 branch October 26, 2026 06:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants