Skip to content

fix: handle tuples in deepcopy_minimal to prevent mutation of user data#1213

Open
roli-lpci wants to merge 1 commit intoanthropics:mainfrom
roli-lpci:fix/deepcopy-minimal-tuple-handling
Open

fix: handle tuples in deepcopy_minimal to prevent mutation of user data#1213
roli-lpci wants to merge 1 commit intoanthropics:mainfrom
roli-lpci:fix/deepcopy-minimal-tuple-handling

Conversation

@roli-lpci
Copy link

Summary

deepcopy_minimal copies dict and list but intentionally skips tuple (returning it as-is). When files.beta.upload() is called with a 4-element FileTypes tuple containing a headers dict, the tuple passes through uncopied. httpx then mutates the user's original headers dict by injecting Content-Type during multipart encoding.

headers = {'X-Custom': 'value'}
file = ('test.txt', b'data', 'text/plain', headers)
client.files.beta.upload(file=file)
print(headers)  # {'X-Custom': 'value', 'Content-Type': 'text/plain'} — mutated by httpx

Users who reuse file tuples across multiple uploads see their headers silently contaminated.

Changes

  • src/anthropic/_utils/_utils.py — Add tuple handling to deepcopy_minimal, matching the existing pattern for dicts and lists. Tuples are recursively copied so mutable elements (like headers dicts) get independent copies.
  • tests/test_deepcopy.py — Replace the old assertion that tuples pass through uncopied (assert obj3 is obj4) with 4 new tests covering: simple tuples, tuples with mutable contents (the bug scenario), dicts containing tuples (files.beta.upload pattern), and lists of tuples (skills/versions pattern).

Note on behavioral change: The previous test explicitly asserted tuples are NOT copied. This was a deliberate scope limitation of deepcopy_minimal that did not anticipate the httpx mutation path. This PR intentionally reverses that — tuples are now recursively copied like dicts and lists.

Note on Stainless: _utils/_utils.py has no codegen header, consistent with hand-maintained utility code. If this change should be applied to the generation template instead, happy to redirect.

Testing

  • 550 existing tests pass, 0 regressions
  • 4 new tests verify tuple copying and mutation isolation

Fixes #1202

…tion

`deepcopy_minimal` copies `dict` and `list` but skips `tuple`, returning
it as-is. When `files.beta.upload()` is called with a `FileTypes` tuple
containing a headers dict, the tuple passes through uncopied. httpx then
mutates the user's original headers dict by injecting `Content-Type`
during multipart encoding, permanently contaminating user data.

This adds tuple handling to `deepcopy_minimal`, matching the existing
pattern for dicts and lists. Tuples are recursively copied so that
mutable elements (like headers dicts) get independent copies.

Note: `_utils/_utils.py` has no Stainless codegen header, consistent
with hand-maintained utility code. If this change should be applied to
the generation template instead, happy to redirect.

Fixes anthropics#1202

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@roli-lpci roli-lpci requested a review from a team as a code owner February 28, 2026 23:17
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.

Use of deepcopy_minimal in files.beta.upload mutates dict in place

1 participant