Skip to content

tui: model switch races session creation and gets a Session not found toast #57354

Open

Description

@Bearmancer

Description

The TUI asks the server to switch a session's model before the server has stored that session, so the server answers 404 SessionNotFoundError and the TUI shows a red toast:

Failed to prepare session
Failed to switch model: Session not found: ses_ea7beb81effeGcyHs8y0qQt2Fj

The session does exist — it is the tab the user is working in. It was created a moment earlier and the server had not committed it yet, so the switch is rejected even though the id is correct.

The client already sends its prompts behind the session creation (packages/client/src/solid/data.ts gates session.prompt on the in-flight create). The model switch taken from the composer selection is not gated that way, so it can leave first.

From the server log, the switch leaves 152 ms before the session is created (time.created = 07:40:36.919Z):

timestamp=2026-10-20T07:40:36.767Z message="Sent HTTP response" http.method=POST http.url=/api/session/ses_ea7beb81effeGcyHs8y0qQt2Fj/model http.status=404

Plugins

None.

OpenCode version

2.1.0

Steps to reproduce

  1. From the home route, send a prompt so a new session is created and opened in a tab.
  2. Before the create request has been answered, press Enter again in the session tab with an empty composer. That commits the composer selection, which switches the session's model.
  3. The toast above appears.

A session the server does not know yet answers the switch with 404:

POST /api/session/ses_ea7beb81effeGcyHs8y0qQt2Fj/model
HTTP/1.1 404 Not Found
{"_tag":"SessionNotFoundError","sessionID":"ses_ea7beb81effeGcyHs8y0qQt2Fj","message":"Session not found: ses_ea7beb81effeGcyHs8y0qQt2Fj"}

Screenshot

The toast is a red box in the top right of the TUI, title Failed to prepare session and the message above.

Operating System

Windows 11

Terminal

Windows Terminal

Linked PR #57361

Activity

  1. opencode-agent commented on Oct 20, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report! I reproduced this on 2.1.0, and it still happens on the latest dev (d30e990).

    Steps:

    1. From the home route, send a prompt. This creates a new session and opens it in a tab.
    2. About 150 ms later, while the create request is still in flight, press Enter with the composer empty in that tab.
    3. The red toast Failed to prepare session / Failed to switch model: Session not found: ses_… appears.

    On Linux the create is normally too fast to hit this. To widen the window, the script below adds random network delay between the TUI and the server, so the model switch can reach the server before the create. The race only shows some of the time, so the script may need a few runs. The recording and screenshots below show the toast.

    As you pointed out, prompts already wait for the in-flight create (sendAdmission in packages/client/src/solid/data.ts). The composer's agent/model commit in packages/tui/src/component/prompt/index.tsx calls switchAgent/switchModel directly, without waiting.

    recording.mp4

    Screenshot

    Screenshot

    Reproduction script (drive.ts)
    // Reproduces anomalyco/opencode#57354: an empty Enter in a freshly created
    // session tab sends POST /api/session/:id/model before the session create has
    // been committed, and the TUI shows "Failed to switch model: Session not found".
    import { Effect } from "effect"
    import { Llm, OpenCodeDriver } from "opencode-drive"
    // The report takes as given that the server stores the new session a little
    // after the TUI fires the model switch (152 ms in the author's log on
    // Windows). Locally both are near-instant, so the TUI<->server link is routed
    // through opencode-drive's chaos proxy with per-chunk jitter: requests on
    // different connections can then reach the server in either order. The race
    // is probabilistic: the toast appeared in 1 of 1 runs on 2.1.0 and 1 of 8 on
    // dev, so re-run a few times if waitFor times out.
    
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        network: true,
        tui: { recording: true, keypressOverlay: true },
      },
      ({ ui, llm, network }) =>
        Effect.gen(function* () {
          yield* llm.queue(Llm.text("Hello from the simulated model."))
          yield* ui.screenshot("01-home")
          yield* Effect.sleep(3000) // let the TUI finish loading over the proxy
          yield* network.set({ jitterMs: 1500 })
          // Send a prompt from home: this creates a new session and opens it.
          yield* ui.submit("Say hello")
          // Immediately press Enter with an empty composer in the new session,
          // which commits the composer's model selection (switchModel).
          yield* Effect.sleep(150)
          yield* ui.screenshot("02a-session-tab")
          yield* ui.enter()
          yield* Effect.sleep(1500)
          yield* ui.screenshot("02-after-empty-enter")
          yield* ui.waitFor("Session not found", { timeout: 8000 })
          yield* ui.screenshot("03-toast")
          yield* Effect.sleep(1500)
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

  2. Bearmancer commented on Oct 20, 2026

    @Bearmancer
    Author

    This was generated by AI during triage.

    Fix PR: #57361

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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