Skip to content

web: session icon shows no progress indicator while a background shell runs #56660

Open
coderanonymus/opencode
#5

Description

@ysm-dev

Description

In the web app, the session icon (titlebar tab and home list row) turns into the progress indicator while the session or one of its subagents is working. It does not while a background shell is working. When the agent's turn ends, the icon goes back to the project avatar, so the session looks finished with a command still running.

A background subagent shows the indicator because it is a child session with status running. A background shell is not a session. It only lives in data.shell, which useSessionTabAvatarState (packages/app/src/shell/layout/project-avatar-state.ts) never reads, and the server marks the parent idle as soon as its turn ends.

The TUI session list has the same gap (#51810). I found no issue for the web app. Related: #45560, and #53211, which adds a waiting status that this check would still need to count.

I reproduced it in the app's e2e harness against a mock server and traced the cause in the code. I have not run it against a live agent.

Plugins

None

OpenCode version

dev at c38d83c (package version 2.0.32)

Steps to reproduce

  1. In the web app, ask the agent to run a long command in the background, for example sleep 300 with the shell tool's background option.
  2. Let the agent's turn end.
  3. Look at the session's tab, or its row on the home page. It shows the project avatar instead of the progress indicator until the shell exits.

Screenshot and/or share link

Tab B has a running background shell and tab C's subagent has one. Neither tab shows the progress indicator.

Tabs B and C show their project avatars

Operating System

macOS 26.6.2

Terminal

n/a, web app in Chromium

Activity

  1. opencode-agent commented on Oct 18, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I can confirm this on 2.0.32 and on the latest dev (63c1a27).

    What I checked:

    • Server: the simulated model starts sleep 300 with the shell tool's background: true option, and the turn ends. The server then reports the session as idle (session.active() is empty, and the session has an idle timestamp). The shell list still shows the shell as running, with metadata.sessionID pointing at that session. The script below reproduces this.
    • Web app: I used the app's e2e harness with that same data. A tab whose session is running shows the progress indicator. A tab whose session is idle but owns a running background shell shows the project avatar instead, as in the screenshot below.

    This matches your analysis: useSessionTabAvatarState only looks at session status and pending inbox items, never at data.shell. Related:

    Screenshot

    Reproduction script (drive.ts)
    // Reproduces #56660 at the server/data level the web app reads:
    // after a turn that started a background shell ends, the session is no longer
    // reported as active/running, while the shell is still running. The web app's
    // useSessionTabAvatarState only checks session status + pending inbox, so the
    // tab/home icon falls back to the project avatar.
    import { Effect } from "effect"
    import { Llm, OpenCodeDriver } from "opencode-drive"
    
    export default OpenCodeDriver.use(
      { ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}), project: { git: true } },
      ({ ui, llm, opencode }) =>
        Effect.gen(function* () {
          yield* llm.queue(
            Llm.toolCall({
              index: 0,
              id: "call_bg",
              name: "shell",
              input: { command: "sleep 300", description: "Sleep in background", background: true },
            }),
            Llm.finish("tool-calls"),
          )
          yield* llm.queue(Llm.text("Started sleep 300 in the background. Turn over."))
          yield* ui.submit("Run sleep 300 in the background")
          yield* ui.waitFor("Turn over.")
          yield* Effect.sleep(3000)
          const active = yield* Effect.orDie(opencode.session.active())
          const shells = yield* Effect.orDie(opencode.shell.list())
          const sessions = yield* Effect.orDie(opencode.session.list())
          yield* Effect.log("SESSIONS " + JSON.stringify(sessions).slice(0, 600))
          yield* Effect.log("ACTIVE " + JSON.stringify(active))
          yield* Effect.log("SHELLS " + JSON.stringify(shells).slice(0, 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. dluc commented on Nov 8, 2026

    @dluc

    This also affects OpenCode Desktop 2.2.5 and session API consumers, not just the web session icon. In Desktop, I see a session appear stopped while a shell script is still running in the background. API clients that derive their Working/idle indicator from session.active() can also show the session as stopped when it is omitted after the agent turn ends, even though its shell remains active.

    Please consider the impact on session API consumers as well as the built-in UI. If background shell activity is intentionally separate from agent execution status, an accessible way for API clients to distinguish it would help avoid displaying the session as fully stopped.

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