Skip to content

fix(webhook): cleanup stale connectors + kill cloudflared process group#173

Merged
bobakemamian merged 1 commit into
mainfrom
fix/webhook-cleanup-stale-connectors
Apr 21, 2026
Merged

fix(webhook): cleanup stale connectors + kill cloudflared process group#173
bobakemamian merged 1 commit into
mainfrom
fix/webhook-cleanup-stale-connectors

Conversation

@bobakemamian

Copy link
Copy Markdown
Contributor

Summary

Two related lifecycle bugs in buttons webhook listen with cloudflared:

  1. Stale connectors stay registered at the Cloudflare edge after a prior listen exits without a clean shutdown. CF load-balances the tunnel hostname across the dead connector (forwarding to a deallocated local port) and the fresh one, which makes /healthz readiness probes flaky and causes real webhook traffic to miss the new process. Fix: before cloudflared tunnel run in named mode, call cloudflared tunnel cleanup <NAME> to drop stale connectors at the edge. Best-effort with a 10s cap; no-op when nothing is stale. Only for ModeNamed (cert.pem identity) — quick tunnels have no persistent identity to clean up. Docs.

  2. Tunnel.Stop() didn't reliably kill the cloudflared child on macOS — it signalled the parent PID directly, so any forked helper (or a briefly-ignored SIGTERM) could outlive us and re-create bug chore(deps): Bump docker/setup-buildx-action from 3 to 4 #1 on the next listen. Fix: set SysProcAttr.Setpgid=true on the child, then SIGTERM -pgid → 3s grace → SIGKILL -pgid in Stop(). Mirrors internal/engine/execute.go:killProcessGroup. Windows keeps its existing os.Interrupt + Kill() fallback via the signal_windows.go split.

Test plan

  • go build ./... clean
  • go vet ./... clean
  • go test ./internal/webhook/... green — new tunnel_cleanup_test.go verifies:
    • named-mode path invokes the cleanup runner with the tunnel name
    • empty name is skipped (guard against "clean up all my tunnels")
    • quick-mode StartTunnel path never invokes cleanup
  • manual: buttons webhook listen, kill -9 the parent, restart — readiness should pass first try (previously required cloudflared tunnel cleanup by hand)

🤖 Generated with Claude Code

Two related webhook-tunnel lifecycle bugs in `buttons webhook listen`:

1. Stale connectors at the Cloudflare edge. If a prior `listen` exited
   without cloudflared tearing down cleanly (crash, SIGKILL, Stop()
   returning before the child actually died), the connector stays
   registered at the CF edge forwarding to a now-deallocated local
   port. A subsequent `listen` spins up a fresh connector but CF load-
   balances across both; half of /healthz probes hit the dead one and
   the readiness check fails intermittently. Fix: before `tunnel run`
   in named mode, run `cloudflared tunnel cleanup <NAME>` to drop all
   stale connectors at the edge. Best-effort, 10s cap, no-op when
   nothing stale exists. Only runs for ModeNamed (cert.pem identity);
   ModeQuick's ephemeral trycloudflare.com tunnels have no persistent
   identity to clean up.

   Docs: https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/local-management/tunnel-commands/#cleanup

2. Tunnel.Stop() didn't reliably kill the cloudflared child on macOS.
   It signalled the parent PID directly; if cloudflared forked any
   helper or briefly ignored SIGTERM, Stop() returned while the child
   (and its registered edge connector) was still alive — re-creating
   issue 1 on the next listen. Fix: set SysProcAttr.Setpgid=true on
   the child so we own a process group, SIGTERM -pgid in Stop(), wait
   3s, SIGKILL -pgid. Mirrors internal/engine/execute.go:killProcessGroup.
   Windows keeps its os.Interrupt + Kill() fallback (no Setpgid
   equivalent) via the existing signal_windows.go split.

Test: tunnel_cleanup_test.go swaps the cleanup runner with a counter
and verifies named-mode invocation, empty-name skip, and quick-mode
skip — no subprocess, no cloudflared binary needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bobakemamian bobakemamian force-pushed the fix/webhook-cleanup-stale-connectors branch from c527f82 to c4c8fcc Compare April 21, 2026 06:29
@bobakemamian bobakemamian merged commit 010548f into main Apr 21, 2026
16 checks passed
@bobakemamian bobakemamian deleted the fix/webhook-cleanup-stale-connectors branch April 21, 2026 06:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant