Skip to content

v2: --continue reopens a session whose tab was closed; picks by time.updated with no closed/archived filter #52655

Open

Description

@DEAD1nsane

Description

opencode --continue (-c) reopens sessions whose tabs the user has closed. Closing a session's tab is a client-side UI action only — the session row and its time.updated are untouched — so the closed session remains the most recently updated root session and -c reopens it on every launch. The only reliable way to retire it is opencode session delete.

Sending prompts in other tabs does not fix this when those sessions are out of scope: the candidate list is project/directory-scoped (sessionListQuery() in packages/tui/src/context/sync.tsx filters by worktree-relative path), and the pick is purely sort by time.updated desc, first session with parentID === undefined (packages/tui/src/app.tsx; same logic in packages/opencode/src/cli/cmd/run.ts via session.list().find(item => !item.parentID)). Additionally, time.updated is written by non-prompt operations (setSummary, setMetadata, setPermission, setWorkspace in packages/opencode/src/session/session.ts), so a background summary on the old session can out-rank a prompt sent elsewhere. Neither the TUI nor the CLI path filters archived sessions — Session.list() → listByProject() has no time_archived filter, unlike listGlobal() which excludes archived by default.

This is the same root cause as #22175 (closed not_planned) and overlaps #35058 (archive does not close tabs) and #38852 (stale session after restart, labeled 2.0).

Steps to reproduce

  1. Open opencode, work in session A, close its tab (Ctrl+X W).
  2. Start session B in another tab, send prompts there.
  3. Exit and run opencode -c.
  4. Session A reopens instead of B; repeats on every -c launch.

Expected Behavior

-c should resume the session the user was last actively using (last prompted in, or last viewed), and should skip sessions the user closed — or at minimum, closing a tab should be persisted so -c can honor it. Archived sessions should be excluded, consistent with listGlobal().

Actual Behavior

-c reopens the most recently written-to root session regardless of whether the user closed it, prompted elsewhere, or archived it. Closing a tab has no effect on which session -c picks.

OpenCode version

v2 (behavior present on dev branch, code inspected 2026-10-01)

Operating System

Termux on Android (Linux aarch64)

Terminal

Termux

Activity

  1. github-actions commented on Oct 2, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of or overlap with existing issues. Please check:

    For keybind-related issues, please also check our pinned keybinds documentation: #4997

  2. opencodejr commented on Oct 28, 2026

    @opencodejr

    Thanks for the detailed report. I reproduced this on v2.2.0 and on the latest dev (891a020), run from source.

    Steps (the script below reproduces it with a simulated model):

    1. Prompt in session A in the TUI.
    2. /new, then prompt in session B, and exit.
    3. Archive session A.
    4. Run opencode --continue.

    Archived session A opens instead of B; the recording below shows it. Archiving also updates the session's time.updated, so the archived session becomes the newest one, and --continue doesn't exclude archived sessions when it picks.

    Making --continue skip sessions whose tab you closed is a separate request. Tab state is TUI-only today, so that part is more of a feature request than this bug.

    Related: #58364 (archived sessions in the Ctrl+O picker), #22175 (the same --continue/archived problem, closed as stale), #57381 (import overwrites time.updated).

    recording.mp4

    Screenshot

    Screenshot

    Reproduction script (drive.ts)
    // Repro for anomalyco/opencode#52655: `opencode --continue` reopens an archived
    // session instead of the session last worked in.
    // It writes a small wrapper (oc.sh) that opencode-drive runs instead of `opencode`:
    // on the second TUI launch it archives session A through the API and then starts
    // the TUI with --continue. OC_DEV=/path/to/checkout runs the CLI from source.
    import { Effect } from "effect"
    import { Llm, OpenCodeDriver } from "opencode-drive"
    
    const wrapper = "/tmp/opencode/oc.sh"
    await Bun.write(wrapper, `#!/usr/bin/env bash
    # Wrapper around \`opencode\` used by drive.ts. When /tmp/opencode/repro-archive
    # holds a session ID and this is a TUI launch (\`--server URL\`), archive that
    # session through the API and then start the TUI with \`--continue\`.
    if [ -n "$OC_DEV" ]; then
      OC=(bun --conditions=browser --preload="$OC_DEV/packages/tui/node_modules/@opentui/solid/scripts/preload.js" "$OC_DEV/packages/cli/src/index.ts")
    else
      OC=(opencode)
    fi
    MARK=/tmp/opencode/repro-archive
    if [ "$1" = "--server" ] && [ -s "$MARK" ]; then
      ID=$(cat "$MARK"); rm -f "$MARK"
      "\${OC[@]}" api --server "$2" session.update --param sessionID="$ID" -d '{"archived":true}' >>/tmp/opencode/repro-api.log 2>&1
      exec "\${OC[@]}" "$@" --continue
    fi
    exec "\${OC[@]}" "$@"
    `)
    await Bun.$`chmod +x ${wrapper}`
    
    export default OpenCodeDriver.use(
      {
        opencode: { command: [wrapper], env: process.env.OC_DEV ? { OC_DEV: process.env.OC_DEV } : {} },
        tui: { recording: true },
      },
      (oc) =>
        Effect.gen(function* () {
          const { ui, llm } = oc
          yield* llm.queue(Llm.text("Reply in session A."))
          // Session A
          yield* ui.submit("work in session A")
          yield* ui.waitFor("work in session A", { timeout: 20000 }).pipe(Effect.andThen(Effect.sleep("3 seconds")))
          yield* Effect.sleep("1 second")
          // Session B, the one the user is actively using
          yield* llm.queue(Llm.text("ok"))
          yield* llm.queue(Llm.text("Reply in session B."))
          yield* llm.queue(Llm.text("Reply in session B."))
          yield* ui.type("/new")
          yield* Effect.sleep("500 millis")
          yield* ui.enter()
          yield* Effect.sleep("1 second")
          yield* Effect.log(yield* ui.screenshot("after-new"))
          yield* ui.submit("work in session B")
          yield* Effect.log("waiting B")
          yield* ui.waitFor("work in session B", { timeout: 20000 }).pipe(Effect.andThen(Effect.sleep("3 seconds")))
          yield* Effect.log(yield* ui.screenshot("in-session-B"))
          yield* Effect.sleep("1 second")
          const list = yield* oc.opencode.session.list({ order: "desc", parentID: null } as any)
          const sessions = (list as any).data as Array<{ id: string; title: string }>
          yield* Effect.log(sessions.map((s) => `${s.id} ${s.title}`).join("\n"))
          const a = sessions[sessions.length - 1]!
          yield* Effect.promise(() => Bun.write("/tmp/opencode/repro-archive", a.id))
          yield* ui.screenshot("before-exit-in-session-B")
          yield* oc.tui.close()
          // Relaunch: wrapper archives session A, then runs `opencode --server … --continue`
          const second = yield* oc.tuis.launch("continue", { recording: true })
          yield* Effect.sleep("4 seconds")
          yield* second.ui.screenshot("after-continue")
          const after = yield* oc.opencode.session.get({ sessionID: a.id } as any)
          yield* Effect.log(`session A after archive: ${JSON.stringify((after as any).time)}`)
          yield* Effect.log("waiting continue")
          yield* second.ui.waitFor("work in session A", { timeout: 10000 })
          yield* Effect.log("BUG: --continue reopened archived session A instead of session B")
        }),
    )

    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.

  3. DEAD1nsane commented on Oct 28, 2026

    @DEAD1nsane
    Author

    Thanks for reproducing this!

    I reproduced this on v2.2.0 and on the latest dev

    Appreciate you taking the time to confirm the bug. The root cause makes sense - archiving updates time.updated, so the archived session becomes the "newest" and --continue picks it up.

    The workaround in the meantime is to be careful about when I archive sessions. For a proper fix, I understand this would need to track tab state separately from session timestamps - more of a feature request than a quick fix.

    Thanks again for looking into this!

  4. added a commit that references this issue on Nov 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions