-
Notifications
You must be signed in to change notification settings - Fork 13
Description
Calling disconnect() while the client is in the Authorizing state (i.e., during the startBot() HTTP request) fails with TransportNotInitialized and the connection proceeds anyway.
Root cause
startBotAndConnect() chains startBot().chain { connect(it) }. During startBot(), the transport state is Authorizing but DailyTransport.call (CallClient) is null — it's only created in initDevices(), which runs as part of connect().
When disconnect() is called in this state:
PipecatClient.disconnect()delegates toDailyTransport.disconnect()DailyTransport.disconnect()callswithCall { ... }which returnsRTVIError.TransportNotInitializedbecausecallis nullrelease()is also a no-op (call?.release()— call is null)- When
startBot()HTTP response arrives, it sets state toAuthorizedand the chain callsconnect(), which creates a newCallClient, joins the Daily room, and the bot connects and starts speaking
There is no way to prevent this from the caller side when using startBotAndConnect(), since the chain is internal.
Expected behavior
disconnect() should succeed in any state and prevent any pending connection from proceeding. Specifically:
PipecatClient.disconnect()should handleAuthorizing/Authorizedstates by setting state toDisconnecteddirectly (no transport teardown needed)startBot()withCallback should not overrideDisconnectedwithAuthorizedafter the HTTP response arrivesconnect()should check state before proceeding and reject ifDisconnected
Suggested fix
PipecatClient.kt — disconnect()
fun disconnect(): Future<Unit, RTVIError> = thread.runOnThreadReturningFuture {
when (transport.state()) {
TransportState.Authorizing,
TransportState.Authorized -> {
transport.setState(TransportState.Disconnected)
connection?.ready?.resolveErr(RTVIError.OperationCancelled)
connection = null
resolvedPromiseOk(thread, Unit)
}
TransportState.Disconnected -> resolvedPromiseOk(thread, Unit)
else -> transport.disconnect()
}
}PipecatClient.kt — startBot() withCallback
.withCallback {
// Don't override Disconnected state — disconnect() may have been
// called while the HTTP request was in-flight.
if (transport.state() != TransportState.Disconnected) {
transport.setState(
if (it.ok) TransportState.Authorized else TransportState.Disconnected
)
}
}PipecatClient.kt — connect() early check
fun connect(transportParams: ConnectParams): Future<Unit, RTVIError> =
thread.runOnThreadReturningFuture {
if (transport.state() == TransportState.Disconnected) {
return@runOnThreadReturningFuture resolvedPromiseErr(
thread, RTVIError.OperationCancelled
)
}
// ... existing code
}DailyTransport.kt — disconnect() (defensive)
override fun disconnect(): Future<Unit, RTVIError> = thread.runOnThreadReturningFuture {
val currentClient = call
if (currentClient == null) {
setState(TransportState.Disconnected)
transportContext.onConnectionEnd()
return@runOnThreadReturningFuture resolvedPromiseOk(thread, Unit)
}
// ... existing callClient.leave() code
}Workaround
Use startBot() and connect() separately instead of startBotAndConnect(), with a disconnect check between them:
val connectParams = client.startBot(apiRequest).await()
if (disconnected) {
client.release()
return
}
client.connect(connectParams).await()Environment
ai.pipecat:daily-transport:1.1.0- Android