Skip to content

fix(response-viewer): a session on a relocated Claude config folder gets its answers back - #812

Merged
Ark0N merged 1 commit into
masterfrom
fix/response-viewer-config-dir
Nov 12, 2026
Merged

Ark0N merged 1 commit into
masterfrom
fix/response-viewer-config-dir

Conversation

@Ark0N

@Ark0N Ark0N commented Nov 12, 2026

Copy link
Copy Markdown
Owner

Problem

A Claude session can run on its own Claude config folder. Codeman supports that through the session environment override CLAUDE_CONFIG_DIR, which is how one machine runs one session on a second Claude account, and a server can also export the variable for every session it starts. Claude then keeps its transcripts and its history.jsonl under that folder instead of ~/.claude.

The response viewer (the eye button, GET /api/sessions/:id/last-response) looked under ~/.claude only. For such a session:

  • the viewer showed "No response yet" or a cleaned terminal dump instead of the answer,
  • everything else that reads the same function came back empty, the Discord bridge's last message included,
  • after a /clear the viewer stayed on the old conversation, because the history file it follows was read from ~/.claude too,
  • a rename did not reach the conversation's /resume title until a hook had named the transcript.

The Edit history panel and the relaunch pin already resolve the right folder. The viewer did not.

This is the third part of issue #701 ("a transcript outside ~/.claude/projects can also be missed"). The other two parts of that issue are not in this PR, so it stays open.

Change

One helper, claudeConversationDirs(session) in src/web/routes/session-routes.ts, answers where this session's Claude keeps its conversations: <config dir>/projects and <config dir>/history.jsonl. It resolves the folder through claudeProjectsDir(), the function the relaunch pin and the Edit history panel use: the session's own override first, then the server's CLAUDE_CONFIG_DIR, then ~/.claude.

  • lastResponseFor() (the viewer route and the Discord bridge) takes both paths from it.
  • resolveActiveClaudeSessionIdFromHistory() gets the history path as a parameter instead of building ~/.claude/history.jsonl itself.
  • syncClaudeTitle() (the rename's /resume title) uses the same lookup when no hook has named the transcript yet.

Three rules live in the helper:

The default folder is not probed, so a session without any override takes the path it took before.

Docs: the root CLAUDE.md, docs/architecture-invariants.md and the wiki pages Agent CLIs and Watching Agents Work no longer list the response viewer among the surfaces that go blind on a relocated folder.

Not in this PR

  • Resume at session create. The check that a resumeSessionId has a transcript (validatedResumeId in the same file) still reads ~/.claude/projects only, so a resume on a relocated folder is likely dropped there. That is a follow-up.
  • The host-wide history scans (Session Manager, search) still read the default folder.
  • Subagent windows, the ultracode panel and Read My Mind still need the documented symlink for a relocated folder.
  • The other two parts of Claude Response Viewer loses earlier conversation history on resume (implemented fix linked) #701: that the viewer opens on the last answered turn (on purpose), and that More reads one transcript instead of the whole chain after a /clear or a resume (a feature request, undecided).

Reviewing the diff

One source file. Read claudeConversationDirs() first, then its three call sites. The rest of the diff is tests and four doc files.

Risk

  • A server that exports CLAUDE_CONFIG_DIR now has the viewer read that folder for every local Claude session. That is the point of the change, and it is a behaviour change for such a server.
  • The default path comes from homedir() now, where the old line read process.env.HOME with /tmp as a fallback. Both give the same folder whenever HOME is set.
  • Multi-user mode: CLAUDE_CONFIG_DIR is already a privileged key there (only an admin may set it), so the viewer reads no folder a non-admin could choose.

Verification

  • Tests: five route cases for the viewer in test/routes/session-routes-claude-last-response.test.ts (the session's own folder, the server's, no fallback to ~/.claude, a /clear followed through the relocated history, a Docker session left alone) and one for the rename in test/routes/session-name-routes.test.ts.
  • They bite: five mutations, each reverting one part of the change (the override, the Docker rule, the history path, the server variable, the rename lookup), and each fails at least one of the new tests.
  • Gate on this head: check:lockfile, typecheck, lint, check:frontend-syntax, check:browser-excludes, format:check, check:public-assets and the CLI catalogue check pass. npm test: 756 files and 17,120 tests passed, none failed.
  • With the variable exported: the six route test files around last-response also pass on a machine that exports CLAUDE_CONFIG_DIR (91 of 91; the test setup clears it).
  • Live: an isolated instance of this head (its own data dir, tmux socket and port) with three real sessions created through POST /api/sessions:
    • envOverrides.CLAUDE_CONFIG_DIR set, and a one-line transcript under that folder: the brief and the full read both return its text,
    • no override, and a transcript of the same name in the relocated folder only: empty, as it should be,
    • an override that names a folder that does not exist: 200 with an empty text.

Not verified: a real Claude process writing into a relocated folder end to end. The live read used a hand-written one-line transcript. Docker and remote sessions are covered by the unit test only (they must stay on the old lookup).

No changeset: the next release's changeset names this change.

Part of #701.

…ets its answers back (#701)

The viewer looked for a Claude transcript under ~/.claude/projects only, and
for the conversation after a /clear in ~/.claude/history.jsonl only. A session
started with its own CLAUDE_CONFIG_DIR (a second account, #255), or any session
on a server that exports one, writes both elsewhere, so the eye button showed
"No response yet" or a terminal dump, and codeman agent read and the Discord
bridge's last message came back empty. This is the third part of the report
in issue 701; the other two are a design choice and a feature request.

lastResponseFor() now asks claudeConversationDirs() for the folder the
session's claude writes to: its own override, then the server's, then
~/.claude, through claudeProjectsDir(), which the relaunch pin and the Edit
history panel already use. The rename's /resume title takes the same lookup
when no hook has named the transcript yet.

- Local sessions only. In a Docker case or on a remote host the override
  names a folder over there, and the same path on this host is someone else's.
- A relocated folder is a path somebody chose, so it is probed bounded first
  (the rule from issue 516). Anything but a directory reads as no transcript,
  never as a reason to look under ~/.claude, where a file of the same name is
  another account's conversation.
- The default folder is not probed, so that path is as before.

Not changed: the resume pre-validation at session create and the host-wide
history scans still read the default folder.

Tests: five route cases for the viewer (own folder, the server's, no fallback,
/clear through the relocated history, Docker left alone) and one for the
rename. Each part of the change fails one of them when reverted.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@Ark0N
Ark0N merged commit 60f1900 into master Nov 12, 2026
2 checks passed
@Ark0N
Ark0N deleted the fix/response-viewer-config-dir branch November 12, 2026 17:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants