Skip to content

Support CLAUDE_CONFIG_DIR so sessions from multiple Claude accounts are visible - #88

Open
amims71 wants to merge 5 commits into
patoles:mainfrom
amims71:feat/claude-config-dir
Open

amims71 wants to merge 5 commits into
patoles:mainfrom
amims71:feat/claude-config-dir

Conversation

@amims71

@amims71 amims71 commented Sep 14, 2026

Copy link
Copy Markdown

Closes #68.

What

Agent Flow now honours CLAUDE_CONFIG_DIR, so sessions from a relocated Claude Code config directory are visible. Because Agent Flow is a viewer rather than a client, the variable is read as a comma-separated list — one board can show several accounts at once:

# one account
CLAUDE_CONFIG_DIR="$HOME/.claude-personal" npx agent-flow-app

# work and personal side by side
CLAUDE_CONFIG_DIR="$HOME/.claude,$HOME/.claude-personal" npx agent-flow-app

Unset, behaviour is unchanged: ~/.claude alone.

Why

CLAUDE_CONFIG_DIR was read nowhere in the repo — every Claude path was a hardcoded os.homedir()/.claude. On my own machine that meant 43 of 58 transcripts were dark to the visualizer:

~/.claude/projects           15 transcripts   <- watched
~/.claude-personal/projects  43 transcripts   <- invisible
~/.claude-personal/settings.json  hooks: {}   <- no hooks registered

Three independent subsystems were affected:

  1. Transcript watching — scripts/relay.ts and extension/src/session-watcher.ts both scanned only ~/.claude/projects. This is the big one; transcripts drive nearly the whole visualization (tool calls, subagents, model detection).
  2. Hook registration — scripts/setup.js and extension/src/hooks-config.ts wrote only to ~/.claude/settings.json, so a session under another config dir emitted no live events at all.
  3. Uninstall — cleaned one settings file, orphaning hook entries elsewhere that pointed at a hook.js it then deleted.

How

A shared resolver (extension/src/claude-home.ts) parses the variable once; the watchers and hook installers iterate over every root it returns.

Two decisions worth reviewing:

The discovery directory stays pinned to ~/.claude/agent-flow/. This is the subtle part. It is a rendezvous point: the relay writes {hash}-{pid}.json there and hook.js reads it to find which relay to POST to. But hook.js runs as a child of Claude Code and inherits CLAUDE_CONFIG_DIR, so an env-derived path would send a personal-account hook looking in ~/.claude-personal/agent-flow/ while the relay wrote to ~/.claude/agent-flow/ — no match, no events, and no error. It is Agent Flow's own directory rather than Claude's, so both sides must agree on one env-independent location. Keeping it fixed also means a single installed hook.js serves every account.

The "already configured" checks now require all roots, not any one. With some, a first run against ~/.claude made every later run skip setup entirely, so a config dir added afterwards would never get configured.

readGlobalSettings (used by isDisable1MContext) reads the first root that exists — per-account settings can't be meaningfully merged, and that entry is ~/.claude whenever the variable is unset, so existing behaviour is preserved.

Testing

extension/test/claude-home.test.ts adds 11 unit tests for the resolver: default fallback, empty/whitespace values, single dir, comma lists with order preserved, tilde expansion, dedup, and relative-path resolution.

Verified end-to-end against the built app/dist/app.js with transcripts planted under two temp config roots. The initial session-list SSE snapshot the UI receives contained both sessions:

CLAUDE_CONFIG_DIR="$R/.claude,$R/.claude-personal"  ->  both sessions present
CLAUDE_CONFIG_DIR="$R/.claude"                      ->  only the first (control)

scripts/setup.js was exercised against temp config dirs to confirm it writes hooks to each one, preserves unrelated existing hooks and other settings keys, stays idempotent across repeated runs (no duplicate entries), and configures a newly added dir rather than skipping it.

Full CI suite green locally: root tests 32/32, extension tests 39/39, extension typecheck, extension test typecheck, web typecheck.

Notes

The VS Code extension inherits VS Code's environment, which usually won't carry CLAUDE_CONFIG_DIR. The plumbing is in place and honoured when it is set; exposing an agentFlow.claudeConfigDirs setting for that case would be a natural follow-up, left out here to keep this focused.

Claude Code honours CLAUDE_CONFIG_DIR to relocate ~/.claude, which is how
people keep separate accounts on one machine. Agent Flow hardcoded
~/.claude everywhere, so anything under a relocated dir was invisible.

Add a single resolver the rest of the codebase can share. Because Agent
Flow is a viewer rather than a client, it parses the variable as a
comma-separated list — the form ccusage accepts — so one board can show
several accounts instead of forcing a choice between them. Passing a
single directory still works and simply scopes it to that account.

Entries are tilde-expanded, resolved to absolute paths and deduplicated
so a doubled entry can't attach two watchers to one directory.
Transcripts drive nearly the whole visualization — tool calls, subagents,
model detection — and both watchers only ever scanned
~/.claude/projects/. A session started under a relocated config dir wrote
its transcript somewhere that was never read, so it never appeared.

Scan and watch projects/ under every configured root. Session ids are
uuids, so collecting across roots cannot collide: the same repo open
under two accounts simply yields two sets of sessions.

The containment cache in SessionWatcher is now keyed by full path rather
than by encoded dir name, since the same name can exist under more than
one root and each copy needs its own verdict. The single CLAUDE_DIR
watcher becomes one watcher per root, closed together on dispose.

DISCOVERY_DIR is deliberately left pinned to ~/.claude/agent-flow. The
relay writes discovery files there and hook.js reads them back, but
hook.js runs as a child of Claude Code and inherits CLAUDE_CONFIG_DIR —
deriving that path from the variable would send a hook from a non-default
config dir looking somewhere the relay never wrote to, silently dropping
its events. Both sides must agree on one env-independent location.
Hooks were only ever written to ~/.claude/settings.json. A session
started under a relocated config dir reads its own settings.json, finds
no Agent Flow hooks, and emits no live events — no SessionStart, no
permission prompts, no Stop.

Write the hook entries to settings.json under every configured root. The
hook command still points at the single pinned ~/.claude/agent-flow/
hook.js, so one installed script serves every account.

The already-configured checks now require *all* roots rather than any
one. With `some`, a first run against ~/.claude made every later run skip
setup entirely, so a config dir added afterwards would never be
configured.

readGlobalSettings reads the first root that exists — per-account
settings can't be meaningfully merged, and that entry is ~/.claude
whenever CLAUDE_CONFIG_DIR is unset, so existing behaviour is unchanged.
Uninstall cleaned ~/.claude/settings.json only, then deleted the whole
~/.claude/agent-flow/ directory. Any other config dir was left holding a
hook entry pointing at the hook.js that had just been removed.
@cla-assistant

cla-assistant Bot commented Sep 14, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@cla-assistant

cla-assistant Bot commented Sep 14, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

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.

Claude: Support different config directories

1 participant