the session list was asking for a directory that does not exist

Your conversation was there the whole time. The dropdown was empty because I sent
the wrong kind of path.

  what the header sent   -Tests-platform                 no such directory
  what is on disk        -home-pastilhas-Tests-platform  1 transcript

Two conventions live next to each other and I used the wrong one. The file
endpoints take a HOME-RELATIVE path (`/Tests/platform`) — that is what `cwdToPath`
produces and what the file browser and editor pass. `/chat/sessions` takes the
ABSOLUTE one, because Claude's folder name is
`cwd.replace(/[^a-zA-Z0-9]/g, '-')` applied to the directory a turn actually ran
in, and a turn runs in a real directory.

`cwdOf` uses the query value verbatim (`query('cwd')?.trim() || home`), so nothing
rejected the relative form — it just resolved to a slug for a directory that has
never existed, and answered an empty list rather than an error. The failure had no
symptom beyond "no conversations", which is also what a genuinely empty directory
looks like.

Joined against `useChatPwds().defaultCwd` — the caller's home as the SERVER sees
it, and the same value `cwdOf` falls back to — rather than guessing at where home
is a second time.

Verified end to end: `~/Tests/platform` now resolves to
`/home/pastilhas/Tests/platform`, whose slug is `-home-pastilhas-Tests-platform`,
which is a directory that exists and holds the transcript.

tsgo clean, frontend builds, 846 pass / 7 fail unchanged.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-15 23:29:25 +00:00
co-authored by Claude Opus 5
parent 465435ccf1
commit 8de6088546
@@ -7,7 +7,7 @@ import {
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useClaudeSessions } from 'state/useClaudeSessions';
import { useChatPwds, useClaudeSessions } from 'state/useClaudeSessions';
import { useWorkspace, cwdToPath } from '../../components/Workspace';
import { usePanelSession } from './use-panel-session';
import type { ChatMessage } from './types';
@@ -34,10 +34,23 @@ const title = (s: { title: string }) => s.title || '(untitled)';
export const ChatHeader = ({ panelId }: { panelId?: string }) => {
const { workspace, cwd } = useWorkspace();
const cwdPath = cwdToPath(cwd);
// The endpoint takes a home-relative path and treats absent as "the caller's default", which is what an
// unscoped panel wants.
const { sessions, renameSession, deleteSession } = useClaudeSessions(cwdPath === '/' ? null : cwdPath);
/**
* `/chat/sessions` wants an ABSOLUTE path, and that is not the convention next door.
*
* The file endpoints take a home-relative path (`/Tests/platform`); chat takes the real one, because
* Claude's folder name is `cwd.replace(/[^a-zA-Z0-9]/g, '-')` applied to the directory a turn actually
* ran in. Handing it the home-relative form asked for `-Tests-platform`, which is not a directory that
* exists, so a dashboard with a perfectly good conversation in it listed nothing at all.
*
* `useChatPwds().defaultCwd` is the caller's home as the server sees it — the same value `cwdOf` falls
* back to — so it is the right thing to join against rather than a second guess at where home is.
*/
const { defaultCwd } = useChatPwds();
const relative = cwdToPath(cwd);
const absoluteCwd = relative === '/' ? null : defaultCwd ? `${defaultCwd.replace(/\/$/, '')}${relative}` : null;
const { sessions, renameSession, deleteSession } = useClaudeSessions(absoluteCwd);
const [sessionId, setSessionId] = usePanelSession(panelId, workspace);
const [renaming, setRenaming] = useState<string | null>(null);