go mod download가 타임아웃이나 인증서 오류로 끊기면 원인은 상위 프록시·출구 NAT·GOPRIVATE 불일치 쪽으로 치우치는 경우가 많습니다. 터미널에서 export GOPROXY=...만 반복하면 runner마다 상태가 갈라지므로, OpenClaw onboard·doctor와 LaunchAgent 상주를 공통 전제로 두고, HTTP 프로브로 살아 있는 GOPROXY 체인을 셸에 고정한 뒤 CI 요약 로그에 선택 결과를 한 줄 남기는 흐름까지 한 체크리스트로 묶습니다. 변수 의미·체인 설계는 Go modules·GOPROXY 의사결정 매트릭스와 함께 보시면 교육 비용이 줄어듭니다.
go mod download 실패 패턴과 관측 포인트
로그 키워드를 먼저 나눕니다. i/o timeout은 대역·DNS, x509는 기업 프록시의 중간 인증서, 404 Not Found는 미러 지연·비공개 모듈 경로 혼동 가능성이 큽니다. 분기할 때 go env GOPROXY GOPRIVATE GOSUMDB 세 줄을 반드시 남기고, 같은 셸에서 curl -fsSI로 상위 URL 왕복을 확인합니다.
같은 Mac에서 게이트웨이와 빌드가 함께 돌면 Node와 Go가 HTTPS_PROXY를 다르게 해석해 “OpenClaw만 정상인데 Go만 실패”하는 겉모습이 납니다. 설치·트러블슈팅 가이드로 PATH·권한을 맞춘 다음 본문 래퍼를 얹으면 원인이 섞이지 않습니다.
설치·설정·분기 체크리스트(onboard·doctor·데몬)
- 설치: 공식 절차로 CLI를 올리고
openclaw onboard로 workspace·기본 경로를 생성합니다. 대화형이 어려운 runner는 환경 변수로 비대화형에 가깝게 맞춥니다. - 건강 상태:
openclaw doctor를 파이프라인 선행 잡에 두고 경고 증가분을 로그에 남깁니다(Node 버전·설정 경로·토큰 공백 등). - 상주: 게이트웨이를 쓰는 구성이면 LaunchAgent·헬스체크 plist에
StandardOutPath·StandardErrorPath를 고정하고launchctl kickstart로 의도적 재기동까지 연습합니다. - 문제 해결: 실패 시 로그 마지막 200줄과
lsof -nP -iTCP로 포트 충돌을 보고,curl로 127.0.0.1 바인딩 응답을 확인합니다. Go 잡은go mod download -x를 한 번만 시도해 URL을 특정합니다.
GOPROXY 래퍼 템플릿: 프로브·폴백·재시도 임계
아래는 “후보 체인을 위에서부터 시도해 HTTP로 살아 있는 첫 항목을 채택”하는 최소 예입니다. 각 프로브 URL당 최대 3회 시도하고 대기는 2초·4초·8초 지수 백오프로 둡니다(회선이 매우 불안하면 4·8·16초로 늘리세요). 프로브 URL은 사내 미러가 제공하는 가벼운 경로로 바꿉니다.
scripts/goproxy_pick.sh(템플릿)#!/usr/bin/env bash
set -euo pipefail
# 예: 왼쪽부터 시도할 체인(실제 URL로 교체)
CANDIDATES=(
"https://corp-goproxy.example,https://proxy.golang.org,direct"
"https://proxy.golang.org,direct"
"direct"
)
PROBE_URLS=(
"https://corp-goproxy.example/healthz"
"https://proxy.golang.org"
"https://sum.golang.org/lookup/github.com/golang/[email protected]"
)
probe_ok() {
local url="$1"
local attempt=1
local max=3
local delay=2
while (( attempt <= max )); do
if curl -fsS --max-time 8 -o /dev/null "$url"; then
return 0
fi
sleep "$delay"
delay=$(( delay * 2 ))
attempt=$(( attempt + 1 ))
done
return 1
}
picked=""
for i in "${!CANDIDATES[@]}"; do
if probe_ok "${PROBE_URLS[$i]}"; then
picked="${CANDIDATES[$i]}"
break
fi
done
if [[ -z "$picked" ]]; then
echo "goproxy_pick: all probes failed" >&2
exit 1
fi
export GOPROXY="$picked"
echo "GOPROXY_SELECTED=$picked"
잡 안에서는 source scripts/goproxy_pick.sh 뒤에 go mod download를 실행합니다. GOPRIVATE는 저장소의 .envrc나 CI 환경 설정에 고정하고, 래퍼는 “공개 경로 생존 확인”만 담당하게 역할을 나누는 편이 읽기 쉽습니다.
CI 로그로 되돌리는 최소 단계
표준 출력만으로는 “어느 체인으로 통과했는지” 사후 추적이 어렵습니다. GitHub Actions라면 GITHUB_STEP_SUMMARY에 마크다운 한 블록을 붙이면 됩니다.
- name: Pick GOPROXY and download modules
run: |
source scripts/goproxy_pick.sh
echo "## go modules" >> "$GITHUB_STEP_SUMMARY"
echo "Using **$GOPROXY**" >> "$GITHUB_STEP_SUMMARY"
go mod download
env:
GOTOOLCHAIN: local
게이트웨이로 알림을 모아 두었다면 동일 요약을 Webhook으로 POST해 채널에 남기는 방법도 있습니다. 토큰·NO_PROXY는 공식 보안 경계에 맞춥니다.
수동 export와 자동 프로브 비교
| 방식 | 맞는 상황 | 주의 |
|---|---|---|
| 환경 변수를 수동 변경 | 단일 runner·짧은 검증·긴급 우회 | runner가 늘면 드리프트, 로그 흔적이 약함 |
| 셸 래퍼+프로브 | 본격 self-hosted 다수·국경 간 회선 변동 | 채택 체인이 로그에 남고 재현 문장이 짧아짐 |
| LaunchAgent로 주기 프로브만 | 장애 감지·알림 우선, 빌드 본체와 분리 | 빌드 직전 일시 장애는 별도 래퍼 필요 |
정리하면 doctor로 호스트를 정리하고, 빌드 직전 래퍼로 GOPROXY를 확정하고, 요약 로그에 한 줄 적는 세 단이 맞으면 분기 시간이 가장 짧아집니다.
FAQ
URL당 3회, curl --max-time 8 전후, 대기 2·4·8초 백오프가 실무에서 균형이 잘 맞습니다. 전부 실패하면 잡을 실패 처리하고 체인·방화벽을 사람이 보게 합니다.
대화형 셸에는 도움이 되지만 launchd·비로그인 runner는 프로필을 안 읽는 경우가 많습니다. 래퍼나 워크플로 prelude에서 프로브 후 export하는 편이 재현성과 로그 측면에서 유리합니다.
Authorization·프록시 자격증명이 들어간 줄은 tee 대상에서 제외하고, CI 시크릿은 마스킹 규칙을 지킵니다. 한 번이라도 토큰이 노출되면 회전을 전제로 합니다.
맺음말
OpenClaw 쪽을 onboard·doctor·데몬으로 안정화하고, Go 쪽은 GOPROXY 래퍼에 프로브·재시도 임계를 넣으면 원격 Mac runner에서도 go mod download 실패가 설명 가능한 이벤트로 남습니다.