mpfaffenberger / mpfaffenberger/code_puppy
claude_cache_client.py: httpx-level cache_control injection only runs when claude-code prefixing is on (dead code for vanilla anthropic); sync OAuth refresh blocks the event loop inside async send()
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 814
- Forks
- 278
- Avg merge
- 2d 5h
- Merged PRs (30d)
- 76
Description
Problem 1: cache_control injection is dead code on the vanilla anthropic path
In ClaudeCacheAsyncClient.send() (code_puppy/claude_cache_client.py lines ~352-366), the cache injection is nested inside the prefix-flag branch:
if body_bytes and self._apply_claude_code_prefix:
prefixed_body = self._prefix_tool_names(body_bytes)
...
# 4. Inject cache_control
cached_body = self._inject_cache_control(body_bytes)
For vanilla anthropic / custom_anthropic models, _apply_claude_code_prefix=False, so _inject_cache_control never runs at the httpx layer. Those paths rely entirely on patch_anthropic_client_messages() instead — meaning the class's namesake feature is dead weight for half of its users, and the _inject_cache_control byte-level path plus _inject_cache_control_in_payload dict-level path are ~80 lines of duplicated logic that must be kept in sync (the comment even says "Enforce here as well so the AsyncAnthropic client patch path matches the raw httpx path"). If the nesting was intentional, the dedent-level comment numbering ("3." / "4.") is misleading; if not, it's a bug.
Problem 2: blocking token refresh inside async send()
_refresh_claude_oauth_token() / _recover_claude_oauth_token_after_auth_error() are sync functions invoked directly from async def send() (lines ~322 and ~447). They call refresh_access_token(force=True) which performs a blocking HTTP token exchange — and the reauthentication callback may run a full interactive OAuth flow. This blocks the event loop for the duration: spinners freeze, other in-flight requests stall, and pause/cancel keys stop responding. This is exactly the "blocking I/O in async code" anti-pattern.
Suggested fix
- Either move
_inject_cache_controlout of the prefix branch (so API-key Anthropic models get wire-level cache control too) or delete the byte-level injector and rely solely on the SDK patch, keeping one source of truth. - Wrap blocking refresh calls:
await asyncio.to_thread(self._refresh_claude_oauth_token).
Filed by Zen Reviewer A (code-puppy-60635a)
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in code_puppy/claude_cache_client.py at ClaudeCacheAsyncClient.send(), _inject_cache_control(), _refresh_claude_oauth_token(), and _recover_claude_oauth_token_after_auth_error(). Trace both vanilla Anthropic and claude-code request paths, then verify cache injection has one intentional source of truth and OAuth refresh no longer blocks the async send event loop.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, authentication, backend, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100