Files
pad/internal/webhooks
xarmian aeb80883f0 fix(webhooks): track delivery goroutines + bounded retry (BUG-2012) (#864)
* fix(webhooks): track delivery goroutines + bounded retry (BUG-2012)

Webhook deliveries ran in untracked `go d.deliver(...)` goroutines that
write to the store — the BUG-842 shutdown-race class that goAsync was
built to prevent — and had no retry.

- Inject a `spawn func(func())` into Dispatcher (SetSpawn). Server wires
  s.goAsync via SetWebhookDispatcher so deliveries are tracked on s.bg
  (Stop() waits for in-flight deliveries) and inherit goAsync's panic
  recovery (BUG-2011). Nil spawn falls back to a plain goroutine, so
  standalone Dispatcher usage is unchanged.
- Add a bounded in-goroutine retry: up to 3 attempts with linear backoff
  on transient failures (network error / timeout / 5xx). Permanent
  failures (4xx, SSRF block, malformed URL) stop immediately. The final
  outcome is recorded once via UpdateWebhookFailure.
- Tests: delivery runs on the injected spawn; transient 5xx retries to
  the cap; permanent 4xx does not; a recovered transient records success.

Claude-Session: https://claude.ai/code/session_019knGmnHcx5rrgWXQ8V8DZS

* fix(webhooks): classify redirect-block + non-5xx as permanent (Codex review)

Second Codex review of PR #864 found two retry-classification gaps:

- P2: an SSRF-blocked (or looping) redirect surfaces as an error from
  client.Do (via CheckRedirect), which the retry loop treated as
  transient — so a redirect to an internal target was retried 3x with
  backoff. Wrap a sentinel (errRedirectRejected) in checkRedirect and
  match it with errors.Is (url.Error unwraps to it) to classify these
  as permanent — attempted once, no retries.
- P3: the status switch treated every non-2xx/non-4xx as transient.
  Narrow transient to 5xx only; 4xx/3xx-no-Location/1xx are permanent,
  matching the stated "network error / timeout / 5xx" retry policy.

Adds TestDispatcher_RedirectBlockIsPermanent.

Claude-Session: https://claude.ai/code/session_019knGmnHcx5rrgWXQ8V8DZS
2026-07-08 12:02:49 -04:00
..