Skip to content

[Bug]: Either not following CLAUDE_CONFIG_DIR or not loading shell env properly? #7740

Open

Description

@ramarivera

Operating system

macOS

Orca version

latest

Details

Image

Just tried to open a claude tab, this happened, it tried to create the default claude config directory, when my environment config exports CLAUDE_CONFIG_DIR=~/.config/claude and it works for most other apps.

Would be fine doing a change to support this if aligned with maintainers

Activity

  1. MichaelHoughtonDeBox commented on Jul 8, 2026

    @MichaelHoughtonDeBox
    Contributor

    On macOS (Orca 1.4.110) I traced a reproducible version of this — it looks like two overlapping causes, and the first is a stale shell-env snapshot rather than CLAUDE_CONFIG_DIR being ignored per se.

    1. Orca snapshots the shell environment at app launch and doesn't re-source it per terminal.

    Repro:

    1. With Orca running, add export CLAUDE_CONFIG_DIR=… to ~/.zshrc (in my case via a conditional keyed on ORCA_USER_DATA_PATH).
    2. Open a brand-new terminal/Claude tab in the already-running instance → echo $CLAUDE_CONFIG_DIR is empty, and Claude creates/uses the default ~/.claude.
    3. In that same terminal, run source ~/.zshrc → CLAUDE_CONFIG_DIR is now set correctly.
    4. Fully Cmd+Q and relaunch the instance → new terminals now have it baked in without any manual sourcing.

    So the .zshrc content is correct; the launched shell just inherited a snapshot captured before the edit. Other ORCA_* vars (ORCA_USER_DATA_PATH, etc.) were injected fine — CLAUDE_CONFIG_DIR simply wasn't in the snapshot until relaunch. If Orca sources the login shell non-interactively (or reuses a cached env), a CLAUDE_CONFIG_DIR exported only in interactive ~/.zshrc won't be seen. Sourcing the user's shell env fresh at terminal/agent spawn (or documenting that env changes need a full relaunch) would resolve this half.

    2. Even once the var is present, the managed-Claude path can override it. Orca's host path materializes the active managed account into the shared ~/.claude (see #6921: "switching is global — it re-materializes shared ~/.claude"), which will win over a user-set CLAUDE_CONFIG_DIR. On macOS the credential lookup is keychain-based and scoped per config dir as Claude Code-credentials-<sha256(configDir)[:8]>, so honoring CLAUDE_CONFIG_DIR also means seeding that scoped service rather than writing the unscoped default.

    Worth noting #6921 / draft PR #6963 already implement the injection path (CLAUDE_CONFIG_DIR per launch + the macOS scoped-keychain seed) for the multi-account case — landing that would cover cause 2 here; cause 1 (env snapshot freshness) looks like a separate, smaller fix.

    Environment: macOS, Orca 1.4.110, Claude Code 2.1.204.

  2. added theissue type on Jul 12, 2026
  3. nwparker commented on Jul 18, 2026

    @nwparker
    Contributor

    Status: REPRODUCED (unit test against real product code; the full new-terminal path also needs the live app)

    Root cause: ClaudeRuntimePathResolver.getRuntimePaths() (src/main/claude-accounts/runtime-paths.ts:14-24) derives the Claude config dir only from process.env.CLAUDE_CONFIG_DIR — the login-shell env snapshotted when the GUI app launched. That snapshot is never re-sourced per terminal/agent spawn, so a CLAUDE_CONFIG_DIR exported only from the user's interactive ~/.zshrc is invisible to the main process. The resolver then falls back to the default ~/.claude, mkdirSync-creates it (the reported "it tried to create the default claude config directory"), and returns an empty envPatch. That empty patch flows through runtime-auth-service.ts:698 into the PTY spawn env (ipc/pty.ts:3076/:3878), so managed Claude launches use ~/.claude instead of the user's dir — until a full Cmd+Q relaunch bakes the var into the snapshot. This isolates cause 1 (env-snapshot freshness) from the thread; cause 2 (managed scoped-keychain seed) is #6921 / draft PR #6963.

    Re-run:

    pnpm exec vitest run --config config/vitest.config.ts src/main/claude-accounts/repro-7740-config-dir-launch-snapshot.test.ts
    

    The test (2 passed) pins the buggy result: unset var → configDir=<HOME>/.claude, dir created, envPatch==={}; control shows the var honored only when present in process.env.

    Key files: src/main/claude-accounts/runtime-paths.ts, src/main/claude-accounts/runtime-auth-service.ts, src/main/ipc/pty.ts, src/main/startup/hydrate-shell-path.ts (existing per-launch PATH-only probe).


    Automated bug-reproduction pass. Artifact on branch nwparker/bug-repro-pass2; writeup docs/bug-reproductions/7740.md.

  4. added
    has_reproConclusive reproduction exists (test/script/CLI evidence). See docs/bug-reproductions/
    on Jul 18, 2026
  5. chanmuzi commented on Aug 18, 2026

    @chanmuzi

    Adding a data point from a different angle: this also affects Remote Orca Servers (SSH relay), and that facet is not covered by the existing analysis or by PR #6956.

    Setup

    • Remote host (Linux, SSH relay v0.1.0) with a multi-account Claude setup: the work account is launched via a shell function that exports CLAUDE_CONFIG_DIR=~/.claude-upstage (the default ~/.claude is a separate personal account).
    • Orca installed its agent hooks into ~/.claude/settings.json on the remote host.

    Symptom

    Every Claude Code session launched with CLAUDE_CONFIG_DIR set is never recognized by Orca. The pane env is fine (ORCA_PANE_KEY, ORCA_AGENT_HOOK_ENDPOINT present, hook HTTP endpoint reachable) — the hooks simply never load, because Claude reads settings exclusively from $CLAUDE_CONFIG_DIR/settings.json and the Orca hook entries only exist in ~/.claude/settings.json.

    Root cause (code)

    Both install paths hardcode the .claude dir name relative to home, with no CLAUDE_CONFIG_DIR resolution:

    • Local: getConfigPath() in src/main/claude/hook-settings.ts → join(homedir(), settings.configDirName, 'settings.json')
    • Remote: getRemoteConfigPath() in the same file → ${remoteHome}/${settings.configDirName}/settings.json

    PR #6956 keeps hooks scoped to the config dir that Orca's own launch prep resolves (managed accounts / WSL), but a user-env-exported CLAUDE_CONFIG_DIR (set by shell rc / a launcher function, not by Orca) and the remote relay install path both remain outside its scope — so this issue would persist even with #6956 merged.

    There is precedent in the codebase for handling exactly this class of problem: the relay's shell-ready rcfile already re-exports ORCA_OPENCODE_CONFIG_DIR → OPENCODE_CONFIG_DIR for OpenCode. An analogous treatment for Claude (probe the login shell's CLAUDE_CONFIG_DIR, or install hooks into every config dir in use) would close the gap. The hook entries themselves are portable — they resolve ~/.orca/agent-hooks/claude-hook.sh at runtime and no-op when it is absent — so installing them into additional config dirs is safe.

    Workaround that fully restores recognition (verified): copy the Orca-managed hook entries from ~/.claude/settings.json into $CLAUDE_CONFIG_DIR/settings.json, then restart the Claude sessions.

    One more side effect worth noting: the hook installer rewrites ~/.claude/settings.json by file replacement, which silently breaks a user-managed symlink at that path (dotfiles setups). Preserving symlinks (write through, or edit in place) would avoid that.

    Happy to contribute a PR for the env-derived + remote-install facet if maintainers agree on the direction.

  6. m-stadt commented on Aug 18, 2026

    @m-stadt

    Another data point, on a platform not yet covered here (Windows + WSL2), and with a mechanism that I think the fix currently under discussion would still miss.

    Setup

    • Orca native Windows build (1.4.184), project routed into WSL2 (Ubuntu).
    • Two Claude config dirs: default ~/.claude (personal) and ~/.claude-etm (work).
    • The work account is launched from an alias that sets the variable inline on the claude invocation, not as an exported shell variable:
      alias claude-etm='CLAUDE_CONFIG_DIR=~/.claude-etm env -u ANTHROPIC_API_KEY -u ANTHROPIC_AUTH_TOKEN -u ANTHROPIC_BASE_URL claude'

    Symptom

    Sessions started that way are never recognised — the pane shows as "Plain shell — no indicator", no beacon, no agent status. Sessions using the default ~/.claude are recognised correctly.

    Verified cause on this machine

    Orca's forwarder is installed and correct; only its location is wrong.

    • ~/.orca/agent-hooks/claude-hook.sh — present, executable. The hook command's $OSTYPE branch correctly selects /bin/sh "$HOME/.orca/agent-hooks/claude-hook.sh" on non-Windows, so the WSL guest path is fine.
    • ~/.claude/settings.json — 11 Orca hook events: SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, Stop, StopFailure, SubagentStart, SubagentStop, TeammateIdle.
    • ~/.claude-etm/settings.json — no hooks key at all.

    So Claude reads .claude-etm, emits no hook events, and the OSC-title fallback doesn't identify it either.

    Why this is a distinct facet

    The two facets analysed above are both environment-snapshot problems — the stale app-launch shell env on macOS (ClaudeRuntimePathResolver.getRuntimePaths() reading only process.env.CLAUDE_CONFIG_DIR), and the Remote-SSH-relay host.

    In this setup the variable is never in any shell environment at any point. It exists only in the claude process's own environment, set inline by the alias. So re-sourcing the login shell per terminal — the fix being discussed — would still resolve to ~/.claude here and the session would still go unrecognised.

    I'd guess this is common for multi-account users: an inline-prefix alias or a wrapper function is the usual way to run two Claude accounts side by side, precisely because it avoids exporting the variable globally.

    Possible directions

    Deferring to maintainers on which fits the architecture, but the ones that would work regardless of how the variable is set:

    1. Install into every discovered config dir — glob the plausible ones (~/.claude, ~/.config/claude, plus any registered via settings) and keep the hook block in sync in each. Idempotent and snapshot-independent.
    2. Resolve from the spawned process rather than the app env — read the child's environment, or have the forwarder report the config dir it was loaded from on SessionStart.
    3. A per-agent/per-project config-dir setting in Orca, so users with a non-default dir can declare it explicitly. This also covers OPENCODE_CONFIG_DIR ([Bug]: OpenCode in Orca ignores default ~/.config/opencode — agents run with the wrong model when OPENCODE_CONFIG_DIR is unset #9186) and would compose with the multi-account requests ([Feature]: Per-worktree Claude account binding (concurrent multi-account + per-project defaults) #6921, [Feature]: Per-worktree account pinning — run different Claude/Codex accounts concurrently #9496).

    Working local workaround

    Copy the ~/.orca-owned hook entries from ~/.claude/settings.json into the alternate dir's settings.json, leaving any non-Orca hooks alone. Recognition then works normally. It drifts on every Orca update that rewrites the hook block, so it's a stopgap rather than a fix.

    Happy to test a patch on the WSL path if that's useful.

  7. nanma commented on Sep 16, 2026

    @nanma

    Another data point, on a facet not covered by the analysis so far: the Agent Session History sidebar panel, and specifically on a headless orca serve runtime.

    Setup

    • orca serve --user-data-dir=... --pairing-address ... on a shared Linux server (multiple users log in as the same root account over SSH).
    • CLAUDE_CONFIG_DIR and CODEX_HOME are both exported (per-user, via an authorized_keys environment= entry) to keep each user's Claude/Codex state out of the shared ~/.claude / ~/.codex.
    • Claude Code sessions run fine and write transcripts correctly under $CLAUDE_CONFIG_DIR/projects/.

    Symptom

    The right-sidebar "Agent Session History" panel shows other users' sessions (the ones using the default ~/.claude), but never shows my own sessions, even though the transcripts exist on disk under $CLAUDE_CONFIG_DIR/projects/<encoded-cwd>/*.jsonl. Same issue for Codex under $CODEX_HOME/sessions/.

    Root cause (from reading the bundled app.asar)

    This is a separate hardcoded path from the ones already identified in this issue — not runtime-paths.ts or hook-settings.ts, but the session-history scanner used to populate the sidebar list. The scanner builds its candidate list from remoteHome (i.e. $HOME) only:

    function R1r(e,t){return[...B1r(e,t),{...ZZ(`claude`,e,t,[`.claude`,`projects`],...)},...]}
    function B1r(e,t){return [aE(t,e,`.codex`), aE(t,e,`.local`,`share`,`orca`,`codex-runtime-home`,`home`)].map(e=>({agent:`codex`,rootDir:aE(t,e,`sessions`),...}))}

    Neither function consults process.env.CLAUDE_CONFIG_DIR or process.env.CODEX_HOME. Interestingly, a different code path in the same bundle (_un() / vun(), used for resolving a single already-known session by ID, e.g. on resume) does check these env vars correctly:

    function _un(){return [join(process.env.CLAUDE_CONFIG_DIR?.trim()||join(homedir(),`.claude`),`projects`), join(homedir(),`.claude`,`projects`)]}
    function vun(){return [join(codexHome(),`sessions`), join(process.env.CODEX_HOME?.trim()||join(homedir(),`.codex`),`sessions`)]}

    So the codebase already has the right pattern for this in one place (_un/vun), just not in the sidebar's discovery scanner (R1r/B1r).

    Notes specific to this scenario

    • This reproduces even on a brand-new orca serve data dir with --user-data-dir correctly set — it's unrelated to the env-snapshot-at-launch issue described above for the GUI app, since orca serve itself has CLAUDE_CONFIG_DIR in its process environment the whole time (verified via /proc/<pid>/environ on the daemon process); it simply never reads it here.
    • Not mentioned yet in this issue: Codex is affected the same way via CODEX_HOME, not just Claude.
    • This is on a shared-root multi-user server, so the workaround of installing hooks/settings into ~/.claude (as suggested above) isn't ideal here — it would mix multiple users' session data into one shared directory. A per-scan env check (matching _un/vun) would be the cleaner fix for this facet.

    Happy to test a fix if one lands.

  8. Satsrag commented on Sep 23, 2026

    @Satsrag

    This still reproduces on Orca 1.4.207 (macOS 27.0, Claude Code 2.1.280), in the plain local GUI setup: CLAUDE_CONFIG_DIR points to an external drive and is exported from ~/.zshrc.

    I hit both facets already described here:

    1. Launched from Dock: the Orca main process has no CLAUDE_CONFIG_DIR (checked with ps -E), so history and usage come up empty.
    2. After launchctl setenv CLAUDE_CONFIG_DIR … and a relaunch: the main process has the var and history works. But the chat UI still shows only the user message and never the assistant reply. The hook installer (managed-agent-hook-controls-*.js: join(homedir(), configDirName, 'settings.json')) still writes only to ~/.claude/settings.json. The transcript in $CLAUDE_CONFIG_DIR/projects/… contains the reply, but last-status.json never gets an entry for the session.

    So even when the process env is correct, the hook install path still ignores CLAUDE_CONFIG_DIR.

    Why this may be missed in testing: a Claude session whose cwd is $HOME does work. Claude Code loads ~/.claude/settings.json as that directory's project settings, so Orca's hooks fire anyway. Only sessions in other directories break.

    The workaround matches the one above: copy hooks and statusLine from ~/.claude/settings.json into $CLAUDE_CONFIG_DIR/settings.json, then start a new session.

  9. m-stadt commented on Sep 28, 2026

    @m-stadt

    Adding a concrete Linux reproduction of the multi-account point ("switching is global — it re-materializes shared ~/.claude"), since here it causes a visible auth conflict rather than just missing sessions.

    Setup — Ubuntu 24.04, GNOME/Wayland, Orca 1.4.215, Claude Code 2.1.283

    • ~/.claude(default): API-key instance for a company LLM gateway. It has to stay on the default dir because the gateway's provisioning scripts write ~/.claude/settings.json.
    • ~/.claude-work (selected by a wrapper script that sets CLAUDE_CONFIG_DIR): claude.ai Team subscription.
    • Orca → Claude → Accounts: the subscription account added and set Active, for status-bar usage tracking.

    What happens

    • Orca writes the active account's credentials into the default dir. From fatrace -f W:
      orca-ide(41857): CWO /home/<user>/.claude/.credentials.json
    • The content is identical to Orca's stored copy in ~/.config/orca/claude-accounts/<id>/auth/.credentials.json, and that copy is never refreshed. Its expiresAt was about 9 days in the past when Orca wrote it.
    • Claude Code in the default dir then sees an API key and a subscription and shows the auth-conflict warning. /logout clears it until Orca re-applies the account.
    • Selecting System default stops the writes. But usage tracking then reads ~/.claude, the API-key instance, so the subscription account can't be tracked at all.

    Requests

    1. Let an added account target a config directory — for example, bind it to a CLAUDE_CONFIG_DIR path instead of materializing into ~/.claude — or support read-only usage tracking that never writes credentials.
    2. Never write an expired token into a config dir: refresh the stored copy first, or skip the write.
    3. Document that account switching overwrites ~/.claude/.credentials.json, so users with two identities know to stay on System default.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workinghas_reproConclusive reproduction exists (test/script/CLI evidence). See docs/bug-reproductions/os:macosIssues affecting macOS

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions