Skip to content

feat(transport)!: replace the legacy transport API - #1427

Open
giortzisg wants to merge 6 commits into
ref/telemetry-schedulerfrom
feat/envelope-transport
Open

giortzisg wants to merge 6 commits into
ref/telemetry-schedulerfrom
feat/envelope-transport

Conversation

@giortzisg

@giortzisg giortzisg commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Description

Replace the legacy transport path with envelope-based delivery, supporting built-in and custom transports consistently.

Issues

Changelog Entry Instructions

To add a custom changelog entry, uncomment the section above. Supports:

  • Single entry: just write text
  • Multiple entries: use bullet points
  • Nested bullets: indent 4+ spaces

For more details: custom changelog entries

Reminders

Stack created with GitHub Stacks CLI • Give Feedback 💬

@giortzisg
giortzisg added this pull request to stack #1428 September 10, 2026 10:42

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread internal/telemetry/scheduler.go Outdated
@giortzisg
giortzisg force-pushed the feat/envelope-transport branch from 8bda8ce to f78b14b Compare September 11, 2026 07:19
Comment thread client.go Outdated

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread internal/telemetry/scheduler.go

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread transport.go Outdated
@giortzisg
giortzisg force-pushed the feat/envelope-transport branch from 6d3e886 to a91cd13 Compare September 22, 2026 09:32
@giortzisg
giortzisg force-pushed the feat/envelope-transport branch from a91cd13 to d03300a Compare September 22, 2026 09:56
Comment thread client.go Outdated
@giortzisg
giortzisg force-pushed the feat/envelope-transport branch from d03300a to 3e79081 Compare September 23, 2026 12:26

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread _examples/feature-showcase/main.go
Comment thread transport.go Outdated
Comment thread internal/telemetry/scheduler.go Outdated
@giortzisg
giortzisg force-pushed the feat/envelope-transport branch from 3e79081 to adbf97e Compare September 29, 2026 09:30

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread client.go
Comment thread internal/telemetry/scheduler.go Outdated
Comment thread internal/telemetry/scheduler.go
Comment thread transport.go
@giortzisg
giortzisg force-pushed the feat/envelope-transport branch from adbf97e to 8ddd34d Compare September 29, 2026 09:41

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread internal/telemetry/scheduler.go
Comment thread transport.go Outdated
@giortzisg
giortzisg force-pushed the feat/envelope-transport branch from 8ddd34d to adac5f9 Compare September 29, 2026 13:23

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread internal/telemetry/scheduler.go Outdated
Comment thread internal/telemetry/scheduler.go
Comment thread internal/telemetry/scheduler.go Outdated
Comment thread transport.go

@szokeasaurusrex szokeasaurusrex left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just some minor questions

Comment thread internal/sentrytest/fixture.go
Comment thread internal/telemetry/processor.go Outdated
Comment thread sql/integration_test.go
"db.namespace": "appdb",
"server.address": "localhost",
"server.port": 5432,
"server.port": float64(5432),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: This kinda looks wrong; why do we now need to use floats for server ports?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is unfortunately somewhat of a hack. The mock transport encodes to the wire format and we decode back so we don't really know the type. This affects only tests though.

@szokeasaurusrex szokeasaurusrex left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

My clanker identified some issues that actually seem to be somewhat meaningful regressions. I had it create a reproduction here: #1450.

I commented the precise issues inline. Would appreciate if you could take a look and say whether these are expected behavior

Comment thread client.go
return client.telemetryProcessor.FlushWithContext(ctx)
}
return client.Transport.FlushWithContext(ctx)
return client.telemetryProcessor.FlushWithContext(ctx)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

h: Clanker thinks that this change breaks the flushing timeout for the sync transport

Routing the synchronous transport through the processor appears to break the flush deadline for buffered logs/metrics. With one buffered log and a fake HTTP request that stalls for 30 seconds, client.Flush(time.Second) takes 30 seconds and returns true; the equivalent stack-base run returns after one second.

Scheduler.FlushWithContext calls flushBuffers() synchronously before reaching the transport flush. That drain invokes blocking SyncTransport.SendEnvelope, which uses a background context rather than the flush context. Can we make the deadline cover buffer draining/submission as well, not just the final transport flush?

Both versions already return true in this scenario; the newly introduced regression is the caller waiting beyond its deadline.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: Clanker says timing regression is fixed, but that this still can report flush success, when failure should be reported instead:

l: The timing regression is fixed, but a pre-existing return-value issue remains: with only a pending client report and a stalled synchronous request, Flush(time.Second) returns after one second but reports true.

The report send reaches the deadline, then SyncTransport.FlushWithContext unconditionally returns true. Could this path check ctx.Err() before reporting success?

This also occurred at the previously reviewed head; it is not a regression introduced by these fixes.

Comment thread client.go
Comment on lines +422 to +423
client.setupTransport()
client.setupTelemetryProcessor()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

m: Clanker thinks there is a deadlock; it claims to have observed it on a make race-test run also. Not sure how relevant this is in practice though.

Starting the processor for mock/custom transports exposes an existing scheduler shutdown race. Repeatedly creating a client with MockTransport and immediately closing it inside testing/synctest passes on the stack base, but fails on this head with a deadlock: the scheduler is left blocked in sync.Cond.Wait after Close returns.

Scheduler.Stop cancels and broadcasts without holding s.mu. The worker can check cancellation, then miss the broadcast before entering Wait; the periodic notifier also exits on cancellation, so no later notification is guaranteed. Can we synchronize cancellation/notification with the waiter before enabling this path for every client?

This is scheduling-dependent, but the focused 1,000-iteration reproduction has failed repeatedly. The same deadlock also appeared in a full workspace race-test run.

Comment thread internal/telemetry/processor.go Outdated
b.scheduler.recorder.RecordItem(report.ReasonInternalError, item)
return false
}
return b.scheduler.sendItem(convertible)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

m: If I understand correctly, this skips buffering. Clanker thinks this is sensible for the SyncTransport, but it might not be for the AsyncTransport, although this change would apply to both.

Did I get this right, and if so, what would need to be changed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yeah that's true. The problem here is that we want the sync transport to be truly sync for non batchable payloads, but we can't really do this cleanly.

@giortzisg
giortzisg force-pushed the feat/envelope-transport branch from 4fd76f1 to 808380d Compare October 7, 2026 11:07

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread client.go
Comment thread client.go
@giortzisg
giortzisg force-pushed the feat/envelope-transport branch from 808380d to f09b1eb Compare October 7, 2026 11:12
@giortzisg
giortzisg force-pushed the feat/envelope-transport branch from f09b1eb to ee77ba1 Compare October 8, 2026 12:42
Comment thread internal/telemetry/scheduler.go
Comment thread internal/telemetry/processor.go Outdated

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread internal/telemetry/processor.go Outdated
Comment thread client.go
@giortzisg
giortzisg force-pushed the feat/envelope-transport branch 2 times, most recently from 1e9eec8 to 6aca134 Compare October 8, 2026 14:32
Comment thread client.go
Comment thread internal/telemetry/scheduler.go
@giortzisg
giortzisg force-pushed the feat/envelope-transport branch from 5674423 to 84c46c8 Compare October 9, 2026 08:36

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread internal/telemetry/scheduler.go

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 91d5ea9. Configure here.

Comment thread transport.go
Sdk: t.resolveSdkInfo(),
}
envelope := protocol.NewEnvelope(header)
envelope.AddItem(item)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Close drops accepted async envelopes

Medium Severity

The worker now exits as soon as t.ctx is canceled, but SendEnvelope still treats the transport as open until t.done is closed under closeMu. Close cancels t.ctx before it can take that lock, so a concurrent SendEnvelope can enqueue after the worker has already stopped. Those envelopes are accepted with a nil error and then never sent or recorded.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 91d5ea9. Configure here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@giortzisg I think this may be valid

Comment thread client.go
Comment on lines 887 to +892
}
}

if client.telemetryProcessor != nil {
if !client.telemetryProcessor.Add(event) {
debuglog.Println("Event dropped: telemetry buffer full or unavailable")
return nil
}
} else {
client.Transport.SendEvent(event)
if !client.telemetryProcessor.Add(ctx, event) {
debuglog.Println("Event dropped: telemetry buffer full or unavailable")
return nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: The capture() function unconditionally accesses client.telemetryProcessor without a nil check, risking a panic if called directly with a noopClient.
Severity: MEDIUM

Suggested Fix

Add a defensive check inside the capture function before accessing telemetryProcessor. This can be achieved by adding an !client.IsEnabled() check, consistent with the pattern used in captureLog and captureMetric, to prevent the method call when the client is disabled and telemetryProcessor is nil.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: client.go#L887-L892

Potential issue: The `capture()` function at line 890 unconditionally calls
`client.telemetryProcessor.Add(ctx, event)`. A `noopClient`, which is a documented and
expected state, has a `nil` `telemetryProcessor`. While the current public API callers
like `CaptureEvent` prevent this issue with an `IsEnabled()` check, the `capture()`
function itself is not self-defensive. This creates a dependency on caller discipline.
Any future code path that calls `capture()` directly on a `noopClient` without a prior
`IsEnabled()` check will cause a nil pointer dereference panic. This pattern is also
inconsistent with `captureLog()` and `captureMetric()`, which do perform this check
internally.

@szokeasaurusrex szokeasaurusrex left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approving, but there are still some l items 🚀

}

// Add adds a TelemetryItem to the appropriate buffer based on its category.
// Add buffers item, or submits single-item categories.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: typos

Suggested change
// Add buffers item, or submits single-item categories.
// Adds buffered items, or submits single-item categories.

Comment on lines 326 to 373
@@ -303,21 +342,25 @@ func (s *Scheduler) sendItem(item EnvelopeConvertible) {
if err != nil {
debuglog.Printf("error while converting to envelope: %v", err)
s.recorder.RecordItem(report.ReasonInternalError, item)
return
return false
}
s.sendEnvelope(envelope)
return s.sendEnvelope(ctx, envelope, wait)
}

func (s *Scheduler) sendEnvelope(envelope *protocol.Envelope) bool {
func (s *Scheduler) sendEnvelope(ctx context.Context, envelope *protocol.Envelope, wait bool) bool {
if len(envelope.Items) == 0 {
return false
}
s.provider.AttachToEnvelope(envelope)
return s.sendPreparedEnvelope(envelope)
return s.sendPreparedEnvelope(ctx, envelope, wait)
}

func (s *Scheduler) sendPreparedEnvelope(envelope *protocol.Envelope) bool {
if err := s.transport.SendEnvelope(envelope); err != nil {
func (s *Scheduler) sendPreparedEnvelope(ctx context.Context, envelope *protocol.Envelope, wait bool) bool {
err := s.transport.SendEnvelope(ctx, envelope)
if wait && errors.Is(err, ErrQueueFull) && s.transport.FlushWithContext(ctx) {
err = s.transport.SendEnvelope(ctx, envelope)
}
if err != nil {
debuglog.Printf("error sending envelope: %v", err)
reason := report.ReasonSendError
if errors.Is(err, ErrQueueFull) {
@@ -330,26 +373,19 @@ func (s *Scheduler) sendPreparedEnvelope(envelope *protocol.Envelope) bool {
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: Given the similar names of these functions, I think some short documentation comments explaining each could be helpful. Especially, it would be helpful to document the meaning of the wait parameter and the boolean return value, as it is not immediately clear what these are indicating.

Comment thread transport.go
Sdk: t.resolveSdkInfo(),
}
envelope := protocol.NewEnvelope(header)
envelope.AddItem(item)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@giortzisg I think this may be valid

Comment thread client.go
return client.telemetryProcessor.FlushWithContext(ctx)
}
return client.Transport.FlushWithContext(ctx)
return client.telemetryProcessor.FlushWithContext(ctx)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

l: Clanker says timing regression is fixed, but that this still can report flush success, when failure should be reported instead:

l: The timing regression is fixed, but a pre-existing return-value issue remains: with only a pending client report and a stalled synchronous request, Flush(time.Second) returns after one second but reports true.

The report send reaches the deadline, then SyncTransport.FlushWithContext unconditionally returns true. Could this path check ctx.Err() before reporting success?

This also occurred at the previously reviewed head; it is not a regression introduced by these fixes.

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.

2 participants