What went wrong
When adrian.init() is called from synchronous startup code,
asyncio.get_running_loop() raises and loop becomes None, so the SDK
skips _ws_client.schedule_connect(loop).
The code logs that the WebSocket will connect later "on first send from within
an async context", but there is no code path that actually starts the initial
connection from _send_frame(). _send_frame() only buffers frames while
disconnected and returns.
As a result, events are silently buffered forever and the backend never
receives them unless some other code explicitly starts the WebSocket
connection.
Reproduction steps
- Call
adrian.init(...) from normal synchronous startup code, before any
event loop is running.
- Later enter async code with
asyncio.run(...) or equivalent.
- Emit an Adrian event so the SDK attempts to send a frame.
- Observe that the frame is buffered, but no WebSocket connection task is
created.
Minimal local reproduction:
import asyncio
import adrian
from adrian.proto import event_pb2 as pb
adrian.init(
api_key="test",
ws_url="ws://127.0.0.1:9999/ws",
auto_instrument=False,
)
ws = adrian._ws_client
print("after init:", ws._connect_task, ws._connected.is_set(),
len(ws._replay_buffer))
async def main():
frame = pb.ClientFrame()
ev = frame.paired_batch.events.add()
ev.event_id = "evt-1"
ev.invocation_id = "inv-1"
ev.session_id = "sess-1"
ev.pair_type = pb.PAIR_TYPE_TOOL
ev.tool.tool_name = "demo"
await ws._send_frame(frame)
print("after send:", ws._connect_task, ws._connected.is_set(),
len(ws._replay_buffer))
asyncio.run(main())
Expected behaviour
If init() runs before an event loop exists, the SDK should still establish the
initial WebSocket connection once it later reaches an async context and tries
to send its first frame.
At minimum, the first async send path should trigger the initial connect
attempt instead of buffering forever.
Actual behaviour
The initial connection is never started.
Observed local output:
after init: None False 0
after send: None False 1
This shows:
- no connect task after init()
- no connect task after first async send
- socket still disconnected
- event buffered in memory
Environment
- Adrian version / commit:
d16881a5807c08ee23a69e74e9c334d62c5973bc
- OS: macOS 26.3.1 (Build 25D771280a)
- Docker version: Docker version 29.4.3, build 055a478
- GPU model (if relevant): N/A
Logs
Click to expand
Relevant code path:
sdk/adrian/__init__.py
- init() catches RuntimeError from asyncio.get_running_loop()
- if loop is None, it skips _ws_client.schedule_connect(loop)
- it logs: "No running event loop at init(); WebSocket will connect on first send from within an async context."
sdk/adrian/ws.py
- _send_frame() buffers when disconnected and returns
- it does not start connect()
- the only connect triggers found are:
- schedule_connect()
- _handle_disconnect() after a prior connection has already existed
Local reproduction output:
after init: connect_task= None connected= False buffer= 0
after async send: connect_task= None connected= False buffer= 1
Relevant code paths
sdk/adrian/__init__.py
sdk/adrian/ws.py
Likely fix
Ensure that the first async send path can trigger the initial connection when
init() was called before an event loop existed.
For example, if _send_frame() detects that:
- no active connection exists
- no connect task exists
it could schedule the initial connect attempt before buffering or replaying
frames.
This would make the runtime behaviour match the log message that the SDK will
connect on first send from within an async context.
Offer to contribute
I’d be happy to work on a fix for this issue.
What went wrong
When
adrian.init()is called from synchronous startup code,asyncio.get_running_loop()raises andloopbecomesNone, so the SDKskips
_ws_client.schedule_connect(loop).The code logs that the WebSocket will connect later "on first send from within
an async context", but there is no code path that actually starts the initial
connection from
_send_frame()._send_frame()only buffers frames whiledisconnected and returns.
As a result, events are silently buffered forever and the backend never
receives them unless some other code explicitly starts the WebSocket
connection.
Reproduction steps
adrian.init(...)from normal synchronous startup code, before anyevent loop is running.
asyncio.run(...)or equivalent.created.
Minimal local reproduction:
Expected behaviour
If init() runs before an event loop exists, the SDK should still establish the
initial WebSocket connection once it later reaches an async context and tries
to send its first frame.
At minimum, the first async send path should trigger the initial connect
attempt instead of buffering forever.
Actual behaviour
The initial connection is never started.
Observed local output:
This shows:
Environment
d16881a5807c08ee23a69e74e9c334d62c5973bcLogs
Click to expand
Relevant code paths
sdk/adrian/__init__.pysdk/adrian/ws.pyLikely fix
Ensure that the first async send path can trigger the initial connection when
init()was called before an event loop existed.For example, if
_send_frame()detects that:it could schedule the initial connect attempt before buffering or replaying
frames.
This would make the runtime behaviour match the log message that the SDK will
connect on first send from within an async context.
Offer to contribute
I’d be happy to work on a fix for this issue.