happy_various AI 실전 기록

LLM 중계기 개발기 5부 · 20편 / 전체 33편

2026년 10월 1일 기준

이 글에는 제휴 링크가 없고 글에 나오는 제품을 만든 곳들(애플·마이크로소프트·OpenAI·앤트로픽)과 아무 관계가 없습니다. 제가 직접 만든 중계기의 개발 기록을 바탕으로 썼습니다. 삽화는 GPT 이미지 생성, 도식은 직접 제작했습니다.

맥에서 되는데 윈도에서 안 되는 이유 — D:를 인터넷 주소로 착각한 날 (LLM 중계기 개발기)

집에서 매일 쓰던 헤어드라이어를 여행 가방에 넣어 갔다고 해 볼게요. 집에서는 한 번도 말썽을 부린 적이 없는 물건이에요. 그런데 호텔 방 벽 앞에 서 보니 콘센트 구멍 모양이 다릅니다. 드라이어는 멀쩡한데, 꽂을 곳이 집과 달랐던 거죠. 집에서 백 번을 써 봤어도 이건 알 수가 없어요. 집 콘센트에서만 써 봤으니까요.

제 LLM 중계기도 그랬습니다. 맥에서 만들고 맥에서 시험할 때는 잘 켜졌는데, 실제로 쓸 윈도 컴퓨터에서는 켜자마자 꺼졌어요. 원인은 글자 두 개, D:였습니다. 윈도의 드라이브 이름을 https: 같은 인터넷 주소의 앞머리로 읽어 버린 거예요.

이 글은 LLM 중계기 개발기의 20편이자, 5부 ‘만들고 나서가 더 어렵다 — 설치·업데이트’의 첫 편입니다. 지난 19편 「한국어는 토큰을 더 먹는다」에서는 같은 내용도 한국어가 토큰을 더 쓴다는 이야기로 4부를 마무리했어요. 5부에서는 다 만든 프로그램을 실제 컴퓨터에 깔고, 켜고, 끄고, 새 판으로 바꾸는 과정에서 생긴 일을 다룹니다. 첫 이야기는 4편에서 한 줄로만 적고 미뤄 둔 첫날 아침의 고장이에요.

사과 모양 노트북과 창문 모양 노트북 사이에서 고개를 갸웃하는 로봇 — 한쪽은 웃고 한쪽은 불꽃이 튄다 (GPT 이미지 생성)

결론 요약

🗺️ 1. 파일 위치와 인터넷 주소 — 닮았지만 다른 두 글

컴퓨터가 무언가를 찾아갈 때 쓰는 글은 크게 두 가지예요.

길 찾기로 치면 파일 위치는 ‘집 안 배치도’, 주소는 ‘바깥 거리의 표지판’쯤 됩니다. 둘 다 이름과 화살표로 길을 알려 주니 얼핏 닮았어요.

펼친 지도와 길가 표지판을 번갈아 보며 헷갈려하는 로봇 — 지도와 표지판의 화살표가 똑 닮았다 (GPT 이미지 생성)

문제는 D:\…처럼 알파벳 하나에 쌍점이 붙은 모양이에요. 사람이나 윈도의 탐색기는 이걸 “D 드라이브”로 읽습니다. 그런데 주소를 읽는 규칙으로 보면, https:와 똑같이 생긴 앞머리 d:로 보여요. “d라는 방식으로 가져오라”는 뜻이 되는 거죠. 불러오는 쪽이 아는 방식 중에 그런 건 없는데도요.

💥 2. 켜자마자 꺼진 아침 — “‘d:‘라는 방식은 모릅니다”

첫날(7월 13일) 아침 8시 8분에 첫 버전이 저장됐어요. 그리고 윈도 컴퓨터에서 켜 보니, 중계기는 켜는 명령을 넣자마자 오류를 내고 죽었습니다. 10시 21분에 저장된 수정 기록에 그 오류의 이름이 남아 있어요.

ERR_UNSUPPORTED_ESM_URL_SCHEME

이름은 길지만 뜻은 단순해요. “지원하지 않는 주소 방식”입니다. 무슨 일이 있었는지 순서대로 보면 이래요.

① 중계기를 켜면 시작 파일이 먼저 돌고, 이 파일이 서버 본체 파일을 불러와요

② 이때 쓰는 ‘불러오기’ 기능은 요즘 자바스크립트가 파일을 나눠 불러오는 표준 방식(ESM)을 따르는데, 이 방식은 불러올 대상을 주소로 받아요

③ 그런데 시작 파일은 본체의 위치를 그대로 건넸어요. 윈도에서 그 위치는 D:\…\server\index.mjs였고요

④ 불러오기 기능은 이 글자를 주소로 읽었고, 앞머리 d:를 보고 “그런 가져오는 방식은 모른다”며 멈췄어요. 중계기는 시작도 못 하고 꺼졌습니다

같은 실수를 지금 일부러 재현해 보면(Node.js 22 기준), 오류 메시지에는 고치는 방법까지 함께 적혀 있어요.

Only URLs with a scheme in: file, data, and node are supported by the default ESM loader. On Windows, absolute paths must be valid file:// URLs. Received protocol ‘d:’

우리말로 옮기면 “file, data, node 방식의 주소만 받습니다. 윈도에서는 파일 위치를 file:// 주소로 바꿔서 주세요. 받은 방식은 ‘d:‘입니다”예요.

고친 방법도 그 말 그대로였어요. 위치를 건네기 전에 주소 모양으로 바꿔 주는 기능(pathToFileURL)을 한 번 거치게 했습니다.

몇 줄만 바꾼 작은 수정이었고, 10시 21분에 저장된 수정은 1분 뒤인 10시 22분에 본 줄기에 합쳐졌어요.

같은 글자 'D:'로 시작하는 위치를 파일 위치로 읽을 때와 주소로 읽을 때, 고친 방법, 그리고 첫날의 '같은 코드, 다른 컴퓨터' 고장 셋 (직접 제작)

🍎 3. 맥에서는 왜 몰랐나

수정 기록에는 이 고장이 개발 중에 잡히지 않은 이유도 적혀 있어요.

헤어드라이어로 치면 집 콘센트에 백 번 꽂아 본 셈이에요. 드라이어가 멀쩡하다는 건 확인됐지만, 호텔 콘센트에 맞는지는 한 번도 확인하지 않은 거예요.

그래서 이번에 붙인 시험은 방향을 바꿨어요. 윈도가 없어도 윈도 모양의 글자를 직접 넣어 보는 것이에요. D:\app\server\index.mjs라는 글자를 불러오기 기능에 그대로 건네면, 맥에서 돌려도 똑같이 “지원하지 않는 주소 방식” 오류가 납니다. 주소를 읽는 규칙은 어느 컴퓨터에서나 같기 때문이에요.

같은 시험에는 빗금 방향만 바꾼 D:/app/server/index.mjs도 들어 있는데, 이것도 똑같이 실패해요. 범인은 역빗금이 아니라 알파벳 하나와 쌍점이었던 거죠.

🔌 4. 같은 코드, 다른 컴퓨터 — 첫날의 고장 셋

더 중요한 건 이 고장이 혼자가 아니었다는 점이에요. 같은 날, 맥에서는 멀쩡한데 윈도에서만, 또는 컴퓨터가 바뀌어서 생기는 고장이 줄줄이 이어졌거든요. 세 개만 골라 볼게요(위 그림 아래쪽 카드).

① 부품 목록 한 줄 다툼 (오전 10시 40분)

프로그램이 쓰는 부품과 그 버전을 적어 둔 부품 목록 파일(package-lock.json)이 있어요. 맥에 깔린 개발 도구 npm은 11.11 버전이라 이 목록에 ‘libc’라는 칸을 적어 넣었고, 윈도에 깔린 그보다 오래된 npm은 그 칸을 지웠어요. 두 컴퓨터가 번갈아 서로의 목록을 고쳐 쓰는 바람에, 설치 명령만 돌려도 목록 파일이 ‘바뀜’ 상태가 됐고 최신 코드를 내려받는 일까지 막혔습니다.

npm 10.8·10.9·11.0·11.1은 그 칸을 지우고 11.11은 쓴다는 걸 버전별로 직접 확인한 뒤, 목록을 그 칸이 없는 모양으로 정해 두고 그 칸이 다시 생기면 실패하는 검사를 붙였어요. 설치할 때는 목록을 고쳐 쓰지 않는 명령(npm ci)을 쓰도록 안내도 바꿨고요.

② 깔려 있는데 ‘미설치’ (낮 12시 36분)

Codex와 Claude가 분명히 깔려 있는데 화면에는 ‘미설치’로 떴어요. Codex 쪽 원인은 윈도에서만 생기는 고장이었습니다. 윈도에서는 Codex 같은 명령어 도구가 .cmd라는 껍데기 파일로 깔려요. 맥에서는 이름만 불러도 바로 실행되는데, 윈도에서는 이름만 불러 띄우는 방식으로 이 껍데기를 열지 못해 “그런 파일 없음” 오류가 났어요. 중계기는 그 오류를 ‘설치 안 됨’으로 읽었고요.

그래서 프로그램을 띄우는 일을 정해 둔 실행 통로 한 곳으로 모으고, 그 통로를 거치지 않고 직접 띄우는 코드가 있으면 실패하는 검사를 붙였어요.

③ 맥에서 싼 윈도용 짐 (밤 10시 22분)

그날 저녁에는 압축만 풀면 바로 쓰는 윈도용 배포판을 만들었어요. 그런데 이 압축판에서만 Claude가 연결되지 않았습니다. 압축 파일을 직접 풀어 보니 원인이 보였어요. Claude를 부르는 부품은 컴퓨터 종류마다 다른 실행 파일을 따로 받아 쓰는데, 맥에서 윈도용 압축판을 만들다 보니 윈도용 실행 파일은 받아지지 않고 맥용이 들어가 있었던 거예요. 여행 가방을 싸면서 집 콘센트에만 맞는 충전기를 넣은 셈이죠.

이미 깔려 있는 Claude 명령어 도구를 쓰도록 바꿨더니, 덤으로 압축 파일도 180MB에서 113MB로 줄었어요. 그리고 배포판에 그 컴퓨터용 부품이 들어 있는지 확인하는 검사를 붙였어요. Claude 하나만이 아니라, 컴퓨터마다 다른 실행 파일을 쓰는 부품이면 무엇이든 확인하도록 넓혀서요.

세 고장 모두 코드는 같았어요. 달랐던 건 컴퓨터였죠. 개발 도구의 버전, 프로그램이 깔리는 모양, 들어가야 할 부품의 종류가요.

윈도만 까다로운 것도 아니에요. 8월에는 맥에서만 생기는 고장도 있었어요. 브라우저가 이미 켜져 있으면, 맥은 “전용 창으로 열어 달라”는 부탁을 조용히 버리고 브라우저만 앞으로 가져왔어요. 기록에는 ‘창을 열었음’이라고 남았는데 실제 창은 없었죠. 컴퓨터마다 저마다의 버릇이 있는 거예요.

🧳 5. 남긴 것 — 검사, 그리고 ‘쓸 컴퓨터에서 시험하기’

첫 고장에 붙인 검사는 경로 검사예요. 하는 일은 단순합니다.

이 검사들은 시간이 지나도 지워지지 않았어요.

그래도 검사만으로는 모자랐어요. 중계기의 위험 점검 문서에는 이런 내용이 적혀 있습니다. 흉내 낸 가짜로 하는 시험은 진짜를 대신하지 못하고, 윈도에서 프로그램을 띄우는 일이나 윈도용 실행 파일, 압축 배포판 같은 것은 작은 단위 시험으로 잡을 수 없다. 그러니 내놓기 전에 실제 환경에서 한 번 돌려 보는 일은 무엇으로도 대신할 수 없다.

그래서 8월부터는 윈도용 배포판을 실제로 풀어 켜 보는 시험과, 새 판으로 바꿔 끼우는 업데이트를 실제로 해 보는 시험도 생겼어요. 가짜 시험과 진짜 점검 이야기는 32편 「가짜는 진짜를 대신 못 한다」에서 더 할게요.

여행 가방을 싸며 여러 변환 플러그 중 맞는 것을 골라 벽 콘센트에 대 보는 로봇 (GPT 이미지 생성)

여행 짐을 쌀 때 가는 곳의 콘센트 모양부터 확인하듯, 프로그램도 쓸 컴퓨터에서 한 번은 켜 봐야 해요. 만든 컴퓨터에서 백 번 잘 돌아도, 쓸 컴퓨터에서의 한 번을 대신하지는 못하니까요.

정리

① 맥에서 잘 돌던 중계기가 윈도에서 켜자마자 꺼졌어요. 파일 위치 D:\…의 D:를 https: 같은 주소의 앞머리로 읽은 탓이었고, 위치를 file: 주소로 바꿔 건네게 고쳤어요 ② 맥의 위치는 빗금으로 시작해 우연히 통했고, 시험도 맥에서만 돌아서 몰랐어요. 지금은 윈도 모양의 글자를 직접 넣어 어느 컴퓨터에서든 재현하고, 바꾸지 않은 코드가 있으면 검사가 실패해요 ③ 같은 날 부품 목록 다툼, ‘미설치’ 오판, 맥에서 싼 윈도용 짐이 이어졌어요. 그때 생긴 검사 중 셋이 지금 핵심 검사 5종에 들어 있고, 남은 교훈은 하나예요. 쓸 컴퓨터에서 시험한다

다음 21편은 「끄는 것도 기술이다」예요. 중계기를 껐는데도 뒤에 남은 프로그램이 다음 실행을 막던 일, “지난번 고쳤다는 보고는 틀렸다”고 스스로 적은 수정, 그리고 이번에도 맥에서는 재현조차 되지 않았던 이유를 이야기할게요.


2026년 10월 1일 기준입니다. 제가 만든 중계기의 개발 기록(저장 기록의 시각과 메모, 검사 스크립트, 문서)을 바탕으로 썼고, 오류 메시지 원문은 Node.js 22에서 같은 실수를 다시 재현해 확인했습니다. 특정 기업·서비스의 공식 입장이 아닙니다.