Skip to content
This repository was archived by the owner on Jul 12, 2026. It is now read-only.

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mac-proxy-sync

Zero-config macOS system-proxy → shell-env bridge. Runs claude, codex, npm, git, curl, pip through your local proxy without ever typing export https_proxy=… again.

License: MIT Platform: macOS Shell: zsh Claude Code skill


Who this is for

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.

What it does

  • 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 by claude, 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 to launchctl setenv so 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.

Install

git clone https://github.com/Lego1997/mac-proxy-sync.git
cd mac-proxy-sync
bash scripts/install.sh

Then, in the current shell only:

source ~/.zshenv && source ~/.zshrc && syncproxy

New Terminal tabs auto-activate. That's the whole setup.

Use

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 syncproxy in any Terminal tab, then quit and relaunch the app from the Dock.

Check status

bash scripts/status.sh

Dumps:

  • current scutil --proxy output
  • current shell proxy env
  • launchctl getenv values (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.proxy overrides (these silently trump env if set)
  • a connectivity probe against api.anthropic.com

First thing to run when something isn't working.

Uninstall

bash scripts/uninstall.sh

Removes 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).

How it works

Two marker-bounded blocks, installed idempotently:

~/.zshenv — function + one-shot call

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).

~/.zshrc — 60-second TTL precmd hook + syncproxy command

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.

Design decisions

  • scutil --proxy over networksetup -getwebproxy <service>: scutil reads the active network service's proxy without needing to know whether you're on Wi-Fi, Ethernet, or USB tethering.
  • Both case variants (http_proxy and HTTP_PROXY): curl / npm / python read lowercase; Go and JVM tools read uppercase. Setting both covers every runtime you hit.
  • ALL_PROXY=socks5h://… (5h, not 5): 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 setenv for 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.

Compatibility

Platform Status
macOS Sequoia (15.x) ✅ tested
macOS Sonoma (14.x) ✅ should work (same scutil output format)
macOS ≤ Ventura ⚠️ untested; 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})

Claude Code users

This repo doubles as a Claude Code skill. After cloning, symlink into your skills directory:

ln -s "$PWD" ~/.claude/skills/mac-proxy-sync

Claude 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.

Troubleshooting

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.

Contributing

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.proxy or /etc/environment)
  • A LaunchAgent variant for instant refresh on network change, opt-in via install.sh --launchagent
  • SSH-over-SOCKS ~/.ssh/config generator (out of current scope but adjacent)

License

MIT — use freely, attribution appreciated, no warranty.

About

Zero-config macOS system-proxy → shell-env bridge. Auto-exports http_proxy/https_proxy/ALL_PROXY from scutil into zsh on every shell start, refreshes every 60s between prompts, mirrors to launchctl for GUI apps. For developers behind Clash/V2Ray/Mihomo proxies with rotating ports.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages