Skip to content

SSE auto-reconnect for axons; AbortSignal for poll-based waits#765

Merged
alb-rl merged 8 commits into
mainfrom
cursor/sse-routes-abortsignal-bf99
Apr 2, 2026
Merged

SSE auto-reconnect for axons; AbortSignal for poll-based waits#765
alb-rl merged 8 commits into
mainfrom
cursor/sse-routes-abortsignal-bf99

Conversation

@alb-rl

@alb-rl alb-rl commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

User description

Summary

  • Axon SSE uses withStreamAutoReconnect; after_sequence is internal (resume from last sequence on 408).
  • StreamBackedAPIPromise + deferred first fetch preserve asResponse / withResponse.
  • poll() supports signal with balanced abort listeners; wired through scenario/blueprint/snapshot waits.

Testing

  • Unit: tests/polling.test.ts (signal + delays + timeout race + listener balance + lastResult)
  • Unit: tests/lib/streaming-reconnection.test.ts (empty stream, getOffset undefined path, asResponse/withResponse, parallel asResponse+await, StreamBackedAPIPromise lazy getData)
  • Unit: tests/resources/axons-subscribe-sse.test.ts (first GET has no after_sequence; reconnect sends after_sequence; custom headers)
  • Smoketests: tests/smoketests/polling-streaming-flow.test.ts (RUN_SMOKETESTS=1) — awaitScored + signal, runs.awaitScored + signal, axon SSE, execution streamStdoutUpdates

Run unit: jest tests/polling.test.ts tests/lib/streaming-reconnection.test.ts tests/resources/axons-subscribe-sse.test.ts
Run smoke: yarn test:smoke (or RUN_SMOKETESTS=1 jest tests/smoketests/polling-streaming-flow.test.ts)

Slack Thread

Open in Web Open in Cursor 

CodeAnt-AI Description

Add abort support to long polls and keep reconnecting streams compatible with response helpers

What Changed

  • Long-running waits now stop when an AbortSignal is cancelled, including the delay between polling attempts and any in-flight wait.
  • Blueprint, scenario scoring, and snapshot waits now accept the same cancel signal so users can end a wait early.
  • Axon event streams now reconnect after idle timeouts using the last event sequence, while still supporting raw response access helpers.
  • SSE streams no longer start the network request until they are actually awaited, which avoids unwanted eager connections.

Impact

✅ Cancel long waits on demand
✅ Fewer stuck blueprint and scenario waits
✅ Fewer dropped axon event streams after idle timeouts

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

cursoragent and others added 2 commits April 1, 2026 21:10
Wrap SSE auto-reconnect in StreamBackedAPIPromise so asResponse/withResponse
still work. Add withStreamAutoReconnect to axon subscribe (after_sequence)
mirroring execution stdout/stderr streams.

Co-authored-by: albert <alb-rl@users.noreply.github.com>
Extend poll() with optional signal and wire it through scenario run
scoring waits, blueprint build waits, and disk snapshot completion.
ScenarioRun.awaitEnvReady forwards signal to devbox awaitRunning.

Co-authored-by: albert <alb-rl@users.noreply.github.com>
@github-actions

github-actions Bot commented Apr 1, 2026

Copy link
Copy Markdown

✅ Object Smoke Tests & Coverage Report

Test Results

✅ All smoke tests passed

Coverage Results

Metric Coverage Required Status
Functions 100% 100%
Lines 90.03% - ℹ️
Branches 68.95% - ℹ️
Statements 88.98% - ℹ️

Coverage Requirement: 100% function coverage (all public methods must be called in smoke tests)

✅ All tests passed and all object methods are covered!

View detailed coverage report
File Functions Lines Branches
src/sdk.ts ✅ 100% 86.11% 72.58%
src/sdk/agent.ts ✅ 100% 100% 100%
src/sdk/axon.ts ✅ 100% 93.75% 100%
src/sdk/blueprint.ts ✅ 100% 100% 80%
src/sdk/devbox.ts ✅ 100% 91.96% 94.28%
src/sdk/execution-result.ts ✅ 100% 92.68% 70.83%
src/sdk/execution.ts ✅ 100% 95.65% 87.5%
src/sdk/gateway-config.ts ✅ 100% 100% 100%
src/sdk/mcp-config.ts ✅ 100% 100% 100%
src/sdk/network-policy.ts ✅ 100% 100% 100%
src/sdk/scenario-builder.ts ✅ 100% 98.46% 80.7%
src/sdk/scenario-run.ts ✅ 100% 92.1% 50%
src/sdk/scenario.ts ✅ 100% 100% 100%
src/sdk/scorer.ts ✅ 100% 100% 100%
src/sdk/secret.ts ✅ 100% 100% 100%
src/sdk/snapshot.ts ✅ 100% 100% 100%
src/sdk/storage-object.ts ✅ 100% 80% 48.93%

📋 View workflow run

cursoragent and others added 2 commits April 1, 2026 21:34
- Lazy-start the first stream request in withStreamAutoReconnect so
  APIPromise construction does not hit the network (fixes worker crashes).
- ScenarioRun.awaitEnvReady passes undefined when no options; only forwards
  longPoll/polling/signal when set.
- Axon.subscribeSse with no args calls axons.subscribeSse(id, undefined).

Co-authored-by: albert <alb-rl@users.noreply.github.com>
raceAbort no longer uses Promise.race with a dangling abort listener.
It attaches onAbort, awaits the wrapped promise via .then, and removes
the listener on both success and failure (and onAbort removes itself
when firing first).

Co-authored-by: albert <alb-rl@users.noreply.github.com>
@alb-rl
alb-rl marked this pull request as ready for review April 1, 2026 22:00
@alb-rl
alb-rl requested a review from dines-rl April 1, 2026 22:00
Comment thread src/resources/axons.ts Outdated
query?: AxonSubscribeSseParams,
options?: Core.RequestOptions,
): APIPromise<Stream<AxonEventView>>;
subscribeSse(id: string, options?: Core.RequestOptions): APIPromise<Stream<AxonEventView>>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

wtf is this

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.

overloading functions in typescript

Comment thread src/resources/axons.ts Outdated
export async function withStreamAutoReconnect<Item>(
streamCreator: (offset: number | undefined) => Promise<Stream<Item>>,
export function withStreamAutoReconnect<Item>(
streamCreator: (offset: number | undefined) => APIPromise<Stream<Item>>,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Wtf is an API promise

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.

it's part of stainless's core primitives, it's our SDK’s return type for most HTTP calls

let dataPromiseMemo: Promise<Stream<Item>> | undefined;
const getDataPromise = () => {
if (!dataPromiseMemo) {
dataPromiseMemo = (async () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Memo? This all looks a little to complicated, did the OG reconnector not work?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why are we creating a new one?

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.

memo = single-flight for the first request and for the constructed reconnecting stream promise, so all APIPromise entry points stay consistent and idempotent

Comment thread src/lib/polling.ts
Drop public AxonSubscribeSseParams; resume offset is only supplied by
withStreamAutoReconnect via getStream(afterSequence). subscribeSse is
now (id, options?) like execution stdout/stderr streams.

Co-authored-by: albert <alb-rl@users.noreply.github.com>
@codeant-ai

codeant-ai Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@codeant-ai codeant-ai Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Apr 1, 2026
@dines-rl
dines-rl self-requested a review April 1, 2026 23:19
Comment thread src/resources/axons.ts Outdated
Comment thread src/resources/axons/axons.ts Outdated
Comment thread src/resources/blueprints.ts
Comment thread src/resources/blueprints.ts Outdated
Comment thread src/resources/devboxes/disk-snapshots.ts
Comment thread src/resources/devboxes/executions.ts
Comment thread src/resources/devboxes/executions.ts
@codeant-ai

codeant-ai Bot commented Apr 1, 2026

Copy link
Copy Markdown
Contributor

CodeAnt AI finished reviewing your PR.

cursoragent and others added 2 commits April 1, 2026 23:27
…tests

- poll(): signal during delays, timeout vs abort, listener balance, lastResult
- withStreamAutoReconnect: empty stream, getOffset undefined, asResponse/
  withResponse, single-flight, StreamBackedAPIPromise lazy getData
- Axons.subscribeSse: after_sequence on reconnect, headers, first GET query
- Smoketests: awaitScored + signal, runs.awaitScored + signal, axon SSE,
  execution stdout stream

Co-authored-by: albert <alb-rl@users.noreply.github.com>
- Axon subscribeSse: merge options.query with after_sequence; spread rest
  options so reconnect cursor is not overwritten by caller query
- Executions streamStdout/Stderr: seed reconnect with parsed query.offset
- Blueprints.awaitScored path: map resolveLongPollTimeoutMs into poll()
- DiskSnapshots.awaitCompleted and Runs.awaitScored: same timeout mapping
- createAndAwaitBuildCompleted: pass signal through to create()

Tests: axon query+reconnect merge; execution initial offset + reconnect

Co-authored-by: albert <alb-rl@users.noreply.github.com>
@alb-rl
alb-rl requested a review from dines-rl April 1, 2026 23:42

await expect(
run.awaitScored({
polling: { pollingIntervalMs: 500, maxAttempts: 500, timeoutMs: 30 * 60 * 1000 },

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is this configuration? I don't think we need any of these right?

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 the default polling config, it's been here for a while

}),
).rejects.toThrow(LongPollAbortError);

try {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Need a test for reeconnect that it reconnects at the right position IE the output starts from where it left off, need maybe 5 events in the axon and then have it disconnect and reconnect showing that it works.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We need some way of showing that this whole reconnect thing works and resumes from the last received SSE event

Publish 5 tagged events, subscribe, wait for server 408 idle, publish 2 more,
assert strictly increasing sequence and markers 1–7 (no duplicates).

Co-authored-by: albert <alb-rl@users.noreply.github.com>
@alb-rl
alb-rl requested a review from dines-rl April 2, 2026 00:33
@alb-rl
alb-rl merged commit 4835860 into main Apr 2, 2026
9 of 11 checks passed
@alb-rl
alb-rl deleted the cursor/sse-routes-abortsignal-bf99 branch April 2, 2026 00:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants