<!-- task=02_kotlin_typescript_process_timeout plan=0 tag=FIX -->

# Plan - FIX

## 이 파일을 읽는 구현 에이전트에게

**경고: `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것은 필수 마지막 단계다. 이 섹션을 채우기 전에는 작업이 완료된 것이 아니다.** 구현 체크리스트를 기준으로 작업하고, 계획 파일과 리뷰 스텁의 체크리스트를 모두 완료하며, 중간/최종 검증을 실행한 뒤 실제 구현 내용과 검증 출력을 `CODE_REVIEW-*-G??.md`에 기록한다. 리뷰 파일의 `이 파일을 읽는 리뷰 에이전트에게` 아카이브 지시는 실행하지 말고, `코드리뷰 전용 체크리스트`는 수정하거나 체크하지 않는다.

## 배경

Kotlin→TypeScript crosstest 실패 시 `npx tsx ...` 하위 `node` 프로세스가 남아 Kotlin 오케스트레이터가 출력 수집 스레드 join에서 계속 대기할 수 있다. 실제 실패 원인은 sibling task에서 다루지만, 오케스트레이터는 실패하더라도 20초 timeout 계약을 지켜 종료해야 한다.

## 분석 결과

### 읽은 파일

- `kotlin/crosstest/kotlin_typescript.kt`
- `typescript/crosstest/kotlin_typescript_client.ts`
- `typescript/crosstest/typescript_kotlin.ts`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsClient.kt`
- `kotlin/src/main/kotlin/com/tokilabs/proto_socket/WsServer.kt`
- `kotlin/src/test/kotlin/com/tokilabs/proto_socket/WsTest.kt`
- `kotlin/src/test/kotlin/com/tokilabs/proto_socket/TlsWsTest.kt`

### 테스트 커버리지 공백

- Kotlin→TypeScript crosstest 정상 PASS는 `./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinTypescriptKt`로만 확인된다.
- `kotlin/crosstest/kotlin_typescript.kt`의 timeout path가 하위 프로세스 트리를 정리하는지 검증하는 자동 테스트가 없다.

### 심볼 참조

- renamed/removed symbol 없음.

### 분할 판단

분할 정책을 먼저 평가했다. 이 작업은 `01_ts_wss_handshake_timeout`과 `02_kotlin_typescript_process_timeout`으로 나눈다. 02는 Kotlin crosstest 오케스트레이터의 process-control 문제만 소유하며, TypeScript WebSocket handshake 수정과 독립적으로 review 가능하다. 두 task 디렉터리는 의존 관계가 없다.

### 범위 결정 근거

이 계획은 Kotlin→TypeScript 오케스트레이터만 다룬다. Kotlin→Go/Dart/Python 파일에도 유사한 parent-only timeout 패턴이 있지만 현재 실패 범위가 Kotlin→TypeScript이고 사용자 요청도 fail 부분 조사이므로 확장하지 않는다.

### 빌드 등급

`cloud-G07`. long-running command diagnosis, process control, stdout/stderr stream 처리, 실제 integration command timeout 신뢰성이 포함된다.

## 구현 체크리스트

- [ ] `kotlin/crosstest/kotlin_typescript.kt`에서 TypeScript client 실행 시 `npx` wrapper 의존을 줄이거나 하위 process tree까지 정리한다.
- [ ] timeout 후 stdout/stderr reader join도 bounded 처리해 오케스트레이터가 멈추지 않게 한다.
- [ ] 모든 중간/최종 검증 명령을 계획 그대로 실행하고 실제 출력을 리뷰 파일에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [FIX-1] Kotlin TypeScript client 프로세스 timeout 정리 보강

#### 문제

`kotlin/crosstest/kotlin_typescript.kt:300-314`는 `npx tsx ...`를 실행한다. timeout 시 `kotlin/crosstest/kotlin_typescript.kt:333-338`에서 `process.destroyForcibly()`만 호출하고 stdout/stderr thread를 무기한 join한다. `npx`가 `sh`와 `node` 하위 프로세스를 남기면 pipe가 닫히지 않아 runner가 멈출 수 있다.

Before:

```kotlin
// kotlin/crosstest/kotlin_typescript.kt:333
val finished = process.waitFor(PROCESS_TIMEOUT_MS, TimeUnit.MILLISECONDS)
if (!finished) {
    process.destroyForcibly()
}
stdoutThread.join()
stderrThread.join()
```

#### 해결 방법

TypeScript client 실행 command를 repo-local `node_modules/.bin/tsx` 직접 실행으로 바꾸거나, `ProcessHandle` descendant까지 `destroyForcibly()`하는 helper를 추가한다. timeout 후에는 reader thread join에도 제한 시간을 두고, 제한 초과 시 명확한 failure를 반환한다.

After:

```kotlin
val finished = process.waitFor(PROCESS_TIMEOUT_MS, TimeUnit.MILLISECONDS)
if (!finished) {
    destroyProcessTree(process)
}
stdoutThread.join(STREAM_JOIN_TIMEOUT_MS)
stderrThread.join(STREAM_JOIN_TIMEOUT_MS)
check(!stdoutThread.isAlive && !stderrThread.isAlive) {
    "typescript-client $mode/$phase stream readers did not finish"
}
```

#### 수정 파일 및 체크리스트

- [ ] `kotlin/crosstest/kotlin_typescript.kt`: `npx` 대신 직접 실행 가능한 `tsx` 경로 또는 process tree cleanup helper 사용.
- [ ] `kotlin/crosstest/kotlin_typescript.kt`: timeout 시 descendant process 정리.
- [ ] `kotlin/crosstest/kotlin_typescript.kt`: stdout/stderr thread join에 bounded timeout 적용.
- [ ] `kotlin/crosstest/kotlin_typescript.kt`: timeout 실패 메시지가 `typescript-client <mode>/<phase>`를 포함하게 유지.

#### 테스트 작성

별도 unit test는 작성하지 않는다. 대상은 crosstest 오케스트레이터 process-control 경로이고, 검증은 실제 Kotlin→TypeScript crosstest와 외부 `timeout`으로 수행한다. sibling task가 TypeScript handshake timeout regression test를 추가한다.

#### 중간 검증

```bash
cd /config/workspace/proto-socket/kotlin && timeout 90s ./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinTypescriptKt
```

예상 결과: sibling task 적용 후 `PASS all kotlin-server/typescript-client crosstests passed`가 출력된다. sibling task 미적용 상태에서는 90초 외부 timeout 안에 명확한 FAIL 또는 timeout으로 종료해야 하며 무기한 hang하면 안 된다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `kotlin/crosstest/kotlin_typescript.kt` | FIX-1 |

## 최종 검증

Go test cache 해당 없음.

```bash
cd /config/workspace/proto-socket/kotlin && ./gradlew test --rerun-tasks
cd /config/workspace/proto-socket/kotlin && timeout 90s ./gradlew run -PmainClass=com.tokilabs.proto_socket.crosstest.KotlinTypescriptKt
bash -lc 'ps -eo pid,ppid,stat,cmd | rg --sort path "kotlin_typescript_client|KotlinTypescriptKt|node_modules/.bin/tsx" || true'
```

예상 결과: Kotlin test PASS, Kotlin→TypeScript crosstest PASS, 마지막 `ps` 검색에 테스트 잔여 프로세스가 없어야 한다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
