Uh oh!
There was an error while loading. Please reload this page.
feat(core): add opt-in buffering for raw invocation event logging - #398
Conversation
Middleware wanting to log the raw request payload previously had no safe way to read IInvocationDataFeature.EventStream: it's read once, lazily, by event deserialization, and isn't guaranteed to be seekable. Reading it directly in middleware would starve deserialization. Add EnableBuffering() (mirrors ASP.NET Core's HttpRequest.EnableBuffering) which buffers the stream into a seekable MemoryStream only when needed, plus an ILambdaInvocationContext.EnableEventBuffering() convenience extension. Opt-in per invocation to avoid the copy when nobody is logging. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qn9kagNWsCubTrnzN6NEqV
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:30094736fe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
j-d-ha
left a comment
There was a problem hiding this comment.
Suggestion: could we consider making EnableEventBuffering() a context-level API, with the invocation-data feature keeping the implementation detail? It feels closer to the existing GetEvent<T>() / GetResponse<T>() helpers and would let middleware opt in without needing to know where the event stream is stored.
Current middleware usage:
lambda.UseMiddleware(async(context,next)=>{varinvocationData=context.Features.GetRequired<IInvocationDataFeature>();invocationData.EnableBuffering();usingvarreader=newStreamReader(invocationData.EventStream,leaveOpen:true);logger.LogInformation("Request: {Raw}",awaitreader.ReadToEndAsync());invocationData.EventStream.Position=0;awaitnext(context);});Possible context-level usage:
lambda.UseMiddleware(async(context,next)=>{context.EnableEventBuffering();varinvocationData=context.Features.GetRequired<IInvocationDataFeature>();usingvarreader=newStreamReader(invocationData.EventStream,leaveOpen:true);logger.LogInformation("Request: {Raw}",awaitreader.ReadToEndAsync());invocationData.EventStream.Position=0;awaitnext(context);});Not blocking—the feature-level API is coherent since it owns the stream.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qn9kagNWsCubTrnzN6NEqV
…ing' into feat/core-invocation-data-buffering
Adding EnableBuffering() directly to the public IInvocationDataFeature interface source-breaks anyone who implements it themselves (custom test hosts, hand-rolled fakes). Split it into a separate IInvocationDataBufferingFeature, probed for via the feature collection like ASP.NET Core's optional HTTP features, so IInvocationDataFeature stays untouched and existing implementations keep compiling. Addresses review feedback from chatgpt-codex-connector on PR #398. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qn9kagNWsCubTrnzN6NEqV
ncipollina
commented
Sep 4, 2026
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:4e42345cb1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Uh oh!
There was an error while loading. Please reload this page.
The prior fix registered IInvocationDataBufferingFeature under its own key in the feature collection, alongside IInvocationDataFeature. Since Features.Set<T> keys strictly by typeof(T), those became two independent slots pointing at the same instance. Middleware replacing IInvocationDataFeature (Features.Set<IInvocationDataFeature>(...)) would leave the buffering slot pointing at the stale instance, so EnableEventBuffering() would silently buffer the wrong stream while the actually-active one still gets consumed once by deserialization - the exact starvation bug this feature exists to prevent. Drop the separate registration. EnableEventBuffering() now resolves whatever IInvocationDataFeature is currently active and probes it for the capability, so there's no second slot to fall out of sync. Addresses further review feedback from chatgpt-codex-connector on PR #398. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qn9kagNWsCubTrnzN6NEqV
ncipollina
commented
Sep 4, 2026
Codex Review: Didn't find any major issues. Delightful! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Uh oh!
There was an error while loading. Please reload this page.
Summary
IInvocationDataFeature.EventStreamis read once, lazily, by event deserialization, and isn't guaranteed to be seekable — reading it in middleware would starve deserialization.EnableBuffering()onIInvocationDataFeature(same idea as ASP.NET Core'sHttpRequest.EnableBuffering()): buffers the event stream into a seekableMemoryStreamonly when a caller asks for it, so the cost is paid only by invocations that actually log the raw payload.ILambdaInvocationContext.EnableEventBuffering()as an ergonomic wrapper, matching the existingGetEvent<T>/GetResponse<T>extension pattern.Changes
IInvocationDataFeature.EnableBuffering()— new method; no-ops if the stream is already seekable.InvocationDataFeature.EnableBuffering()— copies the event stream into aMemoryStream, disposes the original, resetsPositionto0.ILambdaInvocationContext.EnableEventBuffering()extension inFeatureLambdaInvocationContextExtensions.Positionafter reading; the response stream can always be swapped since it's serialized to after the middleware pipeline completes.Validation
dotnet build src/MinimalLambda/MinimalLambda.csproj— success, 0 warnings.dotnet test tests/MinimalLambda.UnitTests(net10.0) — 545/545 passed, including new coverage forEnableBuffering(seekable no-op, non-seekable copy, content/position preservation, post-read reset) and the new context extension.dotnet jb cleanupcode(repo formatter) run over the solution.Notes for Reviewers
Existing
EventStreambehavior is unchanged for consumers who never callEnableBuffering()— factory still hands back the original stream as-is, so this is additive.🤖 Generated with Claude Code
https://claude.ai/code/session_01Qn9kagNWsCubTrnzN6NEqV