go mod download intermittently fails behind congested egress or brittle mirrors. You get: a reproducible path through openclaw onboard and openclaw doctor, copy-paste health probes with explicit retry ceilings, a wrapper that rotates GOPROXY before failing the job, and patterns to surface logs back to CI without opening an SSH session. Pair this with our GOPROXY matrix for chain design—reading is public, no login.
- Silent first-hop failure: the corporate mirror answers slowly; Go times out before the comma-separated chain advances because the process never retried at the toolchain level.
- Environment drift: engineers export
GOPROXYin~/.zshrc, but the CI agent andlaunchdjobs run with a minimal environment, so module pulls still hit the wrong endpoint. - Blind failures: the orchestrator only shows “exit 1” with no pointer to which hop broke, so every incident becomes a manual SSH treasure hunt on the runner.
① Why go mod download breaks on remote Mac CI
Module fetches are a stack of DNS, TCP, TLS, HTTP, and cache behaviors. On Apple Silicon runners that share uplink with desktop automation, a spike in concurrent pulls can starve the first proxy hop even when fallback mirrors are healthy. Certificate stores can diverge between login shells and service accounts, which surfaces as TLS handshake errors that look like “bad module path.”
When OpenClaw or companion bots trigger builds, you want the host to be in a known-good state before Go runs. That is why we treat openclaw doctor as part of the same preflight family as go env: both expose PATH, permission, and networking assumptions the module step inherits. For gateway patrol patterns, see LaunchAgent health checks; for broader retry philosophy, skim failure recovery and retry.
② Install, configure, and triage checklist
Execute top to bottom on a staging Mac before you promote the same plist and CI YAML to production runners.
- Install baseline. Node 22 or newer, OpenClaw CLI available on the service user PATH, non-interactive
npmor official installer—no sudo prompts in CI. - Onboard and doctor. Run
openclaw onboardonce per machine image, thenopenclaw doctor; resolve every warning about permissions, missing CLIs, or config paths. - Go toolchain parity. Pin
go versionacross images; printgo env GOPROXY GOPRIVATE GONOPROXY GOSUMDB GOMODCACHEat job start. - Probe before export. Hit each GOPROXY base URL with
curlusing explicit timeouts; record HTTP status and wall time in the job log. - Wrap downloads. Run
go mod downloadinside a script that can switch chains and cap total attempts—never bare commands in opaque wrappers. - Daemon optional. If the Mac should self-heal between CI jobs, install a LaunchAgent that runs the probe on an interval with
ThrottleIntervalto avoid tight crash loops. - Log hygiene. Tee verbose module output to
$RUNNER_TEMP/gomod.logor equivalent; scrub bearer tokens before upload. - Escalation thresholds. Alert when two consecutive probe cycles fail five minutes apart, or when
go mod downloadexhausts two full chain rotations in a single job.
③ Script templates: probe, backoff, and download wrapper
The probe below treats HTTP 200–499 except timeouts as a signal the TCP/TLS path works; tune the sample path for your mirror vendor. Backoff follows 2s, 4s, 8s with three tries per URL—about twenty-five seconds worst case per hop, which is usually cheaper than a failed CI run.
#!/usr/bin/env bash
set -euo pipefail
# goproxy-probe.sh — returns first healthy base URL or exits 1
CANDIDATES=(
"https://corp-go.example.com"
"https://goproxy.cn"
"https://proxy.golang.org"
)
probe_one() {
local url="$1" attempt=1 delay=2
while [[ $attempt -le 3 ]]; do
if curl -fsS --connect-timeout 8 --max-time 20 -o /dev/null \
"$url/github.com/golang/glog/@v/v1.2.0.info" \
|| curl -fsS --connect-timeout 8 --max-time 20 -o /dev/null "$url/"; then
echo "$url"
return 0
fi
sleep "$delay"
delay=$(( delay * 2 ))
attempt=$(( attempt + 1 ))
done
return 1
}
for c in "${CANDIDATES[@]}"; do
if base=$(probe_one "$c"); then
echo "GOPROXY_PRIMARY=$base"
exit 0
fi
done
echo "all GOPROXY candidates failed" >&2
exit 1
Wrapper sketch: resolve GOPROXY_PRIMARY, export GOPROXY="$GOPROXY_PRIMARY,https://proxy.golang.org,direct" (adjust for policy), run go mod download; on failure print go env -json | jq if available, then second chain; stop after two rotations.
#!/usr/bin/env bash
set -euo pipefail
LOG="${RUNNER_TEMP:-/tmp}/gomod-$(date +%s).log"
export GOPROXY="$(./goproxy-probe.sh | sed -n 's/^GOPROXY_PRIMARY=//p'),https://proxy.golang.org,direct"
echo "Using GOPROXY=$GOPROXY" | tee -a "$LOG"
if ! go mod download -x 2>&1 | tee -a "$LOG"; then
export GOPROXY="https://proxy.golang.org,direct"
echo "Fallback GOPROXY=$GOPROXY" | tee -a "$LOG"
go mod download -x 2>&1 | tee -a "$LOG"
fi
GitHub Actions example (summary echo): append a short block to $GITHUB_STEP_SUMMARY when the file exists so the failing hop appears beside the red check—no dashboard login required to read it.
if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then
{
echo "### go mod fetch"
echo '```'
tail -n 40 "$LOG"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
fi
④ Manual environment edits versus automated proxy selection
| Approach | Strengths | Risks | When to pick it |
|---|---|---|---|
Manual export GOPROXY=... in shell profiles |
Fast for a single developer laptop session | CI and daemons miss the variable; rotates poorly under outage | Interactive debugging only |
| CI YAML env block with static chain | Transparent in Git history | No adaptive failover when one hop degrades | Stable egress with a trusted single mirror |
| Probe script + wrapper (this article) | Logs which hop won; bounded retries; works without a login shell | Requires maintaining candidate URLs and test paths | Shared remote Mac pools and cross-border runners |
Automation does not replace policy: keep GOPRIVATE and GONOPROXY aligned with compliance before you widen public chains. The matrix article linked above spells out those pairings.
⑤ FAQ
go mod download still fails—what next?
Check sum database connectivity (GOSUMDB), corporate TLS inspection roots (SSL_CERT_FILE / NODE_EXTRA_CA_CERTS analogs for Go), and private module paths that accidentally hit the proxy. Capture go mod download -x output.
Set ThrottleInterval to at least 60 seconds and KeepAlive true only after the binary is known stable; log stderr to ~/Library/Logs and page only on consecutive failures.
Yes. Keychain items, proxy PAC files, and sandbox profiles differ per account; mismatched users reproduce “works in SSH, fails in CI.”
Summary
Treat GOPROXY like any other flaky dependency: probe with timeouts, retry with a capped backoff, export a winning chain inside the same execution context as go mod download, and echo the evidence back to CI. Layer that on top of a clean OpenClaw baseline so automation and builds share one predictable host profile.
Continue with the blog index for more playbooks. Product pages and support are linked below—no account required to read them.