Description
When a detached Dev Proxy instance running with --as-system-proxy true is terminated uncleanly (crash, kill -9, OOM, power loss — anything that prevents graceful shutdown), the OS HTTP/HTTPS proxy is left pointing at the now-dead Dev Proxy port. The documented recovery command, devproxy stop --force, does not fix this — it reports Dev Proxy is not running. and never restores the system proxy. The machine is left without working network access until the user manually disables the proxy (e.g. macOS networksetup ... -setsecurewebproxystate off / toggle-proxy.sh off, or Windows Internet Settings) or starts a fresh --as-system-proxy instance that overwrites the stale setting.
Repro (macOS, but the logic is cross-platform)
- Start a detached instance that owns the system proxy:
devproxy --detach --as-system-proxy true
- Confirm the OS proxy is enabled (System Settings → Network → Proxies, or
networksetup -getsecurewebproxy "Wi-Fi").
- Simulate a crash (cannot run cleanup handlers):
- The OS proxy is still enabled, pointing at the dead port.
- Run the documented force-cleanup:
Actual: prints
Dev Proxy is not running. and exits 1; the system proxy stays enabled.
Expected: the system proxy is disabled (network restored).
Root cause
StopCommand (no --pid) enumerates instances via StateManager.LoadAllStatesAsync(), then calls ForceStopAsync per instance — and ForceStopAsync is the only place that calls SystemProxyManager.Disable() during force-stop.
But StateManager.LoadStateFromFileAsync prunes any state file whose PID is no longer alive — it deletes the file and returns null:
// Verify the process is still running
if (!IsProcessRunning(state.Pid))
{
// Clean up stale state file
DeleteFile(filePath);
return null;
}
So for a crashed instance the state record is gone by the time stop --force reads it ⇒ LoadAllStatesAsync() returns empty ⇒ StopCommand prints Dev Proxy is not running. ⇒ Disable() is never invoked. In other words, --force only works for a hung-but-alive instance (PID still running); it cannot recover a dead one, which is exactly the scenario --force crash-cleanup is meant for.
Suggested fix (sketch)
Decouple "restore the OS proxy" from "find a live instance". A few options:
- In the force path, when a stale state file is found for a dead PID and it has
asSystemProxy: true, call SystemProxyManager.Disable() before deleting it (i.e. don't prune-then-forget a system-proxy owner).
- Or have
devproxy stop --force with no matching instance still best-effort call SystemProxyManager.Disable() (engine-agnostic, idempotent, cross-platform), so it always restores the OS proxy.
- Optionally surface the same recovery on the next
devproxy run startup (there is already a FindSystemProxyInstanceAsync hook), but note it currently relies on the same prune-on-dead-PID logic and would need the same adjustment.
Notes
SystemProxyManager.Disable() is already engine-agnostic, idempotent, and cross-platform (Windows WinINET + macOS toggle-proxy.sh), so restoring unconditionally on --force is low-risk.
- Found while live-testing the system-proxy on/off lifecycle. This is independent of any one proxy engine — it lives entirely in
StopCommand / StateManager.
Description
When a detached Dev Proxy instance running with
--as-system-proxy trueis terminated uncleanly (crash,kill -9, OOM, power loss — anything that prevents graceful shutdown), the OS HTTP/HTTPS proxy is left pointing at the now-dead Dev Proxy port. The documented recovery command,devproxy stop --force, does not fix this — it reportsDev Proxy is not running.and never restores the system proxy. The machine is left without working network access until the user manually disables the proxy (e.g. macOSnetworksetup ... -setsecurewebproxystate off/toggle-proxy.sh off, or Windows Internet Settings) or starts a fresh--as-system-proxyinstance that overwrites the stale setting.Repro (macOS, but the logic is cross-platform)
networksetup -getsecurewebproxy "Wi-Fi").Dev Proxy is not running.and exits 1; the system proxy stays enabled.Expected: the system proxy is disabled (network restored).
Root cause
StopCommand(no--pid) enumerates instances viaStateManager.LoadAllStatesAsync(), then callsForceStopAsyncper instance — andForceStopAsyncis the only place that callsSystemProxyManager.Disable()during force-stop.But
StateManager.LoadStateFromFileAsyncprunes any state file whose PID is no longer alive — it deletes the file and returnsnull:So for a crashed instance the state record is gone by the time
stop --forcereads it ⇒LoadAllStatesAsync()returns empty ⇒StopCommandprintsDev Proxy is not running.⇒Disable()is never invoked. In other words,--forceonly works for a hung-but-alive instance (PID still running); it cannot recover a dead one, which is exactly the scenario--forcecrash-cleanup is meant for.Suggested fix (sketch)
Decouple "restore the OS proxy" from "find a live instance". A few options:
asSystemProxy: true, callSystemProxyManager.Disable()before deleting it (i.e. don't prune-then-forget a system-proxy owner).devproxy stop --forcewith no matching instance still best-effort callSystemProxyManager.Disable()(engine-agnostic, idempotent, cross-platform), so it always restores the OS proxy.devproxy runstartup (there is already aFindSystemProxyInstanceAsynchook), but note it currently relies on the same prune-on-dead-PID logic and would need the same adjustment.Notes
SystemProxyManager.Disable()is already engine-agnostic, idempotent, and cross-platform (Windows WinINET + macOStoggle-proxy.sh), so restoring unconditionally on--forceis low-risk.StopCommand/StateManager.