Skip to content

Session list misses sessions when directory case differs on Windows #49556

Open

Description

@pascalandr

Description

Directory-scoped session listing compares the stored directory with exact SQL string equality. On Windows a session stored as \D:\codenomad\ disappears from a \D:\CodeNomad\ listing even though both spellings are the same directory. I hit this through CodeNomad: sessions vanished when the requested case did not match the stored case.

Steps to reproduce

  1. On Windows, create a session in \D:\codenomad.
  2. List sessions with directory \D:\CodeNomad\ (same folder, different case).
  3. The session is missing from the result.

Operating System

Windows 11

Terminal

Windows Terminal

Activity

  1. github-actions commented on Sep 17, 2026

    @github-actions
    Contributor

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

  2. Martin-soaring-dev commented on Oct 7, 2026

    @Martin-soaring-dev

    Additional data point from OpenCode Desktop 2.0.24 on Windows 10.0.26300 (read-only GET /api/session?limit=200&directory=…). Same folder, separators identical — only the drive-letter case differs:

    directory query sessions returned
    e:/…/citavi6_language_add-on_ZH 37
    E:/…/citavi6_language_add-on_ZH 33
    e:/…/MediaMetadataHub 1
    E:/…/MediaMetadataHub 0
    e:/…/默认项目 14
    E:/…/默认项目 0

    So the case-sensitive match still reproduces on 2.0.24. The downstream effect (one folder split into two project rows, new sessions written under the other case) is tracked in #52501.

  3. opencodejr commented on Oct 28, 2026

    @opencodejr

    Thanks for the report. I could reproduce this on the latest dev (950111f).

    I created a session whose directory is stored as D:/codenomad and listed sessions by directory through the core session service. GET /api/session?directory= uses the same filter.

    directory queried sessions returned
    D:/codenomad 1
    D:/CodeNomad 0
    d:/codenomad 0

    The directory filter compares the stored path letter for letter. Elsewhere, OpenCode's own path helpers already treat Windows drive paths as case-insensitive, so D:/codenomad and D:/CodeNomad count as the same directory there. The same filter is in the 2.0.24 and 2.2.0 releases, which matches the 2.0.24 measurements in the comment above. The script below reproduces it.

    Related: #52501 (one folder split into two projects by path case) and #51797 (web home page grouping sessions by exact path). Until it's fixed, querying with the exact stored spelling still returns the sessions.

    Reproduction script (repro-49556.test.ts)
    // Reproduction for anomalyco/opencode#49556: directory-scoped session listing is case-sensitive
    // for Windows drive paths, although @opencode/util/path treats them as case-insensitive.
    // Run from a dev checkout: cp this file to packages/core/test/ and `bun test test/repro-49556.test.ts` in packages/core.
    import { describe, expect } from "bun:test"
    import { Bus } from "@opencode/core/bus"
    import { Database } from "@opencode/core/database/database"
    import { AppNodeBuilder } from "@opencode/core/effect/app-node-builder"
    import { Location } from "@opencode/core/location"
    import { Project } from "@opencode/core/project"
    import { AbsolutePath } from "@opencode/core/schema"
    import { Session } from "@opencode/core/session"
    import { SessionExecution } from "@opencode/core/session/execution"
    import { SessionProjector } from "@opencode/core/session/projector"
    import { SessionStore } from "@opencode/core/session/store"
    import { sameDirectory } from "@opencode/util/path"
    import { LayerNode } from "@opencode/util/effect/layer-node"
    import { Effect } from "effect"
    import { testEffect } from "./lib/effect"
    import { globalProjectNode } from "./lib/project"
    
    const it = testEffect(
      AppNodeBuilder.build(
        LayerNode.group([Database.node, Bus.node, SessionProjector.node, SessionStore.node, Session.node]),
        [
          Bus.node.replace(Bus.configured({ persist: true })),
          Project.node.replace(globalProjectNode),
          SessionExecution.node.replace(SessionExecution.noopLayer),
        ],
      ),
    )
    
    describe("#49556", () => {
      it.effect("lists a session stored as D:/codenomad when queried as D:/CodeNomad", () =>
        Effect.gen(function* () {
          const session = yield* Session.Service
          const store = yield* SessionStore.Service
          const created = yield* session.create({
            location: Location.Ref.make({ directory: AbsolutePath.make("D:/codenomad") }),
            title: "windows session",
          })
          console.log("stored directory:", created.location?.directory ?? (created as any).directory)
          console.log("sameDirectory('D:/codenomad','D:/CodeNomad') =", sameDirectory("D:/codenomad", "D:/CodeNomad"))
          for (const dir of ["D:/codenomad", "D:/CodeNomad", "d:/codenomad"]) {
            const rows = yield* store.list({ directory: AbsolutePath.make(dir) })
            const listed = yield* session.list({ directory: AbsolutePath.make(dir) })
            console.log(`directory=${dir}: store.list -> ${rows.length}, Session.list -> ${listed.data.length}`)
          }
          const listed = yield* session.list({ directory: AbsolutePath.make("D:/CodeNomad") })
          expect(listed.data.map((s) => s.id)).toContain(created.id)
        }),
      )
    })

    Run it: bun repro-49556.test.ts

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