Skip to content

feat(git): Let callers map adapter errors to coded errors, like a refused host - #168

Open
vaind wants to merge 2 commits into
mainfrom
ivan/git-relay-refused-host
Open

vaind wants to merge 2 commits into
mainfrom
ivan/git-relay-refused-host

Conversation

@vaind

@vaind vaind commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Sentry's git endpoint (getsentry/sentry#128444) relays git requests through GitRelay with Sentry's BlacklistAdapter, so a GitHub Enterprise or self-managed GitLab host that resolves to an internal address is refused. The adapter refuses by raising Sentry's own RestrictedIPAddress. scm doesn't know that exception, so GitServer counts the refusal as unhandled_exception and re-raises it, and Sentry's view has to catch, report and answer it itself.

Approach

  • GitRelay(map_error=...) takes a function that gets an exception from the upstream request and returns either a coded error to raise in its place or None to let the original through unchanged. scm can't import Sentry's exception, so the caller decides which of its adapter's errors mean what. Without a mapper, nothing changes.
  • The relay's own handling runs first: requests and urllib3 exceptions still become ResourceGatewayTimeout, ResourceServiceUnavailable and the rest, and the relay's own coded errors (such as the client going away mid-body) pass straight through. So the mapper never sees an exception that holds the Authorization header, and can't relabel those.
  • A mapped error is raised from None. A refusal raised as urllib3 connects has a traceback that runs through urllib3 frames holding the Authorization header.
  • New coded error GitRemoteRefused (git_remote_refused) for a refused host. Its message doesn't name the host or address, like the relay's other errors.
  • GitServer answers it with a 502, next to git_remote_not_https, the other remote the relay won't connect to. A 503 there means "temporarily unavailable, the request can be retried" (resource_service_unavailable), and retrying a refused host never helps. Sentry answers this case with a 503 today; after the bump it becomes a 502.

Usage, for an adapter that refuses with HostRefused:

GitRelay(adapter=..., map_error=lambda e: GitRemoteRefused() if isinstance(e, HostRefused) else None)

Tests

A test adapter refuses the way BlacklistAdapter does, from inside urllib3's connect.

  • Mapped, the refusal becomes GitRemoteRefused with no cause, a suppressed context, and neither the token nor the address. GitServer answers it with a 502 and counts git_remote_refused rather than unhandled_exception.
  • With no mapper, or a mapper returning None, the original propagates as raised.
  • A requests error, a urllib3 error and the relay's own client_closed_request never reach the mapper.

Dropping from None, the _STATUS entry or the pass-through of the relay's own errors, or calling the mapper before the requests handling, each fails a test.

After release

feat makes this a minor release. Sentry's bump PR then passes map_error=lambda e: GitRemoteRefused() if isinstance(e, RestrictedIPAddress) else None in make_git_server() and drops the view's own except RestrictedIPAddress branch.

vaind added 2 commits October 27, 2026 23:46
Sentry builds its relay with BlacklistAdapter, which refuses a GitHub
Enterprise or self-managed GitLab host that resolves to an internal
address by raising its own RestrictedIPAddress from inside urllib3's
connect. That error is neither coded nor a requests exception, so
GitServer counted it as unhandled_exception and re-raised it, and Sentry
had to catch, report and answer it itself.

scm can't import Sentry's exception, so the caller names it in
GitRelay(refused_host_errors=...), and the relay raises GitRemoteRefused
in its place. GitServer then counts and answers it like its other
refusals. The original is dropped with "from None" because its traceback
runs through urllib3 frames that hold the Authorization header, and the
new error says nothing of the address.

The constructor checks the tuple because an except clause with anything
else fails only when a request does, as a TypeError chained to that
request's error.
A fixed list of refused-host exception types only covers that one case.
A map_error callable lets the caller turn any error of its adapter into
a coded error, while scm keeps owning the error classes and their status.

The relay's own conversions of requests and urllib3 errors, and its own
coded errors, still run first, so the mapper never sees an exception
that holds the Authorization header and can't relabel the client going
away. A mapped error is raised "from None" because a refusal raised as
urllib3 connects carries frames that hold the header.
@vaind vaind changed the title feat(git): Report a refused git host as git_remote_refused feat(git): Let callers map adapter errors to coded errors, like a refused host Oct 27, 2026
@vaind
vaind marked this pull request as ready for review October 27, 2026 22:53
@vaind
vaind requested a review from a team as a code owner October 27, 2026 22:53
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