Skip to content

Auto update should never auto upgrade from v1 to v2 #54066

Closed

Description

@jeffhung

Description

OpenCode v1 and v2 are incompatible. For example, session database are different. Plugin API are different. Thus, upgrading from v1 to v2 should always a human decision. Even when auto update is enabled, auto update should never automatically upgrade from v1 to v2.

Otherwise, plugins would fail, human tasks would be interrupted. This also make session database not possible to downgrade if human try to reinstall v1 to resume working.

Plugins

No response

OpenCode version

1.18.30 (installed via homebrew)

Steps to reproduce

No response

Screenshot and/or share link

No response

Operating System

macOS 26.6.2

Terminal

Ghostty within Tmux

Activity

  1. opencode-agent commented on Oct 9, 2026

    @opencode-agent
    Contributor

    Thanks for raising this, @jeffhung. Moving from v1 to v2 is a big change, and we agree it shouldn't happen without the user deciding to.

    For context: the current v1 auto-updater installs only patch releases by itself. For a minor or major release (including 1.x → 2.x), it only shows an "update available" notice. Its npm path also updates the v1 package opencode-ai, not v2's @opencode/cli. So we'd like to find out whether this actually happened to you, and if so, how. Could you share:

    1. The v1 version you were running before (opencode --version, if you still know it) and the v2 version you ended up on.
    2. How OpenCode was installed (npm/bun/pnpm, Homebrew, the curl install script, Scoop, Chocolatey, desktop app, …).
    3. Your autoupdate setting, if you set one.
    4. What you saw: did OpenCode upgrade itself while running, or did the new version show up after something else, such as brew upgrade, opencode upgrade or reinstalling?
    5. Your operating system.

    If this is a general suggestion rather than something that happened to you, please say so too.

    You can reply here with the details, or edit the issue. If you edit it, please also leave a comment saying you've updated it, so we know you're done editing.

    I didn't find an existing report of the same thing. These issues are related (ordered by how closely they relate):

  2. jeffhung commented on Oct 9, 2026

    @jeffhung
    Author

    I have edited the issue to supply more fields.

    I also asked immediately: "Why you upgrade opencode to v2 without my agreement?"
    Below is the results:

    I investigated this thoroughly. Here's what actually happened.
    It wasn't me — OpenCode upgraded itself
    I found the exact event in OpenCode's own log (~/.local/share/opencode/log/opencode.log):
    2026-10-09T05:40:52Z message=upgraded method=brew target=2.0.25
    opencode 1.18.30 -> 2.0.20
    The timeline today (13:40 local time):
    1. You launched a new OpenCode session in ai-fearlessly running 1.18.30 (V1).
    2. Nine seconds later, OpenCode's built-in auto-update kicked in — it detected a newer release and ran brew upgrade opencode by itself, taking you from 1.18.30 → 2.0.20.
    3. Homebrew then cleaned up the old version (Removing: /opt/homebrew/Cellar/opencode/1.18.30).
    Why this crossed the 1.x → 2.x line
    A few things combined:
    - You installed OpenCode via Homebrew (installed_on_request: True in the install receipt), and OpenCode knows how to self-upgrade through Homebrew (method=brew).
    - Auto-update was enabled — it wasn't disabled in your config. Your ~/.config/opencode/opencode.json only sets plugin and instructions; there's no auto-update opt-out.
    - The Homebrew formula now points at the 2.x line, so "newest version" meant "V2". A 1.x → 2.x jump is what a major release does; the updater doesn't hold back major versions.
    I also checked your shell history — there's no brew upgrade or opencode upgrade in it. This wasn't a manual command, and no agent session ran one either. The log line is emitted by OpenCode's own upgrade routine (run=87e976ba, the server process), not by a spawned shell command.
    
  3. opencode-agent commented on Oct 9, 2026

    @opencode-agent
    Contributor

    Thanks for the details and the log line, @jeffhung. They made this reproducible.

    I reproduced it with 1.18.30 set up as a Homebrew install (on Linux, with a stand-in brew and the real Homebrew formula data, which now reports opencode at 2.0.25):

    1. A few seconds after the TUI starts, an "Update Available — A new release v2.0.25 is available. Would you like to update now?" dialog opens, with Confirm already selected.
    2. Typing a first prompt and pressing Enter confirms that dialog instead. OpenCode shows "Updating to v2.0.25" and runs brew upgrade opencode. Running opencode upgrade directly logs upgraded method=brew target=2.0.25, the same target as in your log.

    So the background auto-update did not install 2.x on its own; it only installs patch releases by itself. The upgrade comes from this dialog: it defaults to Confirm and doesn't warn that 2.x is a separate, incompatible line, so an Enter meant for your prompt can start it. The current v1 code has the same dialog. It mainly affects Homebrew installs, because the Homebrew formula now points to 2.x, while npm and the GitHub releases still point to 1.18.35.

    The script below reproduces it. It drives the v1 TUI in a pseudo-terminal, so there is no video. The maintainers will look at it.

    Until then, to stay on v1 you can set "autoupdate": "notify" or false in your config, choose Skip when the dialog appears, and pin the formula with brew pin opencode so brew upgrade leaves it alone.

    Reproduction script (repro.py)
    #!/usr/bin/env python3
    """Reproduce #54066: opencode v1 (1.18.30) installed via Homebrew upgrades to 2.x
    with a single Enter keypress shortly after startup.
    
    Setup (done before running):
      npm pack opencode-linux-x64@1.18.30 && tar xzf opencode-linux-x64-1.18.30.tgz  -> ./v1/package/bin/opencode
      ./fakebin/brew : fake Homebrew that reports the core formula "opencode" as installed and logs calls.
    The real formulae.brew.sh API is queried for "latest" (currently 2.0.25).
    
    The script starts the v1 TUI in a pseudo-terminal, waits a few seconds (as a user
    would before typing), types a prompt and presses Enter, then checks the fake brew log.
    """
    import os, pty, select, time, re, shutil, sys
    
    ROOT = os.path.dirname(os.path.abspath(__file__))
    BIN = os.path.join(ROOT, "v1/package/bin/opencode")
    HOME = "/tmp/oc-v1-home"
    LOG = os.path.join(ROOT, "fake-brew.log")
    shutil.rmtree(HOME, ignore_errors=True)
    os.makedirs(HOME + "/project", exist_ok=True)
    if os.path.exists(LOG):
        os.remove(LOG)
    
    env = {
        "HOME": HOME,
        "PATH": os.path.join(ROOT, "fakebin") + ":/usr/bin:/bin",  # no npm/bun: only brew detected
        "TERM": "xterm-256color",
        "FAKE_BREW_LOG": LOG,
        **({"OPENCODE_ALWAYS_NOTIFY_UPDATE": "1"} if os.environ.get("ALWAYS") else {}),
        "COLUMNS": "120", "LINES": "40",
        **{k: v for k, v in os.environ.items() if k in ("SSL_CERT_DIR", "SSL_CERT_FILE", "NODE_EXTRA_CA_CERTS", "FAKE_BREW_DELAY")},
    }
    
    pid, fd = pty.fork()
    if pid == 0:
        os.chdir(HOME + "/project")
        os.execve(BIN, [BIN] + sys.argv[1:], env)
    
    buf = b""
    def pump(sec):
        global buf
        end = time.time() + sec
        while time.time() < end:
            r, _, _ = select.select([fd], [], [], 0.2)
            if r:
                try:
                    d = os.read(fd, 65536)
                except OSError:
                    return
                buf += d
                # answer cursor position / terminal queries minimally
                if b"\x1b[6n" in d:
                    os.write(fd, b"\x1b[1;1R")
    
    def text():
        return re.sub(rb"\x1b\[[0-9;?<>=]*[ -/]*[@-~]|\x1b\][^\x07\x1b]*(\x07|\x1b\\)|\x1b.", b"", buf).decode("utf8", "ignore")
    
    pump(int(os.environ.get("WAIT","30")))
    t = text()
    print("Update dialog shown:", "Update Available" in t)
    m = re.search(r"A new release v[0-9.]+ is available[^\n]*", t)
    print("Dialog text:", m.group(0) if m else None)
    
    # User starts typing their first prompt and presses Enter
    for ch in "hello":
        os.write(fd, ch.encode()); time.sleep(0.05)
    os.write(fd, b"\r")
    pump(8)
    t = text()
    print("Updating toast:", re.search(r"Updating to v[0-9.]+", t).group(0) if re.search(r"Updating to v[0-9.]+", t) else None)
    print("Update complete:", re.search(r"Successfully updated to OpenCode v[0-9.]+", t).group(0) if re.search(r"Successfully updated to OpenCode v[0-9.]+", t) else None)
    print("--- fake brew log ---")
    print(open(LOG).read() if os.path.exists(LOG) else "(none)")
    open(os.path.join(ROOT,"screen.txt"),"w").write(text())
    os.kill(pid, 9)
    ok = os.path.exists(LOG) and "brew upgrade opencode" in open(LOG).read()
    print("REPRODUCED" if ok else "NOT REPRODUCED")
    sys.exit(0 if ok else 1)

    Run it: python3 repro.py

  4. jeffhung commented on Oct 9, 2026

    @jeffhung
    Author

    Yes. I was entering my prompt. The dialog flashed and it must thought it received confirmation from human, though I was just typing my prompt. I even have no chance to see what the dialog say and it vanished.

  5. neriousy commented on Oct 19, 2026

    @neriousy
    Member

    This is fixed in the v1.19.1 updater: background checks only notify, and updates happen only when you explicitly run /update or opencode upgrade. Moving from v1 to v2 requires opencode upgrade --major, rather than an automatic startup confirmation dialog. If you choose to move to v2, you can also install it with npm i -g @opencode/cli@latest. If you still see this on v2, please open a new issue with the details.

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