Skip to content

web: fresh client shows no projects after opencode serve in a project folder #55222

Open

Description

@cshintov

Description

Running opencode serve in a project folder and opening the web UI in a fresh browser shows no projects ("Nothing here yet"). The folder the server was started in is not listed, so there is nothing to open.

The server knows the folder: GET /api/location returns it. The sidebar reads the per-server project list kept in browser storage, and on a fresh client nothing fills that list.

This is the V2 version of #37096, which was closed with the V1 issues.

OpenCode version

2.0.28 (dev source)

Steps to reproduce

  1. git init demo && cd demo
  2. opencode serve --port 4096
  3. Open the printed URL in a fresh browser profile (empty local storage).
  4. The sidebar shows no projects. curl -u opencode:<password> http://127.0.0.1:4096/api/location returns the demo folder as directory.

Operating System

Linux (Debian container, arm64), headless Chromium

Terminal

n/a

Activity

  1. opencode-agent commented on Oct 13, 2026

    @opencode-agent
    Contributor

    Thanks for the clear report. I could reproduce this.

    Steps:

    1. Installed @opencode/[email protected], ran git init demo, then opencode serve inside it.
    2. Opened the printed URL in a fresh headless Chromium.
    3. Connected with "Use password" and the password the server printed.

    The home page shows only "Add project" and "Nothing here yet — Create a session to get started", even though GET /api/location returns the demo folder as directory. The same thing happens on the latest dev (e9def88a7) run from source. The screenshots below show the empty home on both versions, and the script below reproduces it.

    Related:

    Screenshot

    Screenshot

    Reproduction script (repro.mjs)
    // Reproduces #55222: fresh browser shows no projects after `opencode serve` in a project folder.
    // Usage: node repro.mjs            (uses `opencode` from PATH)
    //        OC_DEV=/workspace/repo node repro.mjs   (runs the CLI from a source checkout)
    // Requires: npm i playwright && npx playwright install --with-deps chromium
    import { spawn, execSync } from "node:child_process"
    import { mkdtempSync } from "node:fs"
    import { tmpdir } from "node:os"
    import { join } from "node:path"
    import { chromium } from "playwright"
    
    const port = 4096 + Math.floor(Math.random() * 500)
    const root = mkdtempSync(join(tmpdir(), "oc55222-"))
    const demo = join(root, "demo")
    execSync(`git init -q ${demo}`)
    const env = { ...process.env, HOME: join(root, "home"), XDG_DATA_HOME: join(root, "data"), XDG_CONFIG_HOME: join(root, "config"), XDG_STATE_HOME: join(root, "state"), XDG_CACHE_HOME: join(root, "cache") }
    const dev = process.env.OC_DEV
    const [cmd, args] = dev
      ? ["bun", ["--conditions=browser", `--preload=${dev}/packages/tui/node_modules/@opentui/solid/scripts/preload.js`, `${dev}/packages/cli/src/index.ts`, "serve", "--port", String(port)]]
      : ["opencode", ["serve", "--port", String(port)]]
    const srv = spawn(cmd, args, { cwd: demo, env })
    let out = ""
    srv.stdout.on("data", (d) => (out += d))
    srv.stderr.on("data", (d) => (out += d))
    
    let url
    for (let i = 0; i < 120 && !url; i++) {
      await new Promise((r) => setTimeout(r, 500))
      url = out.match(/https?:\/\/\S+/)?.[0]
    }
    console.log("server output:\n" + out)
    if (!url) throw new Error("no URL printed")
    const u = new URL(url)
    const pw = out.match(/server password (\S+)/)?.[1]
    if (pw && !u.username) { u.username = "opencode"; u.password = pw }
    const auth = u.username ? "Basic " + Buffer.from(`${decodeURIComponent(u.username)}:${decodeURIComponent(u.password)}`).toString("base64") : undefined
    const base = `${u.protocol}//${u.host}`
    const loc = await fetch(`${base}/api/location`, { headers: auth ? { authorization: auth } : {} }).then((r) => r.text())
    console.log("GET /api/location ->", loc)
    
    const browser = await chromium.launch()
    const ctx = await browser.newContext({ httpCredentials: u.username ? { username: decodeURIComponent(u.username), password: decodeURIComponent(u.password) } : undefined, viewport: { width: 1400, height: 900 } })
    const page = await ctx.newPage()
    await page.goto(url)
    await page.waitForTimeout(3000)
    // Fresh client: connect through the "Use password" form
    if (await page.getByText("Use password").count()) {
      await page.getByText("Use password").click()
      await page.waitForTimeout(1000)
      console.log("--- password form ---\n" + (await page.evaluate(() => document.body.innerText)))
      const inputs = page.locator("input")
      console.log("inputs:", await inputs.evaluateAll((els) => els.map((e) => [e.type, e.name, e.placeholder, e.value])))
      {
        const pwInput = page.locator('input[type="password"]')
        await pwInput.fill(decodeURIComponent(u.password))
        await pwInput.press("Enter")
      }
    }
    await page.waitForTimeout(8000)
    const shot = process.env.SHOT ?? "/workspace/repro/fresh-client.png"
    await page.screenshot({ path: shot })
    const text = await page.evaluate(() => document.body.innerText)
    console.log("--- page text ---\n" + text.slice(0, 1500))
    console.log("--- result ---")
    console.log("page mentions 'demo':", /\bdemo\b/.test(text))
    console.log("page shows 'Nothing here yet':", text.includes("Nothing here yet"))
    await browser.close()
    srv.kill()
    process.exit(0)

    Run it: bun repro.mjs

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