Skip to content

fix(accounts): leave a user's node or python program alone after a Claude account switch - #30328

Open
adifsgaid wants to merge 4 commits into
stablyai:mainfrom
adifsgaid:fix/claude-switch-leaves-user-programs
Open

adifsgaid wants to merge 4 commits into
stablyai:mainfrom
adifsgaid:fix/claude-switch-leaves-user-programs

Conversation

@adifsgaid

@adifsgaid adifsgaid commented Oct 18, 2026 •

Copy link
Copy Markdown

ELI5

After you /exit Claude in a terminal, Orca still remembers that a Claude session finished there. If you then start something else in that terminal, like npm run dev, the next Claude account switch treats it as a Claude session. The dialog counts it, and Restart stops your program and runs claude --resume in its place. With "Always restart" that happens without asking.

What Changed

Before: any node or python process in a pane whose Claude had finished counted as Claude.

After: only a process Orca recognizes as Claude counts. Anything else in that pane is left alone.

Real Claude sessions still count:

  • Current Claude Code, native or from npm (the npm package ships bin/claude.exe now), reads as claude by name.
  • An older npm install shows up as node, so it now goes through the fresh process read that isClaudeInForeground already does for names it doesn't recognize. The native macOS install already takes that path, since it reports its version folder (2.1.x). npm's symlink shim (node /usr/local/bin/claude) was already recognized there. A pnpm or yarn shim, npx, or npm's Windows shim runs the package's cli.js directly, which nothing named Claude, so the second commit adds that exact path to the entrypoint table next to Pi's and Prime Agent's.

Why

Counting node and python as Claude is a guess, and a wrong guess here kills the user's program. A miss only means one session isn't offered a restart. The fresh read is already there and already identifies a real Claude, so dropping the guess doesn't lose real sessions.

I considered keeping the wrapper names but only in tabs that launched Claude. That still restarts a dev server started in such a tab, which is exactly this case.

Linked Issue

Fixes #30327

Visual Proof

N/A: no UI change. The dialog just stops counting panes that aren't running Claude.

Testing

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

  • claude-session-account-move.test.ts: a node and a python3 program in panes whose Claude finished are not offered, and an older npm Claude (node in the cached read, claude in the fresh one) still is, through the fresh read. Both fail on the old code.

  • agent-process-recognition.test.ts: Claude Code's cli.js path is recognized on macOS, Linux and Windows paths, and lookalikes or a plain argument are not.

Ran the shared, providers, daemon and agent-hooks suites and the Claude and Codex renderer suites (19,334 passed), both typechecks, oxlint, oxfmt and the changed-code gate, on macOS. Two tests in repro-13767-shell-ready-marker-lost-to-exec.test.ts time out on this machine, the same on untouched main.

Review

cc @Jinwoo-H, a follow-up to #29898. I offered on #29802 to check a few edge cases from its review against #29898, and this is the one that applied. One smaller thing came up too, left out of this PR:

  • If a pane unmounts in the frame between the restart starting and its deferred connect running (a background worktree getting cold-parked, say), the queued restart is already consumed and the pane comes back as a plain shell. Rare, but the Claude session is lost from the pane.

A second one, Claude sessions in a WSL project never being offered, is fixed on main by #31945.

One I couldn't check: since #31945, WSL panes on a Windows host go by the guest's hook evidence instead of a process read. Reading the code, a WSL pane where Claude exited and the user then started their own program may still be offered, since starting the command clears the shell mark and the pane's Claude session record is kept. This PR doesn't change that path, and I can't run WSL here to confirm it.

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Ensure no issues in: Security, Cross-platoform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance

  • Cross-platform: the fresh read names wrapper-launched agents from their command line on macOS and Linux, and from descendant command lines on Windows.
  • Remote/SSH: those panes were already excluded before this check. Mobile: no change.
  • Performance: one fresh process read per pane whose cached name is node or python, only when an account switch happens.
  • Known limit, same as Pi's entry: the command-line scanner skips a bare script name, so node server.js <path to Claude Code's cli.js> would read as Claude. That needs a program handed Claude Code's own cli.js as an argument, and changing the shared scanner would touch every agent's detection, so I left it.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…aude account switch

After /exit, an idle Claude's done row stays on the pane, and the pane can then run the user's own
program. Any bare node or python process counted as Claude, so a switch offered to restart a dev
server as `claude --resume`, and Always restart did it without asking. Only a process recognized as
Claude counts now; the fresh read still names an npm-installed Claude from its command line.
Before it shipped a native binary, Claude Code's npm package ran a generic cli.js, and a pnpm or yarn
shim, npx, or npm's Windows shim launches it by that path. Nothing named it Claude, so with the bare
node wrapper no longer counted, an account switch would stop offering those sessions. The exact
package path now identifies it, as it already does for Pi and Prime Agent.
@coderabbitai

coderabbitai Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Walkthrough

The change adds exact recognition for Claude Code’s npm CLI entrypoint, including POSIX and Windows paths. Account-move eligibility now requires the process to be recognized as Claude instead of accepting foreground wrapper processes. Added tests cover fresh process confirmation for node, python3, and Claude.

Priority: Normal

Severity of issue fixed: Medium

Merge Risk: Low · up to c543d

An unrelated Node program can be mistaken for Claude when its arguments contain Claude CLI’s path, allowing account switching to queue it for a Claude restart. The trigger is narrow, but should be corrected.

Pre-merge checks: Passed 5
Check name Status Explanation
Linked Issues check Passed The change satisfies #30327. isClaudeProcess now accepts only a process recognized as Claude. It no longer accepts any bare node or python wrapper. The existing fresh process check still identif…
Out of Scope Changes check Passed The diff stays within #30327. The source change prevents false Claude-session matches. The entrypoint identity supports valid Claude detection. The added tests cover the issue behavior and detection e…
Docstring Coverage Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 4 files.
Title check Passed The title clearly describes the main fix: preventing account switches from treating unrelated node or Python programs as Claude sessions.
Description check Passed The description follows the repository template closely. It has an ELI5, a What Changed section with before and after behavior, a Why section with alternatives considered, a Linked Issue (Fixes #30327…
Full details: Linked Issues check

Explanation

The change satisfies #30327. isClaudeProcess now accepts only a process recognized as Claude. It no longer accepts any bare node or python wrapper. The existing fresh process check still identifies an older npm Claude. New tests verify that user node and python3 programs are not movable and that an older npm Claude remains movable. The new exact cli.js identity supports Claude Code npm entrypoints used by pnpm, yarn, npx, and Windows shims.

Full details: Out of Scope Changes check

Explanation

The diff stays within #30327. The source change prevents false Claude-session matches. The entrypoint identity supports valid Claude detection. The added tests cover the issue behavior and detection edge cases. No unrelated product behavior or unrelated files are changed.

Full details: Description check

Explanation

The description follows the repository template closely. It has an ELI5, a What Changed section with before and after behavior, a Why section with alternatives considered, a Linked Issue (Fixes #30327), a Visual Proof marked N/A with reason, a Testing section listing the added tests and the commands run, a Review section with follow-up notes, the upstream boundary checkbox, Notes covering cross-platform, SSH, mobile, performance, and a Checklist. The AI Disclosure section was omitted, but the template marks it as optional for team members and the author does not say whether an AI tool was used. Some checklist items are unchecked, including the lint, typecheck, test, and build item, though the Testing section reports those commands were run locally on macOS. The description is thorough and mostly complete.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 458363c0-1f74-4363-a988-726bf8177591
📥 Commits

Reviewing files that changed from the base of the PR and between aa07d58 and c543d6f.

📒 Files selected for processing: 4
  • src/renderer/src/lib/claude-session-account-move.test.ts
  • src/renderer/src/lib/claude-session-account-move.ts
  • src/shared/agent-node-entrypoint-identities.ts
  • src/shared/agent-process-recognition.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

Comment on lines +17 to +23
// Why: Claude Code's npm package ran a generic cli.js before it shipped a native binary, and a
// pnpm or yarn shim or npx still launches it by that path.
{
pattern: /(?:^|\/)node_modules\/@anthropic-ai\/claude-code\/cli\.js$/,
agent: 'claude',
processName: 'claude'
},

@coderabbitai coderabbitai Bot Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

rg -n "EXACT_NODE_ENTRYPOINT_IDENTITIES|recognizeAgentProcess|isClaudeProcess|claude-session-account-move" src/shared src/renderer/src/lib
sed -n '1,100p' src/shared/agent-node-entrypoint-identities.ts
sed -n '1,120p' src/renderer/src/lib/claude-session-account-move.ts

Repository: stablyai/orca

Length of output: 31163


🏁 Script executed:

printf '%s\n' '--- agent-process-recognition.ts ---'
nl -ba src/shared/agent-process-recognition.ts | sed -n '1,250p'
printf '%s\n' '--- account switch callers ---'
nl -ba src/renderer/src/lib/claude-account-switch-sessions.ts | sed -n '1,240p'
printf '%s\n' '--- relevant tests ---'
nl -ba src/shared/agent-process-recognition.test.ts | sed -n '100,180p'
nl -ba src/shared/agent-process-recognition.test.ts | sed -n '540,595p'
nl -ba src/renderer/src/lib/claude-session-account-move.test.ts | sed -n '1,220p'

Repository: stablyai/orca

Length of output: 34256


🏁 Script executed:

printf '%s\n' '--- command-line entrypoint parser ---'
nl -ba src/shared/agent-command-line-entrypoint.ts | sed -n '1,280p'
printf '%s\n' '--- account-move implementation ---'
nl -ba src/renderer/src/lib/claude-session-account-move.ts | sed -n '120,430p'
printf '%s\n' '--- runtime inspection bindings ---'
rg -n -F --glob '*.ts' -e 'type RuntimeTerminalProcessInspection' -e 'interface RuntimeTerminalProcessInspection' -e 'confirmRuntimeTerminalForegroundProcess' -e 'inspectRuntimeTerminalProcess' src/renderer src/shared

Repository: stablyai/orca

Length of output: 26159


🏁 Script executed:

printf '%s\n' '--- terminal process inspection contract ---'
rg -n -F --glob '*.ts' -e 'export type TerminalProcessInspection' -e 'export interface TerminalProcessInspection' -e 'foregroundProcess:' src
nl -ba src/shared/terminal-process-inspection.ts | sed -n '1,220p'
printf '%s\n' '--- runtime inspection implementation ---'
nl -ba src/renderer/src/runtime/runtime-terminal-inspection.ts | sed -n '1,180p'
printf '%s\n' '--- process inspection producers ---'
rg -n -F --glob '*.ts' -e 'foregroundProcess' -e 'commandLine' src/main src/renderer src/shared | head -160

Repository: stablyai/orca

Length of output: 42214


🏁 Script executed:

printf '%s\n' '--- foreground process selection ---'
nl -ba src/shared/foreground-process-selection.ts | sed -n '1,180p'
printf '%s\n' '--- foreground wrapper identity ---'
nl -ba src/shared/foreground-wrapper-agent.ts | sed -n '1,150p'
printf '%s\n' '--- runtime foreground presence ---'
nl -ba src/main/runtime/runtime-terminal-agent-presence.ts | sed -n '150,235p'
printf '%s\n' '--- command-line recognizer consumers ---'
rg -n -F --glob '*.ts' -e 'recognizeAgentProcessFromCommandLine(' src/shared src/main src/renderer | grep -v 'test.ts' | head -120

Repository: stablyai/orca

Length of output: 14338


🏁 Script executed:

printf '%s\n' '--- local foreground inspection ---'
nl -ba src/main/providers/local-pty-foreground-inspection.ts | sed -n '130,220p'
printf '%s\n' '--- foreground process provider ---'
nl -ba src/main/providers/agent-foreground-process.ts | sed -n '110,180p'
printf '%s\n' '--- runtime inspection host binding ---'
rg -n -F --glob '*.ts' -e 'inspectProcess:' -e 'getForegroundProcess:' -e 'inspectRuntimeTerminalProcess' src/main/runtime src/main/providers src/main/daemon | head -100

Repository: stablyai/orca

Length of output: 18877


🏁 Script executed:

nl -ba src/main/providers/local-pty-foreground-inspection.ts | sed -n '180,285p'
rg -n -F --glob '*.ts' -e 'function resolveAgentForegroundProcessFromPs' -e 'resolveAgentForegroundProcessFromPs' src/main/providers src/main

Repository: stablyai/orca

Length of output: 6140


🏁 Script executed:

nl -ba src/main/providers/agent-foreground-process.ts | sed -n '180,255p'
rg -n -F --glob '*.ts' -e 'function resolveStableForegroundProcess' -e 'resolveStableForegroundProcess' src/main/providers

Repository: stablyai/orca

Length of output: 4105


Stop scanning after the actual Node script token.

Bare server.js is skipped, so a later Claude package path can identify an unrelated Node process as Claude. The foreground resolver returns that recognized claude identity, which can pass the account-move check and queue the pane for a Claude restart.

Suggested fix
-const PROCESS_EXTENSION_RE = /\.(?:exe|cmd|bat|ps1)$/i
+const PROCESS_EXTENSION_RE = /\.(?:js|mjs|cjs|exe|cmd|bat|ps1)$/i

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, and it's not specific to this entry: Pi's and Prime Agent's exact entrypoints behave the same, because the shared scanner skips a bare script name like server.js and takes the next path-looking argument. Hitting it here needs a node program that's handed Claude Code's own cli.js path as an argument. I noted it in the PR description and kept the scanner as is, since changing it touches every agent's detection. Happy to do that as a separate PR if the maintainers want it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@adifsgaid Thanks for clarifying. A separate PR is reasonable because the scanner change affects detection across agents, including Pi and Prime Agent. This remains a known limitation, not a completed fix.

Would you like me to open a follow-up issue for the shared scanner?


✏️ Learnings added
Learnt from: adifsgaid
Repo: stablyai/orca PR: 30328
File: src/shared/agent-node-entrypoint-identities.ts:17-23
Timestamp: 2026-10-19T06:52:36.369Z
Learning: In stablyai/orca, the shared Node entrypoint scanner in src/shared/agent-command-line-entrypoint.ts can skip a bare script token such as server.js and recognize a later path argument as an agent entrypoint. This limitation affects exact entrypoint identities for Claude Code, Pi, and Prime Agent in src/shared/agent-node-entrypoint-identities.ts; it is not specific to Claude Code.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

You are interacting with an AI system.

This branch has not been deployed

No deployments
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 account switch offers to restart a node or python program left in a Claude tab

1 participant