Skip to content

fix(claude_code): Inject x-api-key header in CodingAgentClient requests#119322

Open
sentry[bot] wants to merge 1 commit into
masterfrom
claude/fix-claude-code-api-key-header-7cc06b
Open

fix(claude_code): Inject x-api-key header in CodingAgentClient requests#119322
sentry[bot] wants to merge 1 commit into
masterfrom
claude/fix-claude-code-api-key-header-7cc06b

Conversation

@sentry

@sentry sentry Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Summary

CodingAgentClient (the base API client for coding agent integrations) was not injecting the x-api-key authentication header into outbound requests to Anthropic's API, causing 401 Unauthorized errors during autofix on-completion callbacks.

Fixes SENTRY-5PJQ

Root Cause

The ClaudeCodeAgentIntegration.get_client() correctly passes api_key from the integration metadata to the ClaudeCodeClient constructor. However, the CodingAgentClient base class — which all coding agent API clients extend — had no mechanism to inject the API key into outbound request headers. The key was accepted by the constructor but never used to authenticate requests.

This meant that when call_on_completion_hook fired after an autofix run completed, the resulting API calls to Anthropic (e.g., GET /v1/agents/{agent_id}) were sent without the required x-api-key header, causing Anthropic to return 401.

Fix

Added x-api-key header injection to CodingAgentClient using the finalize_request hook — the same pattern used by other Sentry integration clients (Jira, Bitbucket) for injecting authentication headers:

  • __init__: Accepts an optional api_key parameter (defaults to None), stored as self._api_key
  • finalize_request: When _api_key is set, injects x-api-key header into every outgoing request

This is backwards-compatible: subclasses that don't pass api_key (GitHub Copilot uses Authorization: Bearer, Cursor uses Authorization: Bearer) are unaffected since api_key defaults to None.

Tests

Added tests verifying:

  • finalize_request adds x-api-key when api_key is provided
  • finalize_request skips the header when no api_key is set
  • Existing headers on the request are preserved
  • The header reaches the network layer through the full _requestfinalize_request pipeline

CodingAgentClient._send_request was omitting the x-api-key header when
calling Anthropic's API, causing 401 Unauthorized errors during autofix
on-completion callbacks.

The base CodingAgentClient class accepted api_key via constructor
(passed by ClaudeCodeAgentIntegration.get_client()) but never injected
it into outbound request headers. Other integration clients (Jira,
Bitbucket) use finalize_request to inject auth headers — CodingAgentClient
now follows the same pattern.

Changes:
- Accept optional api_key in CodingAgentClient.__init__
- Override finalize_request to inject x-api-key header when api_key is set
- Backwards-compatible: subclasses not passing api_key (GitHub Copilot,
  Cursor) are unaffected since api_key defaults to None

Fixes SENTRY-5PJQ
@sentry sentry Bot requested a review from a team as a code owner July 9, 2026 17:41
@github-actions github-actions Bot added the Scope: Backend Automatically applied to PRs that change backend components label Jul 9, 2026
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Backend Test Failures

Failures on a1f0278 in this run:

tests/sentry/integrations/coding_agent/test_client.py::CodingAgentClientApiKeyTest::test_request_sends_x_api_key_headerlog
[gw0] linux -- Python 3.13.1 /home/runner/work/sentry/sentry/.venv/bin/python3
tests/sentry/integrations/coding_agent/test_client.py:65: in test_request_sends_x_api_key_header
    client.get("/v1/agents/test-agent-id")
src/sentry/shared_integrations/client/base.py:484: in get
    return self.request("GET", path, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/sentry/shared_integrations/client/base.py:430: in request
    return self._request(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/sentry/shared_integrations/client/base.py:419: in _request
    return BaseApiResponse.from_response(
src/sentry/shared_integrations/response/base.py:64: in from_response
    return XmlApiResponse(response.text, response.headers, response.status_code)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
src/sentry/shared_integrations/response/xml.py:10: in __init__
    self.xml = BeautifulSoup(text, "xml")
               ^^^^^^^^^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/bs4/__init__.py:277: in __init__
    self.builder.prepare_markup(
.venv/lib/python3.13/site-packages/bs4/builder/_lxml.py:149: in prepare_markup
    for encoding in detector.encodings:
                    ^^^^^^^^^^^^^^^^^^
.venv/lib/python3.13/site-packages/bs4/dammit.py:255: in encodings
    self.declared_encoding = self.find_declared_encoding(
.venv/lib/python3.13/site-packages/bs4/dammit.py:314: in find_declared_encoding
    declared_encoding_match = xml_encoding_re.search(markup, endpos=xml_endpos)
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E   TypeError: expected string or bytes-like object, got 'MagicMock'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Backend Automatically applied to PRs that change backend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant