Skip to content

Conversation

@kvaps
Copy link
Member

@kvaps kvaps commented Jan 16, 2026

What this PR does

Splits telemetry collection between cozystack-operator and cozystack-controller for better separation of concerns.

Release note

[platform] Split telemetry collection between cozystack-operator and cozystack-controller

Summary by CodeRabbit

  • New Features

    • Added telemetry support to the operator with configurable options (disable, endpoint, interval).
    • Enhanced telemetry to track application kinds and UI-managed releases.
  • Bug Fixes

    • Removed the runtime cozystack-version flag; version is now managed at build time.
  • Chores

    • Removed unused YAML dependency.
    • Simplified build process with improved Makefile commands.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 16, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Removes the cozystack-version flag from controller and operator, eliminates CozystackVersion from telemetry config, adds telemetry support to the operator, refactors the collector to aggregate ApplicationDefinitions and HelmReleases data, introduces OperatorCollector, implements version injection via ldflags, and updates build tooling.

Changes

Cohort / File(s) Summary
Version Infrastructure
pkg/version/version.go
New file exposing public Version variable initialized to "dev" for build-time injection via ldflags; enables version configuration at compile time
Controller Version & Telemetry
cmd/cozystack-controller/main.go, packages/system/cozystack-controller/values.yaml, packages/system/cozystack-controller/templates/deployment.yaml
Removed --cozystack-version flag; removed cozystackVersion from values and deployment args; simplified telemetry config initialization
Controller Build Process
packages/system/cozystack-controller/Makefile, packages/system/cozystack-controller/images/cozystack-controller/Dockerfile
Added VERSION build argument with ldflags injection into Go binary; removed update-version target dependency
Operator Telemetry Integration
cmd/cozystack-operator/main.go
Added telemetry flags (disable-telemetry, endpoint, interval); creates and attaches OperatorCollector to manager; parses interval duration with error handling
Operator Build & Configuration
packages/core/installer/Makefile, packages/core/installer/images/cozystack-operator/Dockerfile, packages/core/installer/templates/cozystack-operator.yaml, packages/core/installer/values.yaml
Added VERSION build argument to Dockerfile; removed cozystack-version flag from deployment and values; updated Makefile commands to use abbreviated flags (-f, -i) and removed update-version dependency
Telemetry Core Configuration
internal/telemetry/config.go
Removed CozystackVersion field from Config struct; updated DefaultConfig() initialization
Telemetry Collector Refactoring
internal/telemetry/collector.go
Removed discovery client dependency; shifted collection logic from node/PV/loadbalancer metrics to ApplicationDefinitions and HelmReleases-based metrics; changed to list ApplicationDefinitions for kinds and filter HelmReleases by UILabel; simplified NewCollector signature; consolidated metrics emission with sendMetrics only when non-empty
Operator Telemetry Collector
internal/telemetry/operator_collector.go
New collector for operator; implements periodic cluster telemetry (nodes, PVs, LoadBalancers, Packages); includes leader-election support, size-grouping helper, and metric POSTing with cluster ID headers
Dependencies
go.mod
Removed gopkg.in/yaml.v2 v2.4.0 dependency

Sequence Diagram(s)

sequenceDiagram
    actor Startup
    participant Manager as ControllerManager
    participant TelemetryConfig as Telemetry Config
    participant OperatorCollector as OperatorCollector
    participant Cluster as Kubernetes Cluster
    participant Endpoint as Telemetry Endpoint

    Startup->>Manager: Start manager
    Startup->>TelemetryConfig: Parse telemetry flags
    TelemetryConfig->>OperatorCollector: NewOperatorCollector(config)
    OperatorCollector->>OperatorCollector: Initialize with client & config
    Manager->>OperatorCollector: Attach to manager
    Startup->>OperatorCollector: Start(ctx)
    OperatorCollector->>OperatorCollector: Run initial collect()
    OperatorCollector->>Cluster: List nodes, PVs, services, packages
    Cluster-->>OperatorCollector: Cluster metadata
    OperatorCollector->>OperatorCollector: Aggregate metrics
    OperatorCollector->>Endpoint: POST metrics
    Endpoint-->>OperatorCollector: 200 OK
    OperatorCollector->>OperatorCollector: Wait on ticker
    OperatorCollector->>Cluster: Periodic collect()
    Cluster-->>OperatorCollector: Updated metadata
    OperatorCollector->>Endpoint: POST metrics
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 A hop and a skip, the version takes flight,
Via ldflags we build, the binary shines bright,
Collectors now gather what applications reveal,
HelmReleases and Kinds—the metrics we seal! 📊✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main change: splitting telemetry responsibilities between the operator and controller components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @kvaps, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the telemetry system within Cozystack to provide a more granular and accurate view of both cluster infrastructure and application deployments. By separating telemetry responsibilities between the operator and controller, the system can now collect more relevant data for each component. Additionally, the change streamlines version reporting through build-time injection and updates several metric definitions to better reflect the system's state.

Highlights

  • Telemetry Split: Telemetry collection has been split between cozystack-operator (for cluster metrics) and cozystack-controller (for application metrics), allowing for more focused data gathering.
  • Build-Time Version Injection: The Cozystack version is now injected at build time using ldflags, replacing the previous runtime flag approach for improved consistency and reliability.
  • Metric Refinements: The bundle/oidc telemetry has been replaced with a package_info metric, and cozy_workloads_count has been updated to cozy_application_count for clearer application-centric reporting.
  • New Operator Telemetry Collector: A new OperatorCollector has been introduced to specifically handle cluster-level metrics such as node counts, cluster capacity, load balancers, persistent volumes, and installed packages.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively refactors the telemetry collection by splitting responsibilities between the operator and the controller, which improves modularity. The change to inject the version at build time using ldflags is a good improvement over runtime flags. My review includes a high-severity suggestion to address significant code duplication between the new telemetry collectors, which would improve long-term maintainability. I've also included medium-severity comments to encourage the use of structured logging for better observability. Overall, this is a great set of changes that improves the telemetry architecture.

Comment on lines +46 to +67
func (c *OperatorCollector) Start(ctx context.Context) error {
if c.config.Disabled {
return nil
}

c.ticker = time.NewTicker(c.config.Interval)
c.stopCh = make(chan struct{})

// Initial collection
c.collect(ctx)

for {
select {
case <-ctx.Done():
c.ticker.Stop()
close(c.stopCh)
return nil
case <-c.ticker.C:
c.collect(ctx)
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There is significant code duplication between OperatorCollector in this file and Collector in internal/telemetry/collector.go. The Start, NeedLeaderElection, and sendMetrics methods are identical, and the struct definitions are very similar. This makes the code harder to maintain.

I recommend refactoring to use a common baseCollector that can be embedded by both OperatorCollector and Collector to hold the shared logic. This would centralize the common functionality, making it easier to manage.

// Get cluster ID from kube-system namespace
var kubeSystemNS corev1.Namespace
if err := c.client.Get(ctx, types.NamespacedName{Name: "kube-system"}, &kubeSystemNS); err != nil {
logger.Info(fmt.Sprintf("Failed to get kube-system namespace: %v", err))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Throughout this file and internal/telemetry/collector.go, log messages are constructed using fmt.Sprintf. It is a best practice to use structured logging with key-value pairs for variable data. This makes logs more machine-readable and easier to query.
For example, this line could be improved.

Suggested change
logger.Info(fmt.Sprintf("Failed to get kube-system namespace: %v", err))
logger.Info("Failed to get kube-system namespace", "error", err)

@kvaps kvaps force-pushed the refactor/telemetry-split branch from dbefee7 to e185bfe Compare January 17, 2026 10:05
@kvaps kvaps changed the title refactor(telemetry): split telemetry between operator and controller [platform] Split telemetry between operator and controller Jan 17, 2026
@kvaps
Copy link
Member Author

kvaps commented Jan 19, 2026

/retest

1 similar comment
@kvaps
Copy link
Member Author

kvaps commented Jan 19, 2026

/retest

Split telemetry collection between cozystack-operator and cozystack-controller:

cozystack-operator now collects cluster-level metrics:
- cozy_cluster_info (cozystack_version, kubernetes_version)
- cozy_nodes_count (os, kernel)
- cozy_cluster_capacity (cpu, memory, nvidia.com/* resources)
- cozy_loadbalancers_count
- cozy_pvs_count (driver, size)
- cozy_package_info (name, variant)

cozystack-controller now collects application-level metrics:
- cozy_application_count (kind) - counts HelmReleases per ApplicationDefinition

Other changes:
- Add pkg/version for build-time version injection via ldflags
- Remove --cozystack-version flag (version now embedded at build time)
- Remove bundle/oidc configuration from telemetry (replaced by package_info)
- Remove cozy_workloads_count metric (replaced by cozy_application_count)

Co-Authored-By: Claude <[email protected]>
Signed-off-by: Andrei Kvapil <[email protected]>
@kvaps kvaps force-pushed the refactor/telemetry-split branch from e185bfe to 987a74a Compare January 19, 2026 12:52
@kvaps kvaps marked this pull request as ready for review January 19, 2026 13:27
@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. enhancement New feature or request labels Jan 19, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/telemetry/collector.go (1)

138-158: HTTP request lacks timeout, risking indefinite blocking.

Using http.DefaultClient without a timeout can cause the goroutine to hang indefinitely if the telemetry endpoint is unresponsive. This could affect the collector's ticker-based scheduling.

Consider using http.NewRequestWithContext with a timeout context, or create a dedicated http.Client with a configured Timeout.

🛠️ Suggested fix using context with timeout
 // sendMetrics sends collected metrics to the configured endpoint
-func (c *Collector) sendMetrics(clusterID, metrics string) error {
-	req, err := http.NewRequest("POST", c.config.Endpoint, bytes.NewBufferString(metrics))
+func (c *Collector) sendMetrics(ctx context.Context, clusterID, metrics string) error {
+	ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
+	defer cancel()
+
+	req, err := http.NewRequestWithContext(ctx, "POST", c.config.Endpoint, bytes.NewBufferString(metrics))
 	if err != nil {
 		return fmt.Errorf("failed to create request: %w", err)
 	}

Then update the call site at line 132:

-	if err := c.sendMetrics(clusterID, metrics.String()); err != nil {
+	if err := c.sendMetrics(ctx, clusterID, metrics.String()); err != nil {
cmd/cozystack-controller/main.go (1)

246-251: Duplicate error logging on the same failure path.

Lines 248 and 249 both log an error for the same failure case. setupLog.Error will already log the error, and then setupLog.V(1).Error logs it again at a different verbosity level. This appears to be unintentional.

🛠️ Suggested fix
 	if collector != nil {
 		if err := mgr.Add(collector); err != nil {
-			setupLog.Error(err, "unable to set up telemetry collector")
 			setupLog.V(1).Error(err, "unable to set up telemetry collector, continuing without telemetry")
 		}
 	}

Or use setupLog.Info with the error as a field if you want it at V(1) level:

 	if collector != nil {
 		if err := mgr.Add(collector); err != nil {
-			setupLog.Error(err, "unable to set up telemetry collector")
-			setupLog.V(1).Error(err, "unable to set up telemetry collector, continuing without telemetry")
+			setupLog.V(1).Info("unable to set up telemetry collector, continuing without telemetry", "error", err)
 		}
 	}
🤖 Fix all issues with AI agents
In `@internal/telemetry/collector.go`:
- Around line 22-27: Replace the duplicated ApplicationKindLabel constant with
the canonical constant from the API types package: remove the local
ApplicationKindLabel declaration in the telemetry collector and add an import
for the API package (the package that defines ApplicationKindLabel, e.g.,
appsv1alpha1) then reference appsv1alpha1.ApplicationKindLabel wherever the
local constant was used; keep the local UILabel constant as-is and update any
existing references to use the imported symbol to avoid divergence.

In `@internal/telemetry/operator_collector.go`:
- Around line 261-282: In sendMetrics, avoid using http.DefaultClient without a
timeout by creating a context with a deadline (e.g., via context.WithTimeout)
and use http.NewRequestWithContext to build the POST to c.config.Endpoint, then
perform the request with an http.Client that enforces the timeout (or rely on
the context cancel) instead of http.DefaultClient.Do; ensure you call the cancel
function and preserve existing headers (Content-Type and X-Cluster-ID) and error
handling in OperatorCollector.sendMetrics.

In `@packages/core/installer/Makefile`:
- Line 18: The Makefile currently defines targets like image, pre-checks,
image-operator, and image-packages as real targets which can be skipped if files
with those names exist; add a .PHONY declaration listing these targets (e.g.
.PHONY: image pre-checks image-operator image-packages) so Make always executes
them regardless of same-named files, placing the .PHONY line near the top of the
Makefile where other target metadata is defined.

In `@packages/system/cozystack-controller/Makefile`:
- Line 7: Makefile declares the target "image" (and also a "helper" target) but
doesn't mark them .PHONY, so if a file named "image" exists Make will treat the
target as up-to-date; add a .PHONY declaration for these targets by adding a
line like ".PHONY: image helper" near the top of the Makefile so Make always
runs the "image" and "helper" targets (reference the existing "image:
image-cozystack-controller" target to locate where to add the .PHONY).
♻️ Duplicate comments (2)
internal/telemetry/operator_collector.go (2)

23-30: Significant code duplication with Collector type.

The OperatorCollector duplicates substantial logic from Collector in collector.go:

  • Start method (lines 46-67) is nearly identical
  • sendMetrics method (lines 262-282) is identical
  • Struct fields (ticker, stopCh, config) are duplicated

Consider extracting common functionality into a shared baseCollector type that both collectors can embed.


109-114: Use structured logging with key-value pairs.

Throughout this file, log messages use fmt.Sprintf for variable data. Structured logging with key-value pairs improves machine readability and query-ability.

Example fix
-	logger.Info(fmt.Sprintf("Failed to get kube-system namespace: %v", err))
+	logger.Info("Failed to get kube-system namespace", "error", err)
🧹 Nitpick comments (1)
internal/telemetry/collector.go (1)

30-35: stopCh field is unused.

The stopCh channel is created (line 52) and closed (line 61) but is never read from in any select statement. This appears to be dead code, possibly left over from a previous implementation.

Consider removing stopCh from the struct and the related make/close calls if it's not needed.

Comment on lines +22 to +27
const (
// ApplicationKindLabel is the label used to identify application kind on HelmReleases
ApplicationKindLabel = "apps.cozystack.io/application.kind"
// UILabel is the label used to identify UI-managed HelmReleases
UILabel = "cozystack.io/ui"
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find all definitions of ApplicationKindLabel
rg -n "ApplicationKindLabel\s*=" --type=go

Repository: cozystack/cozystack

Length of output: 355


🏁 Script executed:

# Check the collector.go file to see if it imports ApplicationKindLabel or defines it
head -30 internal/telemetry/collector.go | cat -n

Repository: cozystack/cozystack

Length of output: 1000


🏁 Script executed:

# Check what's in rest.go around line 75
sed -n '70,80p' pkg/registry/apps/application/rest.go | cat -n

Repository: cozystack/cozystack

Length of output: 472


🏁 Script executed:

# Search for where appsv1alpha1.ApplicationKindLabel is defined (could be in types)
rg -n "ApplicationKindLabel\s*=" --type=go pkg/apis/

Repository: cozystack/cozystack

Length of output: 159


Centralize ApplicationKindLabel by importing from the API types package.

ApplicationKindLabel is defined in three places: pkg/apis/apps/v1alpha1/types.go (source), pkg/registry/apps/application/rest.go:76 (correctly imports from appsv1alpha1), and here in internal/telemetry/collector.go:24 (duplicates the string literal). Import from the source of truth instead to prevent inconsistencies if the constant is updated.

🤖 Prompt for AI Agents
In `@internal/telemetry/collector.go` around lines 22 - 27, Replace the duplicated
ApplicationKindLabel constant with the canonical constant from the API types
package: remove the local ApplicationKindLabel declaration in the telemetry
collector and add an import for the API package (the package that defines
ApplicationKindLabel, e.g., appsv1alpha1) then reference
appsv1alpha1.ApplicationKindLabel wherever the local constant was used; keep the
local UILabel constant as-is and update any existing references to use the
imported symbol to avoid divergence.

Comment on lines +261 to +282
// sendMetrics sends collected metrics to the configured endpoint
func (c *OperatorCollector) sendMetrics(clusterID, metrics string) error {
req, err := http.NewRequest("POST", c.config.Endpoint, bytes.NewBufferString(metrics))
if err != nil {
return fmt.Errorf("failed to create request: %w", err)
}

req.Header.Set("Content-Type", "text/plain")
req.Header.Set("X-Cluster-ID", clusterID)

resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}

return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

HTTP request lacks timeout.

Same issue as in collector.go: using http.DefaultClient without a timeout can cause indefinite blocking. Apply the same fix using http.NewRequestWithContext with a timeout.

🤖 Prompt for AI Agents
In `@internal/telemetry/operator_collector.go` around lines 261 - 282, In
sendMetrics, avoid using http.DefaultClient without a timeout by creating a
context with a deadline (e.g., via context.WithTimeout) and use
http.NewRequestWithContext to build the POST to c.config.Endpoint, then perform
the request with an http.Client that enforces the timeout (or rely on the
context cancel) instead of http.DefaultClient.Do; ensure you call the cancel
function and preserve existing headers (Content-Type and X-Cluster-ID) and error
handling in OperatorCollector.sendMetrics.

cozyhr show --namespace $(NAMESPACE) $(NAME) --plain | kubectl diff --filename -
cozyhr show --namespace $(NAMESPACE) $(NAME) --plain | kubectl diff -f -

image: pre-checks image-operator image-packages
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Declare image (and related) targets as PHONY.

This avoids Make skipping the target if a file named image exists. Line 18.

🔧 Proposed fix
+.PHONY: pre-checks show apply diff image image-operator image-packages
 image: pre-checks image-operator image-packages
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
image: pre-checks image-operator image-packages
.PHONY: pre-checks show apply diff image image-operator image-packages
image: pre-checks image-operator image-packages
🧰 Tools
🪛 checkmake (0.2.2)

[warning] 18-18: Target "image" should be declared PHONY.

(phonydeclared)

🤖 Prompt for AI Agents
In `@packages/core/installer/Makefile` at line 18, The Makefile currently defines
targets like image, pre-checks, image-operator, and image-packages as real
targets which can be skipped if files with those names exist; add a .PHONY
declaration listing these targets (e.g. .PHONY: image pre-checks image-operator
image-packages) so Make always executes them regardless of same-named files,
placing the .PHONY line near the top of the Makefile where other target metadata
is defined.

include ../../../hack/package.mk

image: image-cozystack-controller update-version
image: image-cozystack-controller
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Declare image (and helper) targets as PHONY.

If a file named image exists, Make will skip this target. Add a .PHONY declaration to avoid that. Line 7.

🔧 Proposed fix
+.PHONY: image image-cozystack-controller
 image: image-cozystack-controller
🧰 Tools
🪛 checkmake (0.2.2)

[warning] 7-7: Target "image" should be declared PHONY.

(phonydeclared)

🤖 Prompt for AI Agents
In `@packages/system/cozystack-controller/Makefile` at line 7, Makefile declares
the target "image" (and also a "helper" target) but doesn't mark them .PHONY, so
if a file named "image" exists Make will treat the target as up-to-date; add a
.PHONY declaration for these targets by adding a line like ".PHONY: image
helper" near the top of the Makefile so Make always runs the "image" and
"helper" targets (reference the existing "image: image-cozystack-controller"
target to locate where to add the .PHONY).

@kvaps
Copy link
Member Author

kvaps commented Jan 19, 2026

/retest

@kvaps kvaps merged commit 57a0276 into main Jan 20, 2026
43 of 45 checks passed
@kvaps kvaps deleted the refactor/telemetry-split branch January 20, 2026 00:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants