본문 바로가기
프로젝트/AI 작업 모니터

Cursor 훅 입력이 사라진 날, PowerShell 안에서 셸을 또 불렀다

by iwbap 2026. 10. 22.
728x90
반응형

AI 코딩 에이전트의 작업 상태를 휴대폰으로 보는 개인 도구를 만들었다. 커밋 주석에 따르면 Windows Cursor 훅에서는 프롬프트·모델·stop 이벤트가 수집기에 닿지 않았다. PowerShell을 중첩 호출하던 구조를 바꾸고, 파이프 전달 테스트를 추가했다.

어두운 배경에 Cursor 훅 입력이 사라졌다, 셸 안에서 셸을 또 불렀다는 제목과 PowerShell 파이프와 중첩 powershell.exe라는 부제가 있고, 오른쪽에 훅 명령으로 들어온 입력이 중첩 powershell.exe를 거치면 stdin이 비고 실행 파일을 직접 부르면 JSON이 도착한다는 두 갈래 도식을 그린 대표 이미지, 원인 설명은 Cursor 공식 동작이 아니라 커밋 코드 주석 기준
이미 PowerShell 안에서 도는 훅에서 셸을 또 띄우자 입력이 사라졌다 (원인 설명은 커밋 코드 주석 기준)

에이전트 상태를 휴대폰으로 보내는 훅

AI 코딩 에이전트 작업 모니터는 PC 수집기에서 서버로 이벤트를 보낸다. Android 앱에서 상태와 사용량을 보고 상시 알림을 받는다.

기록은 10월 22일의 단일 루트 커밋에 묶여 있다. 중간 시도의 시각은 확인할 수 없다.

공식 생명주기 훅을 우선 사용한다. Cursor 사용자 훅은 ~/.cursor/hooks.json에 등록하며 stdin으로 JSON을 받는다.

훅은 세 단계로 동작한다.

  • stdin에서 이벤트 JSON을 읽는다.
  • PC에 상주하는 수집기로 HTTP 요청을 보낸다.
  • 표준 출력(stdout)에 {}를 쓰고 종료한다.

전달에 실패해도 Cursor 작업은 막지 않는다. 설계 문서에 처음부터 적어 둔 조건이다.

에이전트 훅 이벤트가 전달 모드 실행 파일에서 stdin JSON으로 읽힌 뒤 PC의 상주 수집기, 직접 설치한 서버를 거쳐 Android 앱 상시 알림이 되는 흐름도, 전달 모드는 표준 출력에 빈 JSON을 쓰고 바로 끝나며 전달에 실패해도 에이전트 작업은 막지 않는다는 설명 포함

PowerShell을 한 번 더 거치던 초기 계획

10월 21일 계획은 전달용 .ps1을 훅에 등록하는 방식이었다. sessionStart·stop·sessionEnd에 powershell -NoProfile -File을 등록하도록 적었다.

Windows의 EncodedCommand는 경로 인용 문제를 피하려는 분기다. 명령을 UTF-16LE·Base64로 바꾸며 Codex와 Grok에서는 유지했다.

Go 분기를 발췌했다. 한국어 주석은 원본 코드가 아닌 글쓴이의 생략 표시다.

// EncodedCommand avoids path-quoting differences in Windows hook runners.
script := "& '" + strings.ReplaceAll(exe, "'", "''") + "' --forward-hook " + tool + extraArgs
encoded := utf16.Encode([]rune(script))
// ... UTF-16LE 바이트로 바꾸는 부분 생략
return "powershell.exe -NoProfile -NonInteractive -EncodedCommand " +
    base64.StdEncoding.EncodeToString(raw)

-File과 EncodedCommand는 모두 별도 PowerShell을 호출한다. 다만 수정 직전 Cursor에 어느 형식이 등록돼 있었는지는 확인되지 않는다.

Cursor 입력이 사라진 원인은 커밋 주석에 있다

프롬프트·모델·stop이 전달되지 않았다는 설명은 커밋 코드 주석에 있다. Cursor 공식 문서에는 Windows PowerShell 실행 방식이 없다.

고친 Cursor 분기를 보자. 영어 주석은 원문 그대로 남겼다.

if tool == "cursor" {
    // Cursor on Windows already runs hooks in PowerShell and pipes the payload
    // with `$input | <command>`. A nested powershell.exe consumes that pipeline,
    // so the collector never sees the prompt, model, or stop event.
    return "& " + powershellQuote(exe) + " --forward-hook " + tool + extraArgs
}

Cursor는 Windows에서 이미 PowerShell로 훅을 실행하고, $input | 명령 형태로 페이로드를 흘려보낸다. 그 안에서 powershell.exe를 또 띄우면 중첩된 셸이 파이프를 먹어 버린다.

Windows Cursor 훅을 고치기 전과 후로 나눈 비교 도식, 고치기 전에는 Cursor가 PowerShell로 훅을 실행하며 입력을 파이프로 넘기는데 그 안에서 powershell.exe를 또 띄워 중첩 셸이 파이프를 먹고 수집기에 프롬프트와 모델, stop이 오지 않으며, 고친 뒤에는 호출 연산자로 실행 파일을 직접 불러 BOM 제거 후 JSON을 파싱하고 작업 시작과 모델, 완료를 받는다, 원인 설명은 Cursor 공식 동작이 아니라 커밋 코드 주석 기준

이는 커밋 주석의 설명이며 현장 로그는 없다. stop의 completed 상태로 완료를 판단하니 해당 이벤트 누락도 문제가 된다.

Cursor는 실행 파일을 직접 호출하도록 수정했다

Cursor 훅은 호출 연산자 &로 실행 파일을 직접 부른다. 경로 안의 작은따옴표는 두 번 쓴다.

  • Windows Cursor: & '실행 파일' --forward-hook cursor로 직접 호출
  • Windows Codex·Grok: 기존 EncodedCommand 유지
  • 재설치: 옛 전달 항목만 제거하고 사용자 훅과 기존 설정 보존

원본 설정은 최초 한 번만 백업한다. 전달부는 stdin을 최대 1 MiB 읽고 UTF-8 BOM을 제거한다.

Go 전달부의 시작이다. 한국어 주석은 JSON 처리와 1.2초 제한 전송을 생략한 표시다.

func forwardHook(source, execution string) {
    defer func() { _, _ = os.Stdout.WriteString("{}") }()
    raw, err := io.ReadAll(io.LimitReader(os.Stdin, 1<<20))
    if err != nil {
        return
    }
    raw = bytes.TrimPrefix(raw, []byte{0xEF, 0xBB, 0xBF})
    // ... JSON 파싱, 실행 환경 표시 후 로컬 수집기로 전달 (1.2초 제한)
}

defer로 종료 시 항상 {}를 출력한다. BOM 제거는 대비 코드이며 실제 Cursor가 BOM을 보냈다는 기록은 없다.

PowerShell 파이프로 입력을 검증했다

공백 있는 경로에 시험용 실행 파일을 만든다. PowerShell 파이프로 모델 정보와 한글 입력이 도착하는지 검사한다.

PowerShell 테스트 실행문을 줄이면 이렇다. 경로는 ...로 가렸다.

$OutputEncoding = [System.Text.Encoding]::UTF8
Get-Content -LiteralPath '...\payload.json' -Raw -Encoding UTF8 |
    & { $input | & '...\with spaces\fixture.exe' --forward-hook cursor '...\received.bin' }

훅 명령에 powershell이나 EncodedCommand가 들어 있으면 바로 실패한다. 받은 파일에 model_id와 "한글 요청"이 그대로 있어야 통과한다.

실행 환경별 훅 명령 형식과 테스트를 정리한 표 형태 도식, Windows Cursor는 실행 파일 직접 호출과 PowerShell 파이프 한글 JSON 테스트, Windows Codex와 Grok은 EncodedCommand 형식과 cmd.exe stdin 보존 테스트, macOS와 Linux는 따옴표로 감싼 실행 파일 호출과 두 번 설치해도 사용자 훅이 남는 공통 테스트, 실제 Windows Cursor IDE 확인은 아직 추가 검증 목록이라는 메모

Codex의 cmd.exe stdin과 재설치 시 사용자 훅 보존도 검증했다. Go 테스트와 vet는 통과했지만 Windows Cursor IDE 실기는 미검증이다.

서버와 앱에 도달한 실기기 작업은 WSL Codex 확장이었다. Windows Cursor 실동작을 검증한 것은 아니다.

이번에 배운 것

  • 훅 실행 셸을 다시 호출하기 전에 stdin 경로부터 확인한다.
  • cmd.exe와 PowerShell 파이프는 실행 환경을 나눠 시험한다.
  • 재설치 과정에서도 사용자가 만든 훅과 설정을 보존한다.

다음에는 Windows Cursor IDE의 시작·종료를 확인할 계획이다. 테스트 통과와 실기기 확인은 별개다.

자주 묻는 질문

Q. Cursor 훅 실패가 에이전트 작업을 멈추나?
A. 공식 문서에 따르면 기본 설정에서는 종료 코드 2만 작업을 막고, 다른 비정상 종료는 그대로 진행한다. failClosed를 켜면 달라진다. 이 도구는 항상 {}를 출력하고 끝나도록 만들었다.

관련 글: 업데이트 후 관리자 권한을 물려받던 에디터, 작업 스케줄러로 권한 낮춰 재실행
참고: Cursor Docs: Hooks

728x90
반응형