list opencode sessions from every project, not just the serve's own

An opencode session in a git directory never appeared in /chat. The list read GET /session,
which answers for ONE project — the one the request's directory resolves to, and with no
x-opencode-directory header that is the serve's own cwd, DATA_PATH/opencode_server. Not a git
checkout, so it resolves to the catch-all project `global`, along with every other non-git
directory. That is why the default chat dir listed fine and nothing looked broken: a cwd that
IS a checkout gets its own project, and chat pwds are checkouts.

Measured on the live serve before changing anything: /session returned 8 sessions, /api/session
13, the five missing ones being an old project's. A session created in a git directory came back
0 times from /session and 1 from /api/session.

/api/session spans projects, so that is now the list. The per-id reads stay on /session — they
answer for any session regardless of project, verified 200 with and without the header.

The trap, and the reason listSessions normalises rather than returning the response: the two
surfaces disagree in silence. /session carries the working directory as top-level `directory`,
/api/session as `location.directory` with no top-level field, inside a {data: …} envelope.
Swapping the endpoint without the mapping leaves `directory` undefined on every session, which
the cwd filter turns into an empty list — the same shape as the metadata.officer.cwd bug this
filter already had once.

Verified against the live serve: with the mapping, a session in a git directory and one in the
general chat dir both resolve to their cwd, and every session carries a directory.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 02:41:24 +01:00
co-authored by Claude Opus 5
parent 9d2da49572
commit adaaba658c
3 changed files with 37 additions and 12 deletions
+6 -7
View File
@@ -7,18 +7,17 @@ import { logger } from './logger';
// fixed pm2-managed server's HTTP API (never the DB directly). Returns the same shapes as the Claude
// reader, tagged harness:'opencode', so chat.ts can merge both harnesses transparently.
//
// Sessions do NOT all live in one project. That claim was true when turns went through the serve and
// inherited its directory; turns are now `opencode run --dir <cwd>` subprocesses, so each session
// records the directory it ran in and one serve happily lists sessions across many. Verified: a single
// serve returned 7 sessions spread over several directories, which is also why the cwd filter has to
// read `directory` rather than assume.
// Sessions do NOT all live in one project. Each records the directory its turn ran in — the serve takes
// it per request as `x-opencode-directory` — and one serve holds sessions for many. That is why the cwd
// filter has to read `directory` rather than assume, and why the list has to come from a read that spans
// projects (`client.listSessions`, which is where the project-scoping trap is written up).
/**
* List OpenCode sessions for a working directory, so each context (/chat pwd, email account, project)
* sees only its own. With no cwd, returns all. Never throws — returns [] if the server is unavailable.
*
* Filters on the session's own `directory`, which is what OpenCode records when the runner starts it
* with `--dir`. It used to filter on `metadata.officer.cwd`, a tag whose only writer
* Filters on the session's own `directory`, which is what OpenCode records from the directory the turn
* ran in. It used to filter on `metadata.officer.cwd`, a tag whose only writer
* (`client.createSession`) has no callers — so the comparison was against `undefined` for every session
* and the list was ALWAYS empty. `cwdOf` in chat.ts substitutes a default when no `?cwd=` is given, so
* the `!cwd` escape never fired either and there was no configuration in which an OpenCode session