Skip to content

fix(functions): copy headers in invoke() to avoid leaking them across calls#1536

Open
winklemad wants to merge 1 commit into
supabase:mainfrom
winklemad:fix/functions-invoke-header-leak
Open

fix(functions): copy headers in invoke() to avoid leaking them across calls#1536
winklemad wants to merge 1 commit into
supabase:mainfrom
winklemad:fix/functions-invoke-header-leak

Conversation

@winklemad

Copy link
Copy Markdown

No linked issue — found by reading the functions client.

What kind of change does this PR introduce?

Bug fix.

What is the current behavior?

AsyncFunctionsClient.invoke() (and its sync twin) aliases the client's persistent header dict instead of copying it:

headers = self.headers
...
headers.update(invoke_options.get("headers", {}))   # mutates self.headers
...
headers["Content-Type"] = "application/json"         # mutates self.headers

So every call's one-off headers option, its x-region, and the body Content-Type are written back onto self.headers and leak into all later invoke() calls:

await client.invoke("fn1", {"body": {...}, "headers": {"X-Once": "1"}})
await client.invoke("fn2")   # still sends X-Once: 1 and a stale Content-Type

What is the new behavior?

invoke() copies the client headers (headers = {**self.headers}) before mutating them, so per-call headers stay per-call and self.headers is never modified. This matches functions-js, which builds a fresh header object per request, and completes the header-mutation fix from #1249 (which corrected the constructor but left this invoke() path aliasing self.headers).

Additional context

Fixed in both _async and _sync clients. Added test_invoke_does_not_leak_headers_between_calls to the async and sync test suites — it fails on the current code (self.headers gains Content-Type and the per-call header) and passes with the fix. Full functions test suite passes; ruff check + format are clean.

… calls

`invoke()` assigned `headers = self.headers` and then mutated it with the
call's `headers` option and the body `Content-Type`, writing those back
onto the client's persistent headers. Every later `invoke()` then
inherited them. Copy `self.headers` instead, matching functions-js (which
builds a fresh header object per call) and the header-mutation fix in
supabase#1249, which corrected the constructor but left this path untouched.
@winklemad
winklemad requested review from a team and o-santi as code owners July 10, 2026 15:05
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