ZCode 대신 DeepSeek Harness로 GitHub Actions 윈도 패키징
ZCode가 저장소 전체를 조용히 업로드한다는 논란 후 DeepSeek Harness로 옮겨, 공식 윈도 패키지가 없어 GitHub Actions로 직접 .exe를 빌드한 기록을 정리했다.
중국어 원문을 AI로 번역했습니다. 고유명사와 수치는 원문 표기를 우선하며, 중요한 판단에는 아래 출처 원문을 함께 확인하세요.
한 줄 요약: 내 AI 프로그래밍 도구가 "로그인 상태면 저장소 전체(완전한 .git 이력 포함)를 조용히 패키징해 암호화하여 클라우드로 업로드하고, 개인 키는 서버에만 존재한다"는 사실이 실증되었고, 그날 바로 사용을 중단했다. 대체품으로 고른 DeepSeek Harness(이하 DSH)는 공식 Windows 패키지가 없어서, 포크를 떠서 GitHub Actions에서 직접 빌드했다. 전 과정 브라우저 조작으로, 25분 만에 .exe가 나왔다.
들어가며(자기소개): 나는 mldong이고, 하나의 빠른 개발 프레임워크의 저자다. 나의 또 다른 주력 작품은 오픈소스 경량 워크플로 엔진 jeeflow다 — 하나의 프로세스 정의를 Java / Go / Python / Node / PHP / Rust / MoonBit / C# 여덟 개 언어 스택에서 네이티브로 돌려버리며, 엔진 코어는 프레임워크 의존이 전혀 없고, 함께 제공되는 딩톡 스타일 프로세스 디자이너와 통합 프런트엔드도 오픈소스다. 문서 사이트(규격 / 빠른 시작 / 다국어 demo): jeeflow-doc.mldong.com . 이 시리즈 글은 위챗 공식계정 + 掘金에 동시 업데이트되니, 팔로우하고 다음 편도 봐주시길 바란다.
1. 발단: ZCode가 작업 공간 전체를 몰래 클라우드로 전송했다
2026-09-18 오전, 블로거 ferstar가 분석 글을 올렸다(원문은 글 말미의 참고 자료 참조). 핵심 결론은 딱 세 줄이다:
로그인 상태이기만 하면, ZCode(즈푸 공식 AI 프로그래밍 데스크톱 클라이언트)는 백그라운드에서 조용히 작업 공간 전체 — 완전한 .git 이력, LFS 대용량 파일 캐시, reflog 및 전역 앱 설정까지 포함 — 를 패키징·암호화하여 그대로 알리윈 OSS에 업로드한다. 더 아이러니한 점은: 암호화에 쓰는 RSA 공개 키는 서버가 동적으로 내려주고, 개인 키는 클라우드에만 존재하기 때문에, 네가 로컬에서 생성한 그 암호문은 너 자신도, 클라이언트 본체도 풀 수 없다.
업로드 경로(그가 클라이언트 패키지에서 리버스 엔지니어링으로 대조해 낸 타이밍 다이어그램):
두 번째 줄에 주목: 폼이 OSS로 직접 전송하며 즈푸 자체의 비즈니스 서버를 거치지 않는다 — 그래서 클라이언트 로그에는 zcode.z.ai로 나가는 기록만 남고, 로그로 이 일을 발견하는 건 사실상 불가능하다.
이후 커뮤니티의 누군가가 자기 Windows 머신에서 재현 테스트를 했고, 증거는 원문보다도 더 완전했다:
"업로드" 자체보다 더 뼈아픈 몇 가지 세부 사항:
- 작업 공간 32개가 스냅샷 채로 잡혔다 (해당 작업 공간에서 prompt를 한 번이라도 보냈으면 당했다), 최대 단일 패키지 107MB, 어떤 패키지는 업로드 재시도 실패 152회를 겪고도 pending/에 누워 재전송을 기다렸고, 그 외 십여 개의 작은 저장소는 업로드 성공 상태였다;
- 스냅샷은 모델 채널과 무관하다: 네가 서드파티 API를 설정해 추론 내용이 그쪽 서버를 전혀 거치지 않더라도, 스냅샷 사이드카는 오직 로그인 상태 JWT만 인식한다 — 누구 모델을 쓰든 똑같이 사진 찍어 전송한다;
- 전역 설정은 매 스냅샷에 "편승"해 업로드되는데, ZCode 설정에는 평문 API key가 들어 있다;
- 트리거 시점은 prompt를 보내기 전 + 작업 종료 시마다, 단일 세션 최대 62회 캡처;
- 네 가지 "데드라인": 기본 상시 켜짐, UI에 끌 수 있는 스위치가 전혀 없음, 개인정보 정책에 언급 없음, pending 패키지를 수동으로 지워도 30분 이내에 자동으로 다시 패키징해 재전송.
그날 저녁, 즈푸는 공식 커뮤니티에서 대응했고, 언론도 뒤따라 보도했다(아래 그림은 보도 캡처 + 공식 커뮤니티 성명 원문):
대응은 봤다. 사과, 수정, "조만간 오픈소스 + 제3자 감사 초청" 모두 플러스 요소다 — 그러나 "기본 상시 켜짐 + UI에서 끌 수 없음 + 삭제하면 재전송 + 개인 키는 클라우드에만 존재"라는 네 가지는 이미 벌어진 사실이고, 이미 올라간 것은 아무도 되돌릴 수 없다. 코드는 내가 매일 쓰는, 가장 민감한 재산인데, 내가 통제할 수 없고 속이 보이지 않는 동작에 나는 걸지 않는다.
그날, 나는 ZCode를 폐기했다.
2. 무엇으로 바꿀까? DeepSeek Harness(DSH)
후보 중에서 나는 DeepSeek가 오픈소스로 공개한 AI 프로그래밍 agent(DSH, 저장소 홈페이지는 아래 그림 참조)를 택했다. 이유는 소박하다:
- 오픈소스 + 로컬 우선: 코드가 전부 GitHub에 있어 동작을 한 줄 한 줄 대조할 수 있고, 데이터는 본체에 남으며, "서버 개인 키" 같은 내가 손댈 수 없는 것이 없다. "업로드"에 대한 궁극적 답은 바로 이것이다 — 소스를 다 훑어봐도 "몰래" 할 수가 없다, 왜냐하면 몰래 할 여지 자체가 없기 때문이다.
- 커뮤니티가 진짜 활발하다: 이 글을 쓰는 시점에 229k stars, 27k+ forks, Discussions 1300+ 건, 커뮤니티 플러그인 150+, 심지어 ACP 프로토콜, 로컬 소형 모델까지 붙어 있다.
- 확장 가능: 슬로건이 바로 "Everything is a Plugin" — 파일 미리보기, 터미널, 브라우저가 전부 플러그인이라, 마음에 안 들면 직접 고칠 수 있다(이 글 뒤에 복선이 있다).
유일한 문제: DSH 공식에는 Windows x64 완제품 패키지가 없다. 그것의 데스크톱 패키징 스크립트는 "Windows x64 호스트에서 빌드"할 것을 강제로 요구하며(이는 저장소 패키징 스크립트 자체의 제약이고, 비 Windows 환경은 바로 거부한다), 그래서 기존 CI는 Windows 패키지를 만들어낼 수 없다.
그러면 직접 빌드하자. 포크를 하나 떠서 GitHub Actions에 자체 파이프라인을 세운다. 전 과정 브라우저 조작으로, 아래에서 단계별로 훑어본다.
3. fork + 자체 파이프라인: 브라우저 조작 전체 흐름
3.1 첫 번째 단계: 업스트림 저장소 Fork
브라우저로 업스트림 저장소를 열고, 오른쪽 상단의 Fork를 클릭한다(무료 계정 50개 저장소 한도 내에서 마음껏 Fork). 나의 fork 페이지(상단의 "forked from" 한 줄, 그리고 나중에 내가 코드를 고친 뒤 나타난 "3 commits ahead" 표시에 주목):
3.2 두 번째 단계: fork 안에 패키징 workflow 추가
저장소의 .github/workflows/ 디렉터리 아래에 새 파일을 만든다(브라우저에서 Add file → Create new file, 또는 로컬에서 push해도 된다). 내용은 딱 세 가지 일을 한다: 수동 트리거(workflow_dispatch) → windows-2025 runner에서 공식 패키징 명령 실행(unsigned, secrets 0개) → 산출물을 artifact로 업로드:
파일 머리말 주석에 "왜 공식 CI로는 못 만드는가"를 명확히 적어 두었다(패키징 스크립트는 비 Windows 호스트에서 win-x64 빌드를 거부하고, 저장소에 이미 있는 windows job은 Wine 테스트만 돌린다). workflow_dispatch는 내가 수동으로 클릭할 때만 돌도록 보장하고, runs-on: windows-2025 + 서명 생략 파라미터는 secrets 0개를 보장한다.
3.3 세 번째 단계: Actions에서 수동 트리거
저장소 Actions 탭으로 들어가, 왼쪽 workflow 목록에서 내가 새로 만든 workflow를 클릭하고, 오른쪽에서 Run workflow를 클릭한다(master 브랜치 선택, 확인하면 끝):
클릭하면 클라우드에 맡겨진다. 콜드 캐시 첫 빌드는 약 25–40분 걸리니, 페이지를 새로고침하며 보면 된다.
3.4 네 번째 단계: run 성공 확인
23분 59초 후, 상태가 초록으로 바뀐다:
3.5 다섯 번째 단계: .exe 다운로드
run 페이지 하단의 Artifacts 영역이 바로 패키징 산출물이다 — deepseek-harness-windows-x64-unsigned (704 MB, sha256 요약 포함), 맨 오른쪽의 다운로드 아이콘을 클릭하면 바로 로컬로 내려온다:
압축을 풀면 그 안에 두 가지가 있다:
- deepseek-harness-0.1.6-alpha.2-win-x64.exe (NSIS 설치 프로그램, 약 290 MB) — 더블클릭하여 설치;
- win-unpacked/ (설치 불필요 디렉터리) — 복사해 가면 바로 실행되고, DeepSeek Harness.exe가 진입점이며, 내장 런타임이 전부 그 안에 있다.
서명되지 않은 패키지는 최초 실행 시 SmartScreen이 "알 수 없는 게시자"를 띄우는데, "그래도 실행"을 클릭하면 되며, 정상적인 현상이다(코드 서명 인증서가 없으니, 이것이 unsigned의 대가다).
더블클릭하여 실행 — 자체 패키징 버전이 실제로 돌아간 첫 화면(흐림 없음, 멈춤 없음, 작업 공간·모델 선택·세션이 전부 있다):
여기까지 오면 전체 체인이 닫힌다: 브라우저 fork → workflow 파일 하나 추가 → Run workflow 한 번 클릭 → 25분 후 다운로드 → 더블클릭하면 사용 가능.
4. 겪은 세 가지 함정(모두 workflow에 반영해 고쳤다)
위에서 훑은 것은 "최종 사용 가능 버전"이다. 처음 성공하기 전까지, 나는 같은 workflow 파일에서 세 가지 함정을 연달아 겪었다 — 캡처 속 그 commit 제목들(fix(workflow): …)이 바로 그들의 묘비명이다.
함정 1: 단계의 shell은 반드시 pwsh로 써야 하고, bash로 쓰면 안 된다
첫 빌드가 패키징 첫 단계의 tar에서 걸렸다:
Cannot connect to D: resolve failed
한참을 뒤져서야 원인을 찾았다: Windows runner에서 bash 단계는 C:\Program Files\Git\usr\bin을 PATH 맨 앞으로 올리고, Node의 spawnSync('tar')는 MSYS의 GNU tar를 만나게 되는데, 이것이 D:\a\... 같은 절대 경로를 host:file 원격 구문으로 취급해 "D:에 연결"하려다 — 바로 resolve failed가 나는 것이다.
해법: workflow의 모든 단계에 명시적으로 shell: pwsh를 지정해, tar가 네이티브 C:\Windows\System32\tar.exe(bsdtar)에 걸리게 하면 파라미터가 전부 호환된다.
함정의 본질: Windows CI에서 "기본 shell"은 네가 생각하는 그게 아니다. PATH 순서가 네 tar / curl / awk가 도대체 누구인지를 결정한다.
함정 2: .env.windows는 반드시 명시적으로 생성해야 한다
패키징 전에 먼저 apps/desktop/.env.windows를 생성해야 한다 — 공식 패키징 환경 로더가 이 파일만 읽고 ambient 환경 변수로 폴백하지 않으며, 파일이 없으면 곧바로 throw한다. unsigned 최소 세트:
DSH_DESKTOP_APP_ID =... DSH_DESKTOP_AUTO_UPDATE_ENV =production DSH_DESKTOP_MANDATORY_UPDATE_TEST_ORIGIN =... DSH_DESKTOP_MANDATORY_UPDATE_PROD_ORIGIN =...
⚠️ 두 번째 줄에 주목 — 나는 첫 버전에서 간편하게 하려고 test를 넣었고, 그러고 나서 최대의 함정을 맞았다.
함정 3(가장 큰 것): 설치 후 열자마자 화면이 흐려지고 완전히 멈춤
첫 패키지를 설치 불필요 디렉터리에 넣고 exe를 더블클릭하니: 창 전체의 글자가 번져 마치 유리 한 겹이 낀 것 같고, 그러고 나서 창 전체가 클릭이 안 됐다.
GPU? DPI? 둘 다 아니다. 조사 체인(각 단계마다 실증이 있다):
- .env.windows의 DSH_DESKTOP_AUTO_UPDATE_ENV=test → 패키징 시 "페이수 테스트 환경 강제 업데이트 정책"(dshMandatoryUpdatePolicy, authentication: "feishu-test" 포함)을 app.asar의 루트 package.json에 구워 넣었다;
- 시작 시 셸이 테스트 환경 업데이트 정책을 요청 → 401 미로그인 반환;
- 패키징 상태 → "테스트 환경 로그인 / 페이수 로그인 요청" 차단 모달 창을 띄운다;
- 모달 창을 만들 때 메인 창에 CSS 한 줄을 주입한다 — 전 코드베이스에서 유일한 주입 지점:
parent. webContents . insertCSS ( 'body { filter: blur(2px) !important; }' )
→ 이것이 바로 "흐림"이다;
- 모달 창은 modal: true가 메인 창의 모든 입력을 막는데, 로컬 unsigned 패키지는 영원히 페이수 SSO를 통과하지 못한다;
- 설상가상: 로그인 창 자체의 문서 JS는 인터페이스가 resolve되어야 버튼을 렌더링하는데, 실측 결과 <body>가 빈 상태다 — "나중에" 버튼조차 없다.
= 흐림 + 멈춤, 클릭할 곳조차 없다.
수정(양쪽 동시 진행):
- 근본 해결: .env.windows를 DSH_DESKTOP_AUTO_UPDATE_ENV=production으로 변경 — production은 익명 인증이고, prod 엔드포인트는 미등록 bundle에 대해 실측 404(비 force)를 반환 → 팝업이 안 뜬다. 다시 패키징한 것은 원래부터 정상이다;
- 이미 받은 패키지를 다시 패키징하지 않고도 고치는 법: asar 인플레이스 핫패치 — 루트 package.json의 정책 필드를 같은 길이의 공백으로 치환해 지우고, asar header에 있는 해당 파일의 SHA256 2곳(integrity.hash + integrity.blocks[0])을 동시에 바꾼다. 파일 크기는 1바이트도 변하지 않으므로 재패키징이 필요 없다. 핵심 함정: 데이터 슬롯의 오프셋을 손으로 계산하지 말 것(asar header는 4바이트 정렬이라 1바이트만 틀려도 다음 파일 경계를 망가뜨린다), @electron/asar의 extractFile로 ground truth를 얻고, 고유 내용으로 위치를 찾아 치환할 것.
검증 3종 세트를 마치고 나서: asar 안에서 정책 필드를 읽어보니 undefined가 나왔고, --remote-debugging-port를 붙여 실행한 뒤 targets에는 메인 창만 남고 update-dialog는 없었으며, 메인 창의 getComputedStyle(document.body).filter === 'none' 이었다. UI는 온전히 사용 가능했다.
이 함정에서 얻은 교훈: asar에 패키징된 모든 기본값은 곧 "출하 동작"이다. env 하나의 선택(test vs production)이 당신의 패키지가 출하 상태에서 "쓸 수 있는 것"이 될지 "벽돌"이 될지를 직접 결정한다.
부록: 반복 작업에 매번 전체 CI가 필요하지는 않다
로컬에서 코드 수정 → 영향받은 패키지 재빌드 → asar 재패키징(unpacked 집합을 동일하게 유지) → resources/app.asar 에 교체 투입. 나는 스스로를 위해 버전 스냅샷( app.asar.latest / 각 패치 전 백업)을 남겨두었고, 원클릭 롤백이 된다. 요 며칠 나는 이렇게 "파일 트리 검색, 트리 내 위치 찾기, md 미리보기" 몇 가지 소기능에서 빠르게 반복 작업을 했다.
五、소결
- 도구는 바꿀 수 있어도 코드 주권은 양보할 수 없다. 업로드 동작이 "수정 완료 + 오픈소스 예정"인 것은 좋은 일이지만, 기존 데이터에 대해서는 이미 기정사실이다—이 셈을 따져본 뒤 나는 더 망설이지 않았다;
- DSH로 바꾸는 가치는 단지 "코딩 에이전트 하나를 바꾸는 것"이 아니라, 전체 툴체인이 자기 손으로 돌아오는 것이다: 소스코드, 패키징, 패치, 반복 작업이 전부 자기 눈앞에 있다;
- 마찬가지로 Windows에서 DSH를 쓰고 싶은 학생들에게: 브라우저를 fork 하나 → workflow 파일 하나 추가(위의 세 함정을 피할 것) → Actions에서 한 번 클릭 → 25분 후 Artifacts 안에 당신의 .exe가 있다.
P.S. 옮겨온 뒤 나는 DSH에서 파일 미리보기 관련 소소한 강화(파일 검색, 트리 내 위치 찾기, md 이미지 렌더링)를 좀 했다. 디테일을 탄탄하게 다듬은 뒤 상황을 봐서 별도 글로 쓸 것이며, 예고는 하지 않는다.
참고 자료(플랫폼이 외부 링크를 지원하지 않아 본문 핵심 부분은 이미 캡처해 두었다; 아래는 원문 주소이며 직접 방문할 수 있다)
- ferstar:《ZCode의 전체 Git 이력 무음 업로드 꼼수 파헤치기》 blog.ferstar.org/posts/zcode…
- NodeLoc:《급함!!! ZCode가 저장소 전체 스냅샷을 무음 업로드한다: Windows 실측 확인 + 3중 방어 적용》 www.nodeloc.com/t/topic/109…
- DoNews:《즈푸 ZCode가 사용자 저장소를 업로드? 공식 사과: 수정 완료, 오픈소스 예정》(2026-09-18) www.donews.com/news/detail…
- V2EX 토론:《작성자가 ZCode가 Git 전체 이력을 무음 업로드한다고 주장》 global.v2ex.co/t/1242957
- DeepSeek Harness 업스트림 저장소 github.com/deepseek-ai…
- 작성자 fork(본문 workflow 파일과 모든 commit 포함) github.com/menglidong/…
- 작성자 다른 작품: jeeflow 오픈소스 워크플로 엔진 문서 사이트 jeeflow-doc.mldong.com
mldong
java 고급 엔지니어 @某研究所
130
글
375k
조회
936
팔로워