Uh oh!
There was an error while loading. Please reload this page.
fix(app): create fresh web sessions in server directory - #37607
Conversation
…ects On a fresh `opencode web` start, the "New Session" button in the titlebar silently does nothing and the home page hides its "New Session" buttons. This happens because: 1. `newSessionProject()` in home.tsx relies solely on `projects()` (persisted opened projects), which is empty. 2. `openNewTab()` in titlebar.tsx has a 5-case fallback chain that all fail when no projects are persisted: home selection is empty, layout.projects.list() is empty, and global.ensureServerCtx(conn).projects.list() returns only persisted projects — never server-registered ones. Fix: - home.tsx: add sync data fallback to `newSessionProject` (registered projects from the server API) - titlebar.tsx: add a final fallback that reads server-registered projects from sync.data.project when all other cases fail
jcianci12
left a comment
There was a problem hiding this comment.
Verified: fix resolves the New Session button issue
Bug confirmed
Tested against v1.18.2 and v1.18.4 (smanx/opencode Docker image). On a fresh browser profile with no persisted projects, clicking "New session" button or pressing Ctrl+T silently does nothing — no POST to /api/session, no console errors, no user feedback. The UI shows the tooltip but the button is a no-op.
Root cause confirmed: openNewTab in titlebar.tsx iterates 6 fallback chains but after all fail (empty projects.list() on fresh install), fallback is undefined and the function returns silently without any toast or error.
Fix validated (code review)
The PR's 4-tier fallback chain is sound:
- Selected project → 2. Last project → 3. First opened project → 4. Server startup directory from /path
The final fallback using ctx.sync.data.path.directory + ctx.projects.open() + ctx.projects.touch() is the correct approach — on a fresh install, the server always has a working directory. The home.tsx changes mirror this correctly with newSessionDirectory() and a synthetic LocalProject fallback.
API workaround confirms the backend is fine
POST /api/session with {} body creates sessions successfully even when the button doesn't work. The issue is purely client-side — the button handler just needs this fallback to find a directory.
Recommendation
Approve and merge — this is a papercut that affects every new user. Also affects issue #38411 (same root cause).
jcianci12
commented
Jul 24, 2026
Standalone E2E reproduction (runnable — no fork needed)Copy/paste to run: #!/usr/bin/env python3"""E2E: New Session button click does nothing on fresh install.Bug: Clicking "New session" produces no network request, no session creation.Fix: PR #37607 adds /path endpoint fallback for empty projects list.Usage: pip install playwright && playwright install chromium python script.py # dry-run python script.py --record # screenshots"""importargparse, json, sysfrompathlibimportPathBASE_URL="http://localhost:4096"# or your OpenCode instancedeftest(page, record=False):
print("[1/5] Loading page...")
page.goto(BASE_URL, wait_until="networkidle")
page.wait_for_timeout(3000)
page.get_by_role("button", name="New session").wait_for(timeout=5000)
resp=page.evaluate(
"fetch('/api/session?limit=5&order=desc').then(r => r.json())"
)
initial=len(resp.get("data", []))
posts= []
defcapture(req):
ifreq.method=="POST":
posts.append(req.url)
page.on("request", capture)
print("[2/5] Clicking New Session...")
page.get_by_role("button", name="New session").click()
page.wait_for_timeout(2000)
page.remove_listener("request", capture)
resp=page.evaluate(
"fetch('/api/session?limit=5&order=desc').then(r => r.json())"
)
final=len(resp.get("data", []))
session_posts= [uforuinpostsif"/api/session"inu]
bug=len(session_posts) ==0andfinal==initialifrecord:
page.screenshot(path="after-click.png")
print(f" POST to /api/session: {len(session_posts)}")
print(f" Sessions: {initial} before, {final} after")
print(f" {'BUG: silent no-op'ifbugelse'FIXED: button works!'}")
returnbugdefmain():
fromplaywright.sync_apiimportsync_playwrightp=argparse.ArgumentParser()
p.add_argument("--record", action="store_true")
args=p.parse_args()
withsync_playwright() aspw:
browser=pw.chromium.launch(headless=False)
page=browser.new_page(viewport={"width": 1280, "height": 800})
result=test(page, args.record)
browser.close()
sys.exit(0ifresultelse1)
if__name__=="__main__":
main()Fails on v1.18.4 (0 POST requests, session count unchanged). Passes with this PR applied. |
Issue for this PR
Closes#37606
Type of change
What does this PR do?
When the opened-project list is empty, new sessions now fall back to the server startup directory returned by
/path.The home empty state exposes its New Session action, and the titlebar action opens and remembers that directory before creating the draft. Existing selected, recent, and opened-project behavior stays unchanged.
How did you verify your code works?
/new-session?draftId=...for the server startup directory.bun test --preload ./happydom.ts ./src/pages/layout/helpers.test.ts(29 passed).bun run typecheckand Prettier checks.Screenshots / recordings
The browser verification rendered the new-session composer for the server startup directory from a clean storage state.
Checklist