A band above the prompt showing the current session's directory, with a Copy button that puts the path on the clipboard.

A Claude Code mod that adds a band above the prompt showing the current session's directory, with a Copy button that puts the path on the clipboard:
Session dir C:\Users\you\.claude\projects\C--Users-you-repo\<session-id> [Copy]
The path is the session's transcript path without .jsonl (~/.claude/projects/<project>/<session-id>). It follows /clear and resumes. Claude Code only creates that folder once the session writes something there, such as subagent transcripts or large tool outputs, so it may not exist yet.
At the prompt of a terminal Claude Code session:
/plugin install session-dir-button --marketplace dcr-lol/claude-session-mod
Answer y to add the marketplace, then pick a scope.
claude plugin validate session-dir-button
claude plugin test session-dir-button
claude --plugin-dir session-dir-buttonhooks/register.tsx 79 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { SessionDir } from '../types'
5
6const BACKSLASH = String.fromCharCode(92)
7
8const dir = atom({ plugin: 'session-dir-button', key: 'dir' } as const, null as SessionDir)
9
10// The session's own folder sits beside its transcript, named by its id:
11// ~/.claude/projects/<project>/<session-id>.jsonl -> .../<project>/<session-id>
12function sessionDirOf(transcriptPath: string): string {
13 return transcriptPath.endsWith('.jsonl') ? transcriptPath.slice(0, -'.jsonl'.length) : transcriptPath
14}
15
16// Mirrors how Claude Code names a project folder (every non-alphanumeric
17// character of the cwd becomes '-') and adds the session id. Used until a
18// classic event brings the real transcript path.
19async function guessDir($: EngineInterface, cwd: string): Promise<string> {
20 const isWindows = cwd.charAt(1) === ":"
21 const sep = isWindows ? BACKSLASH : '/'
22 const home = (isWindows ? await $.env.get('USERPROFILE') : await $.env.get('HOME')) ?? ''
23 const configDir = (await $.env.get('CLAUDE_CONFIG_DIR')) ?? `${home}${sep}.claude`
24
25 const project = cwd.replace(/[^a-zA-Z0-9]/g, '-')
26
27 return `${configDir}${sep}projects${sep}${project}${sep}${await $.session.id()}`
28}
29
30async function fromTranscript($: EngineInterface, transcriptPath: string): Promise<void> {
31 if (transcriptPath) {
32 await update($, dir, () => sessionDirOf(transcriptPath))
33 }
34}
35
36export const register: Register = on => {
37 on('session.start', async ($, e, next) => {
38 const guess = await guessDir($, e.cwd)
39 await update($, dir, () => guess)
40
41 return next(e)
42 })
43
44 on('classic.SessionStart', async ($, e, next) => {
45 await fromTranscript($, e.transcript_path).catch(() => undefined)
46
47 return next(e)
48 })
49
50 on('classic.UserPromptSubmit', async ($, e, next) => {
51 await fromTranscript($, e.transcript_path).catch(() => undefined)
52
53 return next(e)
54 })
55
56 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
57 const path = await read($, dir)
58
59 if (e.props.hasSurvey || path === null) {
60 return next(e)
61 }
62
63 const { Box, Button, Text } = $.ui.resolve(e)
64
65 async function copy(surface: typeof e.surface): Promise<void> {
66 const copied = await $.ui.copy({ text: path as string, surface })
67 $.ui.toast(copied.isCopied ? 'Session directory copied' : `Could not copy: ${copied.reason}`)
68 }
69
70 return (
71 <Box flexDirection="row" gap={1}>
72 <Text dimColor>Session dir</Text>
73 <Text key="path">{path}</Text>
74 <Button key="copy" label="Copy" onPress={press => copy(press.surface)} />
75 </Box>
76 )
77 })
78}
79types/index.d.ts 8 lines1export type SessionDir = string | null
2
3declare module 'claude-code' {
4 interface PluginState {
5 'session-dir-button': { dir: SessionDir }
6 }
7}
8