Skip to content

"Open project" dialog always shows "No folders found" — GET /file missing required path param #39434

Description

@andrianm28

Bug: "Open project" / directory picker always shows "No folders found" — GET /file request missing required path query param

Summary

The "Open project" dialog (and the subfolder browser inside the "New Session" flow) always shows "No folders found", even when valid directories exist and the server's REST API works correctly when called directly. The root cause is a client/server API contract mismatch: the frontend calls the file-listing SDK method without the now-required path query parameter, so the server rejects every request with a 400.

This looks like a regression of the older #8325 (closed, fixed for a different endpoint/cause) — same user-visible symptom, different root cause.

Environment

  • opencode server version: 1.18.8 (self-hosted opencode web, behind an nginx reverse proxy on a custom domain)
  • Client: web UI (Chrome), accessed via a domain other than localhost
  • Server started with an explicit project directory (single project scope), not 0.0.0.0/browsing from a multi-project root

Root cause

The server's own OpenAPI schema for GET /file marks path as required:

{
  "name": "path",
  "in": "query",
  "schema": { "type": "string" },
  "required": true
}

Confirmed directly against a running server:

# missing "path" -> 400
curl -u user:pass "https://<host>/file?directory=%2Fhome%2Fopencode"
# {"name":"BadRequest","data":{"message":"Missing key\n  at [\"path\"]","kind":"Query"}}

# with "path=" (even empty) -> 200, real listing
curl -u user:pass "https://<host>/file?path=&directory=%2Fhome%2Fopencode"
# [{"name":".cache","path":".cache/", ...}, ...]

Server log confirms the rejection:

level=WARN message="schema rejection" kind=Query reason="Missing key\n  at [\"path\"]"

But the frontend's directory-picker code calls the SDK's file.list() without a path field, in both the v1 and v2 directory pickers:

packages/app/src/components/directory-picker-domain.ts (~line 345):

const request = args.sdk.api.file
  .list({ location: { directory: key } })   // <-- no `path` field
  .then((result) => result.data)
  .catch(() => [])   // <-- error silently swallowed, becomes "No folders found"

packages/app/src/components/dialog-select-directory-v2.tsx (~line 130):

return sdk.api.file
  .list({ location: { directory: absolute } })  // <-- same issue
  .then((result) => ...)

Meanwhile, the SDK's own generated types (packages/sdk/js/src/v2/gen/types.gen.ts) show the correct/expected shape:

export type FileListData = {
  body?: never
  path?: never
  query: {
    directory?: string
    workspace?: string
    path: string   // required, not optional
  }
  url: "/file"
}

So the call sites use an outdated { location: { directory } } shape instead of the flat { directory, path } shape the SDK types (and server) now require. Because the .catch(() => []) swallows the resulting 400 error, the UI shows an empty "No folders found" state with no visible error at all, making this very hard to diagnose from the UI alone.

Steps to reproduce

  1. Run opencode web with a specific project directory (not the filesystem root/home), behind a reverse proxy on a non-localhost domain.
  2. Open the web UI, click "Open project" (or "Add project" from the New Session flow).
  3. Observe: "No folders found", regardless of what's typed in the search box or what directories actually exist.
  4. Open browser DevTools → Network tab → observe GET /file?... (or /find/file?...) requests returning 400 Bad Request with {"message":"Missing key\n at [\"path\"]"}.

Expected behavior

The directory picker should list real folders, since the underlying REST API works correctly when called with the required path parameter.

Suggested fix

Update the call sites in directory-picker-domain.ts and dialog-select-directory-v2.tsx to include an explicit path: "" (or the correct relative path being browsed) alongside directory, matching the FileListData query shape. Also consider not silently swallowing HTTP errors in .catch(() => []) for this codepath, so future contract mismatches surface as a visible error rather than a confusing empty state.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions