VS Code Jupyter 커널 연결 실패: pyenv + Poetry 환경에서의 충돌 분석

settings.json 설정 충돌이 만들어내는 간헐적 커널 연결 실패

pyenv + Poetry 환경으로 전환 후 VS Code에서 Jupyter 커널이 간헐적으로 연결되지 않는 문제를 분석한다.

jupyter.kernelCreationModejupyter.preferredKernelSpec 설정이 Python extension의 interpreter 선택과 충돌하면서 발생하는 무한 뺑뺑이 현상의 원인과 해결 방법을 정리한다.

Engineering
DevOps
Python
저자

Kwangmin Kim

공개

2026년 04월 06일

pyenv + Poetry 전환 시 자주 묻는 것들

conda에서 pyenv + Poetry로 전환할 때 반복적으로 혼동되는 개념을 먼저 정리한다.

conda가 하던 일을 왜 두 도구로 나누는가

conda는 Python 버전 관리 + 가상환경 + 패키지 관리를 하나의 도구로 처리한다. pyenv + Poetry는 이 역할을 둘로 분리한다.

conda 하나가 하던 일
├── Python 버전 관리  →  pyenv가 담당
└── 가상환경 + 패키지  →  Poetry가 담당

Poetry는 “Python이 이미 있다”는 것을 전제로 동작한다. 어떤 버전의 Python으로 venv를 만들지는 Poetry 소관이 아니다. 그래서 pyenv가 따라오는 것이다.

현재 pyenv에 설치된 버전이 하나뿐이라면 pyenv의 존재 이유가 없다. 여러 Python 버전을 동시에 관리할 필요가 없다면 pyenv 없이 Poetry만 써도 된다.

pyenv는 누가 관리하는가 — VS Code? Poetry?

pyenv는 독립 도구다. 사용자가 직접 CLI로 관리한다.

pyenv      → 사용자가 직접 관리 (pyenv install, pyenv global)
Poetry     → pyenv가 설치한 Python을 빌려서 venv 생성 (관리하지 않음)
VS Code    → pyenv의 존재를 모름, .venv\Scripts\python.exe 경로만 앎

pyenv는 Poetry가 venv를 만들 때 한 번 관여하고 끝이다. 이후 VS Code는 .venv 안의 python.exe만 직접 가리킨다.

pyenv 3.11.9  →  Poetry가 .venv 생성  →  VS Code는 .venv만 봄
  (한 번만 관여)                           (pyenv 존재 모름)

python.defaultInterpreterPath는 pyenv 경로인가?

아니다. Poetry가 만든 venv 경로다.

"python.defaultInterpreterPath": "${workspaceFolder}\\.venv\\Scripts\\python.exe"

이 설정은 VS Code에서 Ctrl+Shift+PPython: Select Interpreter.venv를 선택하면 자동으로 저장되는 경로와 동일하다. UI로 선택하나 settings.json에 직접 쓰나 결과는 같다.

VS Code에 Python 호출 경로가 왜 2곳인가

Python extension   →  python.exe 경로로 직접 연결
Jupyter extension  →  kernel.json 이름으로 탐색 후 연결

같은 python.exe를 가리키지만 탐색 방식이 다르다. 두 extension이 다른 팀에서 독립적으로 개발됐기 때문이다.

Microsoft Python 팀  →  Python extension 개발
Microsoft Jupyter 팀 →  Jupyter extension 개발

Jupyter extension이 먼저 있었고, kernel.json 방식은 Jupyter 자체의 표준이었다. 나중에 Python extension이 “interpreter 직접 지정” 방식을 추가하면서 두 방식이 공존하게 됐고, 설정 충돌 가능성이 생겼다.

conda가 편했던 이유 중 하나도 여기 있다. conda는 두 방식 모두와 잘 통합되도록 VS Code 쪽에서 특별히 처리했기 때문에 사용자가 이 차이를 의식할 필요가 없었다.


들어가며

conda 환경에서는 아무 문제 없이 잘 되던 Jupyter 커널 연결이, pyenv + Poetry 환경으로 전환하면서 간헐적으로 실패하기 시작했다. 증상은 다음과 같다.

  • Interpreter 선택 → Jupyter kernel 설정 순서: 될 때도 있고 안 될 때도 있음
  • Jupyter kernel 선택 → Interpreter 선택 순서: 무한 로딩, 커널 연결 실패

conda 때는 이런 일이 없었기 때문에 pyenv나 Poetry 자체의 문제라고 의심하기 쉽지만, 실제 원인은 settings.json의 설정 충돌이었다.


근본 문제

conda가 문제없었던 이유

conda는 환경 생성 시 Jupyter 커널 등록까지 자동으로 처리한다. VS Code가 conda 환경을 인식하면 interpreter와 kernel이 자동으로 묶여서 관리되기 때문에 별도로 설정할 것이 없다.

pyenv + Poetry는 다르다. 가상환경(.venv)을 생성하고, ipykernel을 설치하고, 커널 스펙을 등록하는 과정이 분리되어 있다. VS Code는 이 각각을 독립적으로 관리하며, 설정이 잘못되면 서로 충돌한다.

충돌을 일으킨 설정

// .vscode/settings.json
"jupyter.kernelCreationMode": "startUsingLocalKernelSpec",
"jupyter.preferredKernelSpec": "data-standardization",
"python.defaultInterpreterPath": "${workspaceFolder}\\.venv\\Scripts\\python.exe"

세 설정이 동시에 있으면 다음과 같은 충돌이 발생한다.

설정 역할 문제
python.defaultInterpreterPath Python extension이 사용할 인터프리터 지정 커널을 직접 지정하지 않음
jupyter.kernelCreationMode: startUsingLocalKernelSpec 커널 스펙 파일로 커널 강제 연결 Python extension의 interpreter 선택을 무시
jupyter.preferredKernelSpec 특정 커널 자동 선택 위 두 설정과 연결 순서 충돌

왜 간헐적으로만 실패하는가

VS Code의 Python extension과 Jupyter extension이 각각 비동기로 초기화된다. 어떤 extension이 먼저 초기화 완료되느냐에 따라 어느 설정이 우선권을 갖는지 달라진다.

  • Python extension이 먼저 완료 → defaultInterpreterPath 적용 → Jupyter가 그 interpreter를 커널로 사용 → 성공
  • Jupyter extension이 먼저 완료 → preferredKernelSpec으로 커널 고정 → Python extension이 나중에 interpreter를 바꾸려다 충돌 → 무한 로딩

타이밍에 따라 성공/실패가 갈리기 때문에 “될 때도 있고 안 될 때도 있는” 현상이 나타난다.


분석: 실제 상태 진단

문제 발생 시점의 실제 환경 상태는 다음과 같았다.

# pyenv, venv 모두 정상
$ pyenv version
3.11.9 (set by C:\Users\kmkim\.pyenv\pyenv-win\version)

# poetry venv 정상
$ poetry env info
Path: C:\Users\kmkim\Desktop\projects\data_standardization\.venv
Valid: True

# ipykernel 설치 정상
$ .venv/Scripts/python.exe -c "import ipykernel; print(ipykernel.__version__)"
7.2.0

# 커널 스펙 정상 등록
$ jupyter kernelspec list
data-standardization    C:\Users\kmkim\AppData\Roaming\jupyter\kernels\data-standardization

커널 스펙 내용도 올바른 경로를 가리키고 있었다.

// kernel.json
{
  "argv": [
    "C:\\Users\\kmkim\\Desktop\\projects\\data_standardization\\.venv\\Scripts\\python.exe",
    "-m", "ipykernel_launcher", "-f", "{connection_file}"
  ],
  "display_name": "data-standardization"
}

pyenv도 정상, Poetry venv도 정상, ipykernel도 설치됨, 커널 스펙도 올바름. 문제는 환경 자체가 아니라 settings.json의 중복/충돌 설정이었다.


해결 방법

충돌 설정 제거

// 변경 
"python.defaultInterpreterPath": "${workspaceFolder}\\.venv\\Scripts\\python.exe",
"jupyter.kernelCreationMode": "startUsingLocalKernelSpec",   //  제거
"jupyter.preferredKernelSpec": "data-standardization",       //  제거

// 변경 
"python.defaultInterpreterPath": "${workspaceFolder}\\.venv\\Scripts\\python.exe"

python.defaultInterpreterPath 하나만 남긴다. VS Code Jupyter extension은 선택된 Python interpreter를 자동으로 커널로 사용한다. 커널 스펙이 별도로 등록되어 있어도 interpreter 기반 연결이 더 안정적이다.

적용 절차

  1. settings.json에서 위 두 줄 제거
  2. VS Code Reload Window (Ctrl+Shift+P → “Reload Window”)
  3. 노트북 열고 우측 상단 커널 선택 → .venv (Python 3.11.9) 선택
  4. 이후 매번 설정할 필요 없이 프로젝트에 고정됨

왜 conda에서는 이런 설정이 필요 없었나

conda는 VS Code와의 통합이 빌트인으로 제공된다.

  • conda activate <env> 시 자동으로 Jupyter 커널 등록
  • VS Code Python extension이 conda 환경을 자동 감지
  • interpreter 선택만으로 kernel까지 자동 연결

pyenv + Poetry는 이 통합이 없다. 대신 python.defaultInterpreterPath로 venv를 명시적으로 지정해주면 conda와 동일하게 동작한다. 단 명시적 설정 하나로 충분하고, 더 많은 설정이 오히려 충돌을 만든다.


추가 해결: ipykernel 7.x 호환성 문제와 버전 다운그레이드

settings.json 충돌 제거로 한동안 문제가 잡혔으나, 다시 Jupyter 커널 무응답 증상이 재발했다. 이번에는 02번 포스트(VS Code Jupyter 커널 무응답 — ZMQ 소켓 충돌 진단과 해결)의 ZMQ 소켓 충돌 진단·조치도 효과가 없었다. 시간 순으로 정리하면 다음과 같다.

  1. 1차 시도 — 02번 포스트의 ZMQ 프로세스 충돌 해결법 (중복 Python 프로세스 종료) → 일시 효과 후 재발
  2. 2차 시도 — 03번 포스트의 settings.json 충돌 제거 → 일시 효과 후 재발
  3. 최종 해결ipykernel을 6.x 계열로 다운그레이드 → 재발 없음

결국 근본 원인은 설정 충돌이나 프로세스 중복이 아니라 ipykernel 라이브러리 버전 자체에 있었다.

ipykernel 7.x의 변경점

ipykernel 7.x는 2024년 말~2025년 초 릴리즈된 메이저 버전으로, 아래 변경이 들어갔다.

  • 커널 프로토콜 내부 구조 개편 — async 처리 방식, subshell 지원 등 근본 변경
  • jupyter_client / pyzmq와의 상호작용 방식 변경 — 메시지 순서·핸드셰이크 타이밍 재설계
  • 일부 API의 deprecation 및 신호 처리 로직 재작성

이 변경 자체는 장기적으로 옳은 방향이지만, 클라이언트 구현이 따라오지 못한 상태에서 릴리즈되어 호환성 문제를 일으켰다.

왜 VS Code에서 문제가 되는가

VS Code Jupyter extension은 순수 jupyter_client를 사용하지 않고 자체 구현한 커널 클라이언트로 커널과 통신한다. 따라서 ipykernel의 내부 프로토콜이 바뀌면 VS Code 쪽이 별도 업데이트되기 전까지 호환성이 깨진다.

  • VS Code 자체 커널 클라이언트가 기대하는 핸드셰이크 타이밍·메시지 순서ipykernel 7.x 동작이 어긋남
  • 특히 Windows에서 ZMQ 소켓 초기화 순서 문제로 “Connecting…” 상태에서 응답 대기 무한 루프 증상이 다수 보고됨
  • GitHub microsoft/vscode-jupyter 이슈 트래커에 ipykernel 7.x 관련 리포트가 다수 존재

증상이 02번 포스트의 ZMQ 소켓 충돌과 유사해 보이지만, 두 Python 프로세스가 충돌한 것이 아니라 단일 프로세스 내 프로토콜 타이밍이 어긋난 것이 실제 원인이었다. 진단 증상이 비슷해 원인을 혼동하기 쉬운 케이스다.

6.29 / 6.31이 안전한 이유

  • ipykernel 6.x는 3년 넘게 안정화된 브랜치
  • VS Code Jupyter extension이 실질적으로 이 버전을 기준으로 테스트되어 있음
  • 6.31은 6.x 계열의 최신이라 보안·버그픽스는 받으면서 호환성 유지

조치 명령어

poetry add ipykernel@^6.31 --group dev
# 또는
pip install "ipykernel>=6.31,<7"

Poetry를 쓴다면 pyproject.toml에 버전 핀을 명시하여 추후 자동 업그레이드를 방지한다.

# pyproject.toml
[tool.poetry.group.dev.dependencies]
ipykernel = "^6.31"   # 7.x는 VS Code 호환성 문제로 제외

다운그레이드 후 .venv를 재생성하는 것이 가장 깔끔하다.

poetry env remove python
poetry install

일반 원칙: 인프라 라이브러리는 도구가 따라올 때까지 기다린다

이 사건에서 뽑아낼 수 있는 일반 원칙은 다음과 같다.

라이브러리 유형 업그레이드 전략
내 코드가 직접 쓰는 라이브러리 (pandas, numpy, requests 등) 최신 버전 유지 — 기능·성능·보안 개선 혜택
다른 도구가 뒤에서 통신하는 인프라 라이브러리 (ipykernel, pyzmq, jupyter_client 등) 해당 도구(VS Code, JupyterLab 등)가 따라올 때까지 이전 메이저 버전 유지

“최신이 항상 좋다”는 휴리스틱이 인프라 계층에서는 깨진다. 도구 생태계가 과도기인 시점에는 안정화된 이전 메이저 버전을 고정하는 것이 안전하다. ipykernel 7.x와 VS Code Jupyter extension은 이 과도기의 전형적 사례였다.

이 원칙은 ipykernel에만 국한되지 않는다. Node.js 생태계의 webpack·babel·번들러, JVM 생태계의 kotlin·빌드 플러그인, Python의 mypy·pytest 플러그인 등 “도구가 뒤에서 의존하는 라이브러리”는 동일 규칙이 적용된다.


정리

항목 내용
증상 Jupyter 커널 간헐적 연결 실패, 커널 선택 후 무한 로딩
1차 원인 jupyter.kernelCreationMode + jupyter.preferredKernelSpec + python.defaultInterpreterPath 3중 충돌
1차 해결 Jupyter 관련 두 설정 제거, python.defaultInterpreterPath 하나만 유지
재발 후 근본 원인 ipykernel 7.x와 VS Code Jupyter extension의 프로토콜 호환성 문제
최종 해결 ipykernel을 6.31(6.x 계열 최신)로 다운그레이드 + pyproject.toml에 버전 핀
일반 원칙 내 코드가 쓰는 라이브러리는 최신, 인프라 라이브러리는 도구가 따라올 때까지 이전 메이저 유지
conda와의 차이 conda는 interpreter 선택 시 kernel까지 자동 연결, pyenv+Poetry는 명시적 interpreter 경로 지정 필요

Subscribe

Enjoy this blog? Get notified of new posts by email: