Skip to content

[Bug]: Per-account Claude folder created with no links to ~/.claude for one account — plugins missing and session history stored outside ~/.claude/projects #33170

Closed

Description

@soonJ817

What happened

After Orca auto-updated from 1.4.226 to 1.4.232 (macOS, arm64), Orca created per-account Claude folders under ~/Library/Application Support/orca/claude-profiles/<account id>/home and launched Claude with CLAUDE_CONFIG_DIR pointing there.

We have five Claude accounts in Orca. For four of them, the account home folder contained 13 symlinks back to ~/.claude (projects, session-env, file-history, shell-snapshots, todos, paste-cache, tasks, plans, transcripts, sessions, history.jsonl, plugins, skills).

For one account — the most recently added one (added in Orca the day before the update) — the home folder was created about 28 minutes after the others with zero symlinks. Orca's logs did not show why.

Impact

Every Claude session that ran under that account:

  1. Had no plugins: /mcp listed only two servers. Plugin-provided MCP servers (Linear, GitHub, Notion, Slack, Figma, context7) were missing. claude --debug-file showed Plugin "linear" not cached … plugin-cache-miss.
  2. Wrote its session history inside the account folder instead of ~/.claude/projects. Tools that read ~/.claude/projects could not see those sessions, and ListAgents did not list them, so other sessions could not message them.

We had ten sessions affected over about 80 minutes. We downgraded to 1.4.224 (the last release without per-account folders) and merged the stranded history back into ~/.claude/projects. The per-account history continued the original session files (no overlapping uuids; parentUuid chains continue from the original file), so appending to the original files restored them.

Expected

Every per-account home gets the same shared links, including accounts added shortly before the per-account feature first runs. If the links cannot be created, Orca should report it rather than launching Claude in an empty config dir.

Versions

Notes

We do not know the root cause. The only difference we could see was that the account was the newest one and its folder was provisioned later than the others.

Activity

  1. eatmoreduck commented on Oct 24, 2026

    @eatmoreduck
    Contributor

    Dug through the profile-setup chain on v1.4.232 to map how an account folder can come to exist with zero symlinks and no log line. The code has a designed-in window that matches your timeline exactly, plus a reason it would never self-heal. Three checks on your machine can confirm or kill it.

    The window. Setup is the only thing that creates the 13 links, and it runs in two shapes: awaited in prepareLaunch when the marker (claude-profiles//profile.json — one level ABOVE home) is missing, and in the background from publish()/terminalEnv once the login-shell env has resolved. But terminalEnv deliberately returns pointer-only env BEFORE envResolved, and publish() writes the account home into the pointer file before any setup has run — its own comment says: "Why even a missing or covered folder: setup creates it without a login, so a sign-in, or Claude's own first run, signs in there." So the design explicitly lets Claude land in a folder Orca has not set up yet: a pane opened early after the update (env not yet resolved), with the pointer already naming your fifth account, launches claude → the shell function reads the pointer → CLAUDE_CONFIG_DIR= → Claude creates the folder itself on first run. That produces exactly your observation: home created ~28 min after the others (first use of that account, not the migration wave), zero symlinks (Orca's setup never ran), and zero Orca log lines (Orca never touched it).

    Why it never heals. Background setup only fires for routedProfile() — the globally routed account. The marker-based awaited setup only runs in prepareLaunch's launch preparation. So if the fifth account never becomes the routed profile in a session where envResolved is true (e.g. you use it through panes that inherited the early pointer, while another account is the saved selection), setup never executes for it, the marker stays absent, and Claude keeps writing real directories (your impact #2 — sessions inside the account folder instead of ~/.claude/projects — is the fingerprint: on the four good accounts projects is a symlink; on the broken one it should be a REAL directory).

    Three checks that would confirm:

    1. Does claude-profiles//profile.json exist? (Prediction: absent for the fifth, present for the other four.)
    2. Inside the fifth's home, is projects a real directory with session data (not a symlink)? Same for sessions.
    3. Grep your Orca logs across the whole period (not just around creation) for "Account setup was incomplete" and "Account setup failed" — prediction: nothing for the fifth account, i.e. setup never even attempted-and-failed.

    If that holds, the fix shape is at the routing layer, not the link layer: the pointer file (or the claude shell function) should treat a marker-less home as "not ready" — either block the pointer write until setup completes, or have the shell function fall back to the user's own config dir when the pointed-at folder has no marker. Happy to build the failing test and PR once the checks confirm which side of the race your machine hit.

  2. nwparker commented on Oct 25, 2026

    @nwparker
    Contributor

    #33479 (merged) fixes one cause we could reproduce: if Claude runs in a brand-new account before Orca's background setup finishes, Claude creates its own plugins/ folder and setup used to leave it unlinked, so plugins showed as "not cached". New accounts now merge and link it on first setup. Accounts that already hit this don't heal automatically: delete claude-profiles/<id>/home/plugins and start Claude again. We couldn't reproduce the state where no shared folders were linked at all on current main. If you still see that on the next build, could you share your Orca version and OS, and a listing of that account's home folder (names only, ls -la)?

  3. eatmoreduck commented on Oct 27, 2026

    @eatmoreduck
    Contributor

    The reporter has been quiet for 48h, so I kept digging on the window my analysis left open — the part #33479 doesn't cover (it fixes the healing side: first setup now adopts folders Claude made; it does not change when setup gets a chance to run). While scoping a fix I hit what looks like a real design tension that's worth a maintainer call before anyone builds the "treat a marker-less home as not ready" variant:

    An unconditional marker gate in the claude shell function would break the deliberate flow it currently implements: the function creates a missing home on purpose (mkdir -p -m 700, header comment "Create account homes without a login silently") so that a newly added account's first run signs in inside that account's folder before setup has completed — and #33479's first-setup merge exists precisely to adopt what Claude writes there. Gating on the marker would send that first run to the user's own config dir instead, i.e. the login lands in the wrong place. So the naive routing-layer gate is not actually safe.

    The narrower window my analysis named — pointer already naming an account whose setup never fires because background setup only runs for the routed (saved-selection) profile, while the pane's pointer can name a different account — still seems open, and it doesn't need the shell function to change. Two candidates that leave the first-run design intact:

    • Make background setup also cover the account a live pointer names when it differs from the saved selection and has no marker (the pointer file already tells us which home needs adopting), or
    • Have publish() fire the one-shot background setup for an account it is about to point at that has no marker — the same adoption fix(claude): share folders Claude made in an account folder before its first setup #33479 performs at first setup, just triggered at pointer-write time instead of waiting for a launch.

    Both stay inside the existing marker semantics ("missing marker = setup hasn't run") and neither touches the shell function bytes. Happy to build the failing test and PR for whichever direction you prefer — the repro harness in claude-shell-function.test.ts already runs real shells, so the no-setup-fires scenario can be expressed there or next to publish()'s tests. If the reporter's three checks ever come back, they'd confirm which account hit this window, but the window itself is code-visible either way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions