Zero-config macOS system-proxy → shell-env bridge. Runs
claude,codex,npm,git,curl,pipthrough your local proxy without ever typingexport https_proxy=…again.
macOS developers behind a local HTTP/HTTPS proxy whose port rotates — common with Clash, V2Ray, Mihomo, Surge, or similar tools used in mainland China, restrictive corporate networks, or university VPNs. If you've typed this more times than you'd like:
export https_proxy=http://127.0.0.1:10905 http_proxy=http://127.0.0.1:10905…this is for you. One install, then every new Terminal tab picks up the current port from System Settings automatically.
- Reads your active macOS system proxy via
scutil --proxy. - Exports
http_proxy/https_proxy/ALL_PROXY/no_proxy(both lowercase and uppercase variants) for every zsh invocation — including non-interactive subshells spawned byclaude,codex,make, etc. - Refreshes every 60 s between prompts, so long-running sessions pick up port rotations without manual intervention.
- Exposes
syncproxy, a command that additionally mirrors env tolaunchctl setenvso GUI-app subprocesses (Codex.app, ChatGPT.app Node helpers, VS Code tasks) inherit the proxy too. - Cleanly unsets env when you turn the system proxy off in System Settings — no stale ports.
git clone https://github.com/Lego1997/mac-proxy-sync.git
cd mac-proxy-sync
bash scripts/install.shThen, in the current shell only:
source ~/.zshenv && source ~/.zshrc && syncproxyNew Terminal tabs auto-activate. That's the whole setup.
Nothing new to type. claude, codex, curl, git, npm, pip just
work. When you rotate the port in System Settings → Wi-Fi → Details →
Proxies:
- New tabs pick up the new port immediately.
- Existing tabs catch up within 60 s (precmd hook).
- Already-running GUI apps need a restart: run
syncproxyin any Terminal tab, then quit and relaunch the app from the Dock.
bash scripts/status.shDumps:
- current
scutil --proxyoutput - current shell proxy env
launchctl getenvvalues (what GUI apps will inherit)- whether marker blocks are present in
~/.zshenv/~/.zshrc - PATH resolution for
claude/codex/node/npm/git/curl git config http.proxyoverrides (these silently trump env if set)- a connectivity probe against
api.anthropic.com
First thing to run when something isn't working.
bash scripts/uninstall.shRemoves both marker blocks from your dotfiles, clears launchctl setenv for
each var, and prints the unset …; exec zsh commands you need to paste
into your current shell (a subshell can't unset its parent's env — that's a
shell fact, not a bug).
Two marker-bounded blocks, installed idempotently:
Runs for every zsh invocation, including non-interactive (zsh -c …,
subshells spawned by other programs). This is the critical placement: a CLI
binary like claude spawns subshells that don't read .zshrc, but they
do read .zshenv.
The parser is a single awk invocation with exact-field match
($1 == "HTTPPort" instead of anchored regex), which produces an
eval-able shell line in one pass (~8 ms total). It guards on both
HTTPEnable == 1 and non-empty host/port before exporting, so you
never get the http_proxy=http://: broken-URL footgun if HTTPS is
configured but HTTP isn't (or vice-versa).
Re-reads scutil --proxy between prompts, but at most once per minute.
Ensures long-running claude / codex sessions don't silently hold a
stale port when the proxy rotates. The alternative — a LaunchAgent watching
State:/Network/Global/Proxies — is instant but adds a moving part.
60 s TTL is the right tradeoff.
Both blocks are fenced by # >>> mac-proxy-sync BEGIN >>> /
# <<< mac-proxy-sync END <<< (the conda/pyenv convention), so install.sh
can safely update them, diff them, or uninstall.sh can cleanly remove
them.
scutil --proxyovernetworksetup -getwebproxy <service>:scutilreads the active network service's proxy without needing to know whether you're on Wi-Fi, Ethernet, or USB tethering.- Both case variants (
http_proxyandHTTP_PROXY): curl / npm / python read lowercase; Go and JVM tools read uppercase. Setting both covers every runtime you hit. ALL_PROXY=socks5h://…(5h, not5): hostname resolution happens on the proxy side, which matters when local DNS can't resolve blocked hostnames.- LAN CIDRs in
no_proxy:localhost,127.0.0.1,::1,.local,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12,169.254.0.0/16. Local dev servers, metadata endpoints, and link-local traffic bypass the proxy (which would otherwise fail). - Unset-on-disable: if you toggle the system proxy off in System Settings, new shells have no proxy env. No stale port carries over.
launchctl setenvfor GUI apps: only helps apps launched by launchd after the setenv call, and only for subprocesses that read env (Node, Python, shell helpers). Native apps using CFNetwork (most macOS apps) read System Settings directly and don't need env at all — but their helper processes often do.
| Platform | Status |
|---|---|
| macOS Sequoia (15.x) | ✅ tested |
| macOS Sonoma (14.x) | ✅ should work (same scutil output format) |
| macOS ≤ Ventura | scutil --proxy has existed forever |
| Linux | ❌ scutil is macOS-only; concept applies but needs a rewrite |
| bash | ❌ uses zsh-specific features (precmd, emulate, ${(P)v}) |
This repo doubles as a Claude Code skill. After cloning, symlink into your skills directory:
ln -s "$PWD" ~/.claude/skills/mac-proxy-syncClaude Code will then auto-discover it. Trigger phrases include "set up
proxy auto-sync", "why is npm / git / curl failing from Terminal in
China", "disable mac-proxy-sync", "I'm moving back to the US — remove the
proxy setup". See SKILL.md for the full trigger description
and supported modes.
| Symptom | Likely cause | Fix |
|---|---|---|
claude --version hangs |
proxy app not running | start Clash / V2Ray / etc., then syncproxy |
git pull fails, curl works |
git config http.proxy set |
git config --global --unset http.proxy |
| VS Code tasks have no proxy | tasks inherit parent env at launch | quit VS Code, run syncproxy, relaunch |
| Stale port in one tab | TTL hasn't fired | just run syncproxy in that tab |
| Got rate-limited by Anthropic | proxy IP is shared | not this tool's problem |
Run bash scripts/status.sh for a full diagnostic dump.
Issues and PRs welcome. Particularly interested in:
- bash support (port the function, drop
precmd, replace${(P)v}) - Linux equivalent (read
gsettings get org.gnome.system.proxyor/etc/environment) - A LaunchAgent variant for instant refresh on network change, opt-in via
install.sh --launchagent - SSH-over-SOCKS
~/.ssh/configgenerator (out of current scope but adjacent)
MIT — use freely, attribution appreciated, no warranty.