Skip to content

"FleetApi.start()" uses default 30s timeout but "fleet.start" RPC blocks until fleet completes #539

@Rasaboun

Description

@Rasaboun

Summary

session.rpc.fleet.start() is unusable for any non-trivial workload because it inherits the 30s default timeout from JsonRpcClient.request(), but the session.fleet.start RPC is a long-running blocking call that only responds once the fleet finishes and the session goes idle.

The problem

FleetApi.start() is a thin wrapper with no timeout parameter:

# copilot/generated/rpc.py
class FleetApi:
    async def start(self, params: SessionFleetStartParams) -> SessionFleetStartResult:
        params_dict = {k: v for k, v in params.to_dict().items() if v is not None}
        params_dict["sessionId"] = self._session_id
        return SessionFleetStartResult.from_dict(
            await self._client.request("session.fleet.start", params_dict)
        )

It calls self._client.request(...) which defaults to 30s:

# copilot/jsonrpc.py
async def request(self, method, params=None, timeout=30.0):
    ...
    return await asyncio.wait_for(future, timeout=timeout)

Any fleet workload taking more than 30s raises asyncio.TimeoutError.

Observed behavior

I ran 5 tests against github-copilot-sdk==0.1.26rc0 / @github/copilot@0.0.411 to characterize fleet.start:

Test Result
Trivial plan (1 task) fleet.start returned after 47.6s — same time as SESSION_IDLE
No plan at all Returned after 8.3s with started=True (agent had nothing to do)
Typed API, trivial plan Returned after 14.7s — happened to fit within 30s
notify instead of request Fleet did not run at all — must be a request
Precise ordering (2 tasks) FLEET_RETURNED and SESSION_IDLE at exactly the same time (23.04s)

Key findings

  1. fleet.start blocks until the fleet finishes — it returns at the same time as SESSION_IDLE, not when the fleet is "started"
  2. notify does not workfleet.start must be called as a JSON-RPC request
  3. started=True is always returned (even with no plan), so the field doesn't convey meaningful status
  4. The 30s default timeout causes TimeoutError for any real workload — forcing users to bypass the typed API

Current workaround

Bypass FleetApi.start() and call the raw RPC with a longer timeout:

params = SessionFleetStartParams(prompt="implement the plan")
params_dict = {k: v for k, v in params.to_dict().items() if v is not None}
params_dict["sessionId"] = session.session_id

raw_result = await session._client.request(
    "session.fleet.start", params_dict, timeout=600.0
)
result = SessionFleetStartResult.from_dict(raw_result)

Reproduction

import asyncio
from copilot import CopilotClient
from copilot.generated.rpc import Mode, SessionFleetStartParams, SessionModeSetParams

async def main():
    client = CopilotClient()
    await client.start()
    session = await client.create_session(
        {"model": "gpt-5-mini", "streaming": "true", "reasoning_effort": "low"}
    )

    await session.rpc.mode.set(SessionModeSetParams(mode=Mode.PLAN))
    await session.send_and_wait(
        {"prompt": "Create a plan with 4 independent tasks: write factorial.py, palindrome.py, fibonacci.py, bubblesort.py"},
        timeout=120.0,
    )

    await session.rpc.mode.set(SessionModeSetParams(mode=Mode.AUTOPILOT))

    # This will raise asyncio.TimeoutError for any plan that takes >30s
    result = await session.rpc.fleet.start(
        SessionFleetStartParams(prompt="implement the plan")
    )

asyncio.run(main())

Suggested fix

Make fleet.start return immediately with an acknowledgment (started=True) and let the caller wait via SESSION_IDLE events. This would match what the return type name (started) implies.

Environment

  • github-copilot-sdk: 0.1.26rc0
  • @github/copilot CLI: 0.0.411
  • Python: 3.12
  • macOS Darwin 25.2.0

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions