Skip to content

feat(generator): opt out of WebApplicationBuilder.Build() interception - #16

Merged
ANcpLua merged 1 commit into
mainfrom
feat/optout-aspnetcore-build-interceptor
Jun 30, 2026
Merged

feat(generator): opt out of WebApplicationBuilder.Build() interception#16
ANcpLua merged 1 commit into
mainfrom
feat/optout-aspnetcore-build-interceptor

Conversation

@ANcpLua

@ANcpLua ANcpLua commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Why

C# allows only one interceptor per call site (CS9153). When a consumer runs the qyl auto-instrumentation generator and another generator that also intercepts WebApplicationBuilder.Build() — e.g. qyl's ServiceDefaultsSourceGenerator, which composes our QylInterceptedAspNetCore.Build — the two collide and the build fails. (This is currently breaking qyl main.)

What

A consumer can hand Build() interception to the cooperating generator while keeping all other qyl interceptors:

<QylAutoInstrumentationInterceptWebApplicationBuilderBuild>false</QylAutoInstrumentationInterceptWebApplicationBuilderBuild>
  • The generator reads build_property.QylAutoInstrumentationInterceptWebApplicationBuilderBuild via AnalyzerConfigOptions. Only an explicit false yields the call site; absent or any other value keeps full instrumentation, so default generated output is byte-identical (existing snapshot unchanged).
  • The core package build/ + buildTransitive/ targets expose it as a CompilerVisibleProperty.
  • New tools/verify-build-interceptor-optout.py (wired into the verify goal suite) proves the opt-out is surgical: default emits the Build interceptor + a control HttpClient interceptor; opt-out drops only Build, keeps HttpClient, and still builds.

Verification (local)

python3 tools/verify-generator-snapshots.py     -> generator-snapshots-ok   (default byte-identical)
python3 tools/verify-build-interceptor-optout.py -> build-interceptor-optout-ok

Follow-up

After merge: release 3.1.1 (trusted publishing). Then qyl pins 3.1.1, sets the property in qyl.collector, and its ServiceDefaults Build interceptor composes QylInterceptedAspNetCore.Build.

🤖 Generated with Claude Code

Add a consumer-settable build property so a cooperating generator can own the
WebApplicationBuilder.Build() call site:

    <QylAutoInstrumentationInterceptWebApplicationBuilderBuild>false</...>

C# allows only one interceptor per call site (CS9153). When a consumer also runs
another generator that intercepts builder.Build() — e.g. qyl's ServiceDefaults
generator, which composes our QylInterceptedAspNetCore.Build — the two collide.
This opt-out lets the consumer hand Build() to that generator while keeping every
other qyl auto-instrumentation interceptor.

- Generator reads build_property.QylAutoInstrumentationInterceptWebApplicationBuilderBuild
  via AnalyzerConfigOptions; only an explicit "false" yields the call site. Absent or
  any other value keeps full instrumentation, so default output is byte-identical.
- Core package build/buildTransitive targets expose it as a CompilerVisibleProperty.
- New tools/verify-build-interceptor-optout.py proves it is surgical (default emits the
  Build interceptor + a control HttpClient interceptor; opt-out drops only Build, keeps
  HttpClient, still builds) and is wired into the verify goal suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 61d06e31-a376-4417-9e6f-448a9daa75c3

📥 Commits

Reviewing files that changed from the base of the PR and between 8858081 and 9337efe.

📒 Files selected for processing (5)
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs
  • src/Qyl.OpenTelemetry.AutoInstrumentation/build/Qyl.OpenTelemetry.AutoInstrumentation.targets
  • src/Qyl.OpenTelemetry.AutoInstrumentation/buildTransitive/Qyl.OpenTelemetry.AutoInstrumentation.targets
  • tools/verify-aot-autoinstrumentation-goal.py
  • tools/verify-build-interceptor-optout.py
📜 Recent review details
⏰ Context from checks skipped due to timeout. (2)
  • GitHub Check: verify (qyl-linux)
  • GitHub Check: webapi-aot-demo (qyl-macos)
🧰 Additional context used
📓 Path-based instructions (2)
**

⚙️ CodeRabbit configuration file

AGENTS.md

**: # Qyl.OpenTelemetry.AutoInstrumentation agent rules

Mission

This repository is the runtime AOT auto-instrumentation lane for qyl, evolving into a
self-describing observability substrate. The foundation is unchanged: .NET 10
NativeAOT-compatible zero-code instrumentation through managed build assets, source generation,
DiagnosticListener consumption, and module-initializer boot. The direction is the North Star
below.

Keep this repository separate from:

  • semantic-convention package generation (Qyl.OpenTelemetry.SemanticConventions is a referenced
    vocabulary package, not generated here),
  • the old CLR-profiler/OpenTelemetry auto-instrumentation substrate.

North Star — declare and prove the whole stack

Every observability tool today is pull-by-observation: a backend learns what a service emits
by receiving samples over time, and never knows whether it has seen the whole surface. qyl has a
capability none of them have — because instrumentation is source-generated interceptors + a static
contract + a referenced semconv registry + (incrementally) DTO inference, the complete set of
telemetry a binary can ever produce is a compile-time-derivable fact, with provenance.

The substrate goal: every qyl binary ships a complete, machine-readable Telemetry Capability
Graph (TCG)
— the full possible OpenTelemetry surface for that exact binary, each capability
tagged compile-time-owned vs runtime-valued — and proves it by self-hosting (instrumenting its
own pipeline with its own mechanism, zero extra code). Any external entity consumes the TCG to know
the entire stack before a span is sampled. The contract becomes the shared semantic graph; an OTLP
backend is just one consumer.

Three pillars:

  1. Self-host (the proof). qyl instruments qyl with qyl — QylSelfTelemetry /
    SemConvConformanceProcessor are the seed; the binary observing itself is how "declared TCG ==
    runtime reality" is checked.
  2. **Compile-time-complete ...

Files:

  • src/Qyl.OpenTelemetry.AutoInstrumentation/build/Qyl.OpenTelemetry.AutoInstrumentation.targets
  • tools/verify-aot-autoinstrumentation-goal.py
  • src/Qyl.OpenTelemetry.AutoInstrumentation/buildTransitive/Qyl.OpenTelemetry.AutoInstrumentation.targets
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs
  • tools/verify-build-interceptor-optout.py

⚙️ CodeRabbit configuration file

**: Operating principles (solo-dev, agentic SDLC — reviews are advisory, agents act on them):

  1. LAZY: one self-contained, correct review beats ten partial ones. Every finding is
    definitive — concrete evidence with file:line, a concrete fix, no "consider maybe",
    no open or ambiguous questions back to the author. If you cannot decide a point
    from the diff plus repo context, stay silent on it. Never cite a source, API, or
    version you have not verified; an unverifiable claim is a dropped claim.
  2. IMPATIENT: never stall a PR. There are no compatibility obligations here — internal
    and dogfooding code has NO public-API contract; removing shims, breaking signatures,
    and deleting dead paths are normal, desirable changes. Do not flag backward
    compatibility, deprecation ceremony, or migration paths. (SemVer applies only to
    commercially sold libraries — this repo has none.)
  3. EGO: hold the bar of the best reviewer on the market — flag real correctness,
    security, data-loss, and structural problems precisely; produce zero noise.

Files:

  • src/Qyl.OpenTelemetry.AutoInstrumentation/build/Qyl.OpenTelemetry.AutoInstrumentation.targets
  • tools/verify-aot-autoinstrumentation-goal.py
  • src/Qyl.OpenTelemetry.AutoInstrumentation/buildTransitive/Qyl.OpenTelemetry.AutoInstrumentation.targets
  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs
  • tools/verify-build-interceptor-optout.py
src/**/*.cs

⚙️ CodeRabbit configuration file

src/**/*.cs: Zero-code instrumentation runtime: this code runs inside EVERY request of host
applications. Top priorities, in order: (1) allocations and boxing on hot paths —
flag closures, LINQ, params arrays, string concat in listener/semantic-tag code;
(2) tag cardinality — any attribute value that is unbounded (raw URLs, user input,
exception messages) explodes at scale; (3) Activity/Meter lifecycle — undisposed
listeners, leaked subscriptions, double-Start/Stop; (4) thread safety of shared
listener state. PublicAPI.Shipped/Unshipped.txt are analyzer-managed: edits must
come from the analyzer flow, and API breaks are fine (internal product, no
compatibility contract).

Files:

  • src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs
🔇 Additional comments (5)
src/Qyl.OpenTelemetry.AutoInstrumentation/build/Qyl.OpenTelemetry.AutoInstrumentation.targets (1)

10-16: LGTM!

src/Qyl.OpenTelemetry.AutoInstrumentation/buildTransitive/Qyl.OpenTelemetry.AutoInstrumentation.targets (1)

10-16: LGTM!

src/Qyl.OpenTelemetry.AutoInstrumentation.SourceGenerators/QylAutoInstrumentationGenerator.cs (1)

8-8: LGTM!

Also applies to: 84-105, 220-233

tools/verify-build-interceptor-optout.py (1)

1-125: LGTM!

tools/verify-aot-autoinstrumentation-goal.py (1)

26-26: LGTM!


📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added an opt-out setting for one specific web app build interception, giving consumers more control over generated instrumentation.
    • Expanded build verification to cover the new opt-out behavior.
  • Bug Fixes

    • Generated interceptors now respect the opt-out setting and skip only the targeted build interception while keeping other interceptors active.

Walkthrough

Adds a consumer-controlled opt-out for WebApplicationBuilder.Build() interceptor generation. MSBuild targets in both build and buildTransitive expose QylAutoInstrumentationInterceptWebApplicationBuilderBuild as a compiler-visible property. The source generator reads this via AnalyzerConfigOptions and filters out the affected interceptor kind. A new Python script validates the default and opt-out build outputs.

Changes

Build interceptor opt-out

Layer / File(s) Summary
MSBuild compiler-visible property
src/.../build/Qyl.OpenTelemetry.AutoInstrumentation.targets, src/.../buildTransitive/Qyl.OpenTelemetry.AutoInstrumentation.targets
Both targets conditionally register QylAutoInstrumentationInterceptWebApplicationBuilderBuild as a CompilerVisibleProperty, guarded by _QylAutoInstrumentationCoreBuildAssetsAlreadyImported.
Generator flag wiring and filtering
src/.../QylAutoInstrumentationGenerator.cs
Adds using Microsoft.CodeAnalysis.Diagnostics, reads build_property.QylAutoInstrumentationInterceptWebApplicationBuilderBuild in Initialize, combines the flag with the invocation pipeline, and filters AspNetCoreWebApplicationBuilderBuild targets in EmitInterceptors when the flag is false.
Verification script and gate registration
tools/verify-build-interceptor-optout.py, tools/verify-aot-autoinstrumentation-goal.py
New script creates a temporary consumer project, builds with and without the opt-out property, and asserts the correct interceptor tokens are present or absent; registered in the gate command list.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: opting out of WebApplicationBuilder.Build() interception.
Description check ✅ Passed The description directly matches the changeset and explains the opt-out property, generator behavior, and verification script.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/optout-aspnetcore-build-interceptor
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/optout-aspnetcore-build-interceptor

Comment @coderabbitai help to get the list of available commands.

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