runWebhookAdd (internal/control/webhook.go:55) passes every webhook.ValidateURL error to c.failErr. That helper (internal/control/control.go:232) knows two error kinds — not-found and store-internal — and answers ExitUsage for everything else. So the two refusals the server decides at runtime, cannot resolve <host> and the SSRF refusal for a private address, reach clients as exit 2 alongside the scheme and empty-host checks.
Exit 2 is documented as a usage error, an app bug from a client's side. gitbay-ios maps it to "Something went wrong. Please try again." and drops the server's sentence, so a user who types a webhook URL the server cannot resolve gets no hint why. Seen 2026-09-07 with https://example.invalid/ui-smoke:
{"error":"cannot resolve example.invalid: lookup example.invalid on 46.38.252.230:53: no such host","exit_code":2,"protocol_version":1}
Suggested: answer ExitFailure for a URL the server refuses after parsing it, the way release create answers "push the tag first" with exit 1, and keep exit 2 for argv shape (missing url, bad flags). Clients already show exit 1 verbatim. The same failErr fallback will hand exit 2 to any other validator error that is neither not-found nor internal, so it may be worth a look beyond webhooks.
referenced in commit 606c4ec959 by cmc: e2e: a refused webhook URL answers exit 1
2026-09-07 19:19 UTC