Skip to content

devproxy stop --force cannot restore the system proxy after a crashed instance #1731

Description

@waldekmastykarz

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)

  1. Start a detached instance that owns the system proxy:
    devproxy --detach --as-system-proxy true
    
  2. Confirm the OS proxy is enabled (System Settings → Network → Proxies, or networksetup -getsecurewebproxy "Wi-Fi").
  3. Simulate a crash (cannot run cleanup handlers):
    kill -9 <devproxy-pid>
    
  4. The OS proxy is still enabled, pointing at the dead port.
  5. Run the documented force-cleanup:
    devproxy stop --force
    
    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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions