Repository navigation
fix(phone-browser-mcp): reclaim tunnels/forwards leaked by SIGKILLed wrappers (#2529) - #2530
Merged
Merged
Conversation
…wrappers (#2529) Each session now records <host>.<pid>.<lport>.state (host, adb serial, phone forward port) next to its ControlMaster socket. At startup the launcher sweeps its own host's stems: a stem whose wrapper PID is gone, or alive but no longer a phone-browser-mcp.sh wrapper (PID-reuse guard via /proc/<pid>/cmdline with a ps fallback), gets a bounded `ssh -O exit`, its socket removed and exactly the recorded phone forward removed. Sockets without a .state only get their tunnel closed. Only uid-owned, non-symlink files in the naming scheme are touched; .state files are claimed by atomic rename and kept for a retry when the phone is unreachable. The sweep is time-bounded (PHONE_BROWSER_SWEEP_BUDGET) and never fails the launch. A reaper (the script re-run as --reap-after, under setsid when available, stdin/stdout on /dev/null, stderr to $STATE_DIR/reaper.log) polls the wrapper every PHONE_BROWSER_WATCH_INTERVAL seconds and reclaims the session if the wrapper dies without running its traps; normal cleanup stops it. Tests cover dead-wrapper reclaim, live-wrapper preservation, stateless sockets, PID reuse, host/symlink isolation, unreachable-phone retry, the SIGKILL watcher and clean stdout. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
seoseo-ai
approved these changes
Oct 26, 2026
seoseo-ai
left a comment
Collaborator
There was a problem hiding this comment.
Approved after exact-head validation and fresh operator authorization using the relay-held seoseo-ai credential.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
요약
scripts/phone-browser-mcp.sh가 래퍼 SIGKILL 시 SSH ControlMaster 터널과 폰adb forward를 남기던 누수(#2529)를 두 겹으로 회수합니다.$STATE_DIR/<host>.<wrapper-pid>.<local-port>스템에.ctl(마스터 소켓)과.state(host / adb serial / 폰 forward 포트)를 기록합니다..state는adb forward tcp:0직후(로컬 포트 0 임시 스템)에 써서 SIGKILL 창을 최소화합니다. 새 래퍼는 자기 터널을 열기 전에 자기 host의 스템만, 명명 규칙에 맞는 파일만, 현재 uid 소유·심볼릭 링크 아닌 것만 훑고:phone-browser-mcp.sh래퍼가 아니면(PID 재사용 가드:/proc/<pid>/cmdline, 없으면ps -o args=, 둘 다 없으면 보수적으로 "살아 있음") 고아로 판정 →ssh -S <ctl> -O exit(timeout 5s) + 소켓 삭제 +.state에 기록된 forward만 폰 adb로forward --remove..state없는 옛 소켓: 터널만 닫고 소켓 삭제, 폰 forward는 건드리지 않음(stderr에 기록)..state는 원자적 rename으로 선점 후 처리 → 동시 sweep/reaper가 같은 forward를 두 번 지우지 않음..state를 남겨 다음 실행이 재시도하고, 그 sweep의 나머지 forward 제거는 건너뜀(한 번만 접속 시도).PHONE_BROWSER_SWEEP_BUDGET(기본 20s, 0=끔), 모든 메시지는 stderr, 실패해도 실행은 계속.--reap-after <host> <pid> <lport>모드로 다시 띄웁니다(setsid있으면 별도 세션, stdin/stdout은/dev/null, stderr는$STATE_DIR/reaper.log, 256KB 넘으면 비움).PHONE_BROWSER_WATCH_INTERVAL(기본 3s, 0=끔)마다 래퍼 PID를 확인하고, 래퍼가 trap 없이 죽으면 같은 회수 로직을 실행합니다. 정상 정리 시 cleanup이 reaper를 종료하고.state를 지우므로(이것만으로도 reaper는 종료) 남지 않습니다..state를 남겨 다음 실행이 재시도합니다.기존 동작/플래그/env(
PHONE_BROWSER_KEEP_AWAKE,--mcp, 포트 선택, 보안 주석, stdout=MCP 전용)는 그대로입니다. 헤더 주석에 상태 파일·sweep·reaper 설명과 새 env 두 개를 추가했습니다. docs/·README에는 이 런처 언급이 없고,tools-cheatsheet.md는 매 세션 상시 주입 문서라 늘리지 않았습니다.근거
PPID=1, 폰 forward 56개 → trap이 돌지 않는 종료(SIGKILL 추정) 경로.테스트
bash scripts/phone-browser-mcp.test.sh→PASS=73 FAIL=0(기존 39 + 신규 34)sleepPID → 고아 판정)/dev/nullPHONE_BROWSER_WATCH_INTERVAL=0이면 reaper 없음, 잘못된 env 값 rc 2bash -n두 파일 OKshellcheck --severity=warning -e SC2155,SC1090,SC1091+--severity=error(CI와 같은 옵션) OKpython3 scripts/changelog_fragments.py check→changelog fragments ok (256 pending)CCC_HARNESS_PHASE=static bash scripts/validate-harness.sh→HARNESS VALIDATION: PASS트레이드오프 / 남은 위험
adb forward tcp:0가 돌아온 직후.state를 쓰기 전 아주 짧은 순간에 SIGKILL되면 그 forward 하나는 기록되지 않습니다.setsid가 없는 환경(일부 Termux)에서는 reaper가 래퍼와 같은 프로세스 그룹에 있어, 그룹 전체 SIGKILL이면 같이 죽습니다. 이 경우 다음 실행 sweep이 회수합니다.Closes #2529
🤖 Generated with Claude Code