Skip to content

opencode api and session import drop the --server path prefix #60542

Open

Description

@fabiovincenzi

Description

With a --server URL that has a path prefix, opencode api and opencode session import connect, then send their request without the prefix.

#50428 fixed the connection check, which goes through the generated client, so --server http://host/prefix now connects. But both commands then build the request by hand with new URL("/api/...", serverUrl), and the leading slash drops the prefix.

This was reported in #46498, which was closed as a duplicate of #50427 before the reporter replied. I have a fix with tests and will open a PR.

Plugins

No response

OpenCode version

v2.2.2

Steps to reproduce

  1. opencode serve --port 4096
  2. Reverse proxy mounting it under /proxy/, e.g. nginx location /proxy/ { proxy_pass http://127.0.0.1:4096/; }
  3. opencode api --server http://127.0.0.1:8080/proxy GET /api/session
  4. The request goes to /api/session instead of /proxy/api/session and gets a 404

Screenshot

No response

Operating System

macOS 27.0.1

Terminal

No response

Activity

  1. opencodejr commented on Oct 30, 2026

    @opencodejr
    Contributor

    Thanks for the clear report. I could reproduce this on v2.2.2, and it still happens on the latest dev (92b2ca0).

    Setup: opencode serve --port 4096 (with OPENCODE_PASSWORD) behind a small proxy that serves it under /proxy/, strips the prefix and logs each path it receives. Each command ran with --server http://127.0.0.1:8080/proxy:

    • The connection check and /api/location go through the generated client and keep the prefix (/proxy/api/info, /proxy/api/location).
    • opencode api ... GET /api/session sends GET /api/session and gets HTTP 404 Not Found.
    • opencode api ... session.list (an operation name) sends GET /openapi.json and fails with Failed to load OpenAPI document: HTTP 404. Your report doesn't mention this case, but it's the same problem.
    • opencode session import ... sends POST /api/experimental/session/import and fails with Failed to import session: Not Found.

    This matches your analysis: the new URL("/...", serverUrl) calls in packages/cli/src/commands/handlers/api.ts and session/import.ts drop the base path. The /openapi.json fetch in api.ts needs the same fix as the other two. The script below reproduces it.

    Related: #50427 / #50428 (the same fix for the generated client), #46498 (the earlier report of this problem), #47442 (an earlier PR that also covered these commands).

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Reproduces: `opencode api` / `opencode session import` drop the --server path prefix.
    # Usage: ./repro.sh            (uses `opencode` from PATH)
    #        OC_DEV=/path/to/repo ./repro.sh   (runs CLI from source checkout)
    set -u
    WORK=$(mktemp -d /tmp/opencode/prefix-XXXX)
    cd "$WORK"
    export OPENCODE_PASSWORD=repro-secret
    export XDG_DATA_HOME="$WORK/data" XDG_CONFIG_HOME="$WORK/config" XDG_STATE_HOME="$WORK/state" XDG_CACHE_HOME="$WORK/cache"
    oc() {
      if [ -n "${OC_DEV:-}" ]; then
        bun --conditions=browser --preload="$OC_DEV/packages/tui/node_modules/@opentui/solid/scripts/preload.js" "$OC_DEV/packages/cli/src/index.ts" "$@"
      else
        opencode "$@"
      fi
    }
    echo "version: $(oc --version)"
    
    oc serve --port 4096 >"$WORK/serve.log" 2>&1 &
    SERVE=$!
    
    # Reverse proxy mounting the server under /proxy/ (strips the prefix), like nginx
    # `location /proxy/ { proxy_pass http://127.0.0.1:4096/; }`. Anything else -> 404.
    cat >"$WORK/proxy.ts" <<'EOF'
    Bun.serve({
      port: 8080,
      async fetch(req) {
        const url = new URL(req.url)
        console.log(`proxy: ${req.method} ${url.pathname}`)
        if (!url.pathname.startsWith("/proxy/")) return new Response("not found (no /proxy prefix)", { status: 404 })
        const target = "http://127.0.0.1:4096" + url.pathname.slice("/proxy".length) + url.search
        return fetch(target, { method: req.method, headers: req.headers, body: req.body, redirect: "manual" })
      },
    })
    EOF
    bun "$WORK/proxy.ts" >"$WORK/proxy.log" 2>&1 &
    PROXY=$!
    trap 'kill $SERVE $PROXY 2>/dev/null; pkill -f "serve --port 4096" 2>/dev/null' EXIT
    
    for i in $(seq 1 60); do curl -sf -u opencode:repro-secret http://127.0.0.1:8080/proxy/api/info >/dev/null && break; sleep 1; done
    echo "--- curl through proxy (works):"
    curl -s -u opencode:repro-secret -o /dev/null -w "HTTP %{http_code}\n" http://127.0.0.1:8080/proxy/api/session
    
    echo "--- opencode api --server http://127.0.0.1:8080/proxy GET /api/session"
    oc api --server http://127.0.0.1:8080/proxy GET /api/session; echo "exit $?"
    
    echo "--- opencode api --server http://127.0.0.1:8080/proxy session.list (operation id -> /openapi.json)"
    oc api --server http://127.0.0.1:8080/proxy session.list; echo "exit $?"
    
    echo '{"info":{},"messages":[]}' >"$WORK/session.json"
    echo "--- opencode session import --server http://127.0.0.1:8080/proxy session.json"
    oc session import --server http://127.0.0.1:8080/proxy "$WORK/session.json"; echo "exit $?"
    
    echo "--- paths the proxy received:"
    cat "$WORK/proxy.log"

    Run it: bash repro.sh

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