Skip to content

web: sessions not grouped under project on homepage when the same directory is loaded with two drive-letter cases (D:\ vs d:\) #51797

Open

Description

@jiangfire

Description

On Windows, the same directory gets loaded twice by the location services — once with an uppercase drive letter (D:\Codes\processv3) and once with a lowercase one (d:\Codes\processv3) — so /api/debug/location reports two entries for one folder. The Web UI homepage groups sessions by exact string match against project directories, and because the session's stored location.directory doesn't string-match the project list, all sessions for that project are filtered out. The project row itself shows up exactly once in the sidebar, but clicking it (or F5) shows no sessions under it — the conversations effectively "disappear" from the project grouping even though the server-side data is correct.

Expected Behavior

Sessions should be grouped under their project on the homepage regardless of drive-letter case. D:\Codes\processv3 and d:\Codes\processv3 are the same directory on Windows and should resolve to one location (and one match) — or at minimum the frontend comparison should be case-insensitive on win32.

Actual Behavior

  • The location registry holds two entries for the same folder (upper- and lowercase drive letter).
  • Server-side data is correct: sessions carry projectID=fd413c6b… matching the processv3 project row, and GET /api/session?project=… returns them.
  • The Web UI homepage filters sessions by comparing session.location.directory against the project directory list with exact string matching, so the sessions are dropped from the UI. The conversations never appear under the project group.

Plugins

no

OpenCode version

v2.0.18

Steps to reproduce

  1. On Windows, start the opencode service and open the Web UI homepage (/).
  2. Make sure the same directory is reachable under two drive-letter spellings (e.g. a session/TUI was launched with d:\Codes\processv3 at some point while the service also booted D:\Codes\processv3).
  3. Open GET /api/debug/location — the directory appears twice:
[
  {"directory":"D:\\Codes\\processv3"},
  {"directory":"d:\\Codes\\processv3"},
  ...
]
  1. Query sessions by project id — they are all there and correctly linked:
GET /api/session?project=fd413c6b402e04c300e1d97956417119bf671ed7
-> 3+ sessions, each with "location":{"directory":"D:\\Codes\\processv3"}
  1. Query sessions by the lowercase directory (the spelling the client keeps using) — empty:
GET /api/session?directory=d%3A%5CCodes%5Cprocessv3
-> {"data":[],"cursor":{"previous":null,"next":null}}
  1. On the Web UI homepage, the project D:\Codes\processv3 appears exactly once in the project list, but no sessions are grouped under it. Clicking the project entry or pressing F5 does not bring the sessions back.

Screenshot and/or share link

No response

Operating System

Windows_NT 10.0.19045 (win32 x64)

Terminal

Windows Terminal

Activity

  1. github-actions commented on Sep 28, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    • #49556: Windows session listings miss results when the requested directory differs only by case.
    • #14570: Web/Desktop hides sessions and duplicates workspaces when equivalent Windows paths differ in drive-letter case or slash format.
    • #17183: Windows path-format differences across clients cause sessions to disappear from project views.

    For keybind-related issues, please also check our pinned keybinds documentation: #4997

  2. opencodejr commented on Oct 28, 2026

    @opencodejr

    Thanks for the detailed report. I was able to reproduce this, both on v2.0.18 and on the latest dev (86ce19d). The sandbox runs Linux, so I checked the two code paths you point to directly rather than running a real Windows server:

    • Web UI home page: with a project at d:\Codes\processv3 and a session whose location.directory is D:\Codes\processv3, the home page's session filter returns no sessions. It compares directories with a case-sensitive key, even though other path helpers in the codebase already ignore drive-letter case on Windows.
    • Server: with win32 simulated, the step that normalizes location directories keeps D:\Codes\processv3 and d:\Codes\processv3 as two different keys. That fits the two entries you saw in /api/debug/location.

    The script below runs both checks. Until this is fixed, opening the project with the same drive-letter case every time should keep the sessions grouped. A related fix for the same kind of case mismatch elsewhere is #53349.

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Repro for #51797: Windows drive-letter case variants (D:\ vs d:\) are treated as
    # different directories by core's Location canonicalization and by the web Home
    # session filter. Runs against an opencode checkout (default /workspace/repo).
    # The core check simulates win32 by overriding process.platform (sandbox is Linux).
    set -euo pipefail
    REPO="${1:-/workspace/repo}"
    
    APP_TEST="$REPO/packages/app/src/home/sessions/repro-51797.test.ts"
    CORE_TEST="$REPO/packages/core/test/repro-51797.test.ts"
    trap 'rm -f "$APP_TEST" "$CORE_TEST"' EXIT
    
    cat > "$APP_TEST" <<'EOF'
    import { expect, test } from "bun:test"
    import type { SessionInfo } from "@opencode/client/promise"
    import type { LocalProject } from "@/shell/state/layout"
    import { buildHomeSessionRecords } from "./records"
    
    test("#51797 home groups sessions whose drive letter differs only in case", () => {
      const project = { id: "p", worktree: "d:\\Codes\\processv3" } as LocalProject
      const session = {
        id: "s1", projectID: "p", title: "s1",
        location: { directory: "D:\\Codes\\processv3" },
        time: { created: 1, updated: 1 },
      } as SessionInfo
      const records = buildHomeSessionRecords({
        sessions: () => [session],
        projectDirectories: () => ["d:\\Codes\\processv3"],
        projects: () => [project],
      })
      console.log("home records for project d:\\Codes\\processv3:", records.map((r) => r.session.id))
      expect(records.map((r) => r.session.id)).toEqual(["s1"])
    })
    EOF
    
    cat > "$CORE_TEST" <<'EOF'
    import { expect, test } from "bun:test"
    
    test("#51797 canonical location treats D:\\ and d:\\ as one directory on win32", async () => {
      Object.defineProperty(process, "platform", { value: "win32" })
      const { LocationServiceMap } = await import("../src/location-service-map")
      const a = LocationServiceMap.canonical({ directory: "D:\\Codes\\processv3" } as any).directory
      const b = LocationServiceMap.canonical({ directory: "d:\\Codes\\processv3" } as any).directory
      console.log("canonical:", JSON.stringify(a), JSON.stringify(b))
      expect(a).toBe(b)
    })
    EOF
    
    echo "== app: Home session filter =="
    JSX_PRELOAD=""
    [ -f "$REPO/packages/ui/test/solid-jsx.ts" ] && JSX_PRELOAD="--preload ../ui/test/solid-jsx.ts"
    (cd "$REPO/packages/app" && bun test --conditions=solid --isolate $JSX_PRELOAD --preload ./happydom.ts ./src/home/sessions/repro-51797.test.ts) || true
    echo "== core: LocationServiceMap.canonical (win32 simulated) =="
    (cd "$REPO/packages/core" && bun test ./test/repro-51797.test.ts) || true

    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions