feat: add open-shell-in-worktree action - #319
Conversation
Adds an "Open shell in this directory" action (key: O) to the Session Actions menu, spawning the user's $SHELL in the worktree via the existing PTY session pipeline. Introduces a 'shell' detection strategy whose detector always reports idle, since a plain shell has no agent output to parse. Closes kbwo#229
kbwo
left a comment
There was a problem hiding this comment.
Note
This review was drafted by Claude Code (model: Opus 5). I (@kbwo) have gone through it myself and verified the content before posting.
Thanks for the PR! Reusing the existing PTY session pipeline instead of introducing a second PTY per session is the right call, and isPrimaryCommand: false is a good catch — it keeps the exit-code-1 fallback retry from re-spawning a shell that the user deliberately exited. Two things I would like changed before merging.
1. O for "Open" is too generic a mnemonic — it will collide with the next "open X" action
The shortcut is keyed on the verb ("Open") rather than on the thing being opened:
src/components/SessionActions.tsx#L15—{label: 'O Open shell in this directory', value: 'openShell'}src/components/SessionActions.tsx#L35-L37—case 'o':src/components/SessionActions.tsx#L59— theS/O/R/Xhint line
The problem is that "open" is not a distinguishing property of this action. The originating issue #229 already asks for a second one in the same breath — "there could be a shortcut to spawn your default IDE in that dir as well" — and "Open in file manager" is an equally natural follow-up. All of them are equally "O", so whichever one lands first squats on the key and the rest have to settle for arbitrary leftovers. That is a menu that gets worse as it grows.
The existing keys in this menu already avoid that trap by keying on the noun rather than the verb: S = Session, R = Rename. Could you pick a key on the same basis and propose it here? I do not have a specific one in mind — I would rather leave the choice to you, as long as it identifies what is being opened rather than the act of opening.
Whichever key you land on, please update the label, the switch case, and the S/O/R/X hint line together.
2. The default shell is defined twice, with two different values
This PR introduces two independent answers to "what is the user's shell?":
src/services/sessionManager.ts#L581—const shell = process.env['SHELL'] ?? '/bin/sh';src/components/ConfigureCommand.tsx#L54—shell: 'bash',
The second one is not dead config: DEFAULT_COMMANDS is read at ConfigureCommand.tsx#L315 to prefill the command field when a user picks a detection strategy while adding a preset. So a fish or zsh user gets their real shell via the new action, but a hardcoded bash when they build a preset around the same new shell strategy — same concept, two answers, and the divergence is invisible until someone hits it.
Could you extract a single resolver — e.g. resolveDefaultShell() in src/utils/ — and have both call sites reference it? That also gives one place to fix two robustness gaps that the current duplication hides:
??only falls back onnull/undefined, so aSHELL=''environment (common in cron and some container images) reachesspawn('').||is what you want here./bin/shdoes not exist on Windows, andSHELLis normally unset there, so this action can never work on Windows. The repo does carry Windows handling elsewhere (src/utils/terminalCapabilities.ts#L27) and documents Windows config paths (docs/command-config.md#L70), so aprocess.env.COMSPECbranch forwin32belongs in that helper. CI only runsubuntu-latest(ci.yml#L12), so this will not surface on its own.
What
Adds an "Open shell in this directory" action (key
O) to the Session Actions menu, so you can open a plain shell in a worktree's directory without leaving ccmanager.Closes #229.
How
A shell is just another command spawned into the worktree, so it reuses the existing PTY session pipeline rather than introducing a second PTY per session (the approach explored in the earlier #22):
SessionManager.createShellSession(worktreePath)spawns$SHELL(falling back to/bin/sh) with no args via the samespawn()used for agent sessions, as a non-primary session that coexists with any agent session in the same worktree.'openShell'entry inSessionActions(menu item +Okey), routed through a newcaseinApp.tsx'shandleSessionActionthat navigates into the normal session view — same flow asnewSession.'shell'state-detection strategy whose detector always reportsidle, since a plain shell has no agent output to parse (avoids false busy/waiting icons in the worktree list).Tests
shell.test.ts(4 cases) covering always-idle behaviour, empty terminal, agent-like output, and zero background/team counts.bun run lint,bun run typecheck, and the fullbun run testsuite (1792 passing) all green.Design note
Because
StateDetectionStrategyis enforced viaRecord<StateDetectionStrategy, …>inConfigureCommand.tsx, adding theshellstrategy also surfaces it in the preset detection-strategy picker (labelled "Shell (no agent detection)"). That's harmless and arguably useful for making a shell preset, but if you'd prefer to keep it out of the agent-strategy picker I'm happy to hide it there in a follow-up.