Shell support
Kangentic spawns agent CLI processes in platform-native shells. This page documents how shells are detected, adapted, and configured across platforms.
Shell detection order
Section titled “Shell detection order”Kangentic auto-detects the user’s shell in the following order:
| Platform | Detection Order |
|---|---|
| Windows | PowerShell 7 (pwsh) > PowerShell 5 (powershell) > Git Bash (bash) > cmd.exe |
| macOS | zsh > bash > fish > nushell (nu) > sh (default: $SHELL or zsh) |
| Linux | bash > zsh > fish > dash > nushell (nu) > ksh > sh (default: $SHELL or bash) |
Override the detected shell in Settings under the Terminal tab, or set terminal.shell in the config file.
Shell-specific adaptations
Section titled “Shell-specific adaptations”Kangentic adapts command execution for each shell:
| Shell | Adaptation |
|---|---|
| PowerShell | Launched with -NoLogo. The typed agent command is prefixed with the & call operator so a quoted executable path runs instead of echoing, on every platform since v0.46.0, because pwsh can be the login shell on macOS and Linux too. A Set-Location -LiteralPath prelude, written raw without that prefix, corrects the working directory when the path contains [ or ], which PowerShell would otherwise treat as wildcards |
| WSL Bash | Spawned as wsl.exe plus its distro arguments. The agent command’s leading executable path is converted to /mnt/c/... format. Invoked with the arguments already written into the shell string, so no --login is added |
| cmd.exe | No --login flag (it has no such concept). It cannot start in a UNC path, so a session whose working directory is one starts in your home directory and gets a pushd "<unc path>" prelude, which maps the share to a temporary drive letter before the command runs |
| fish | No --login flag (fish doesn’t support it). Arguments get fish’s own single-quoted form (see quoteArg()) |
nushell (nu) | No --login flag. A quoted command head gets a ^ prefix, the only way nushell runs one, and arguments are double-quoted |
| bash/zsh/dash/ksh/sh | Standard --login invocation. --login is the fallback, applied to every shell that is not WSL, cmd.exe, PowerShell, fish, or nushell |
Spawn clear prelude
Section titled “Spawn clear prelude”An agent spawn prefixes the command it types with the shell’s own clear: Clear-Host; for the PowerShell family, cls & for cmd.exe, and clear; for everything else (bash, zsh, fish, nu, dash, ksh, sh, WSL, Git Bash). The shell then erases its own startup preamble and the command echo the instant the command runs, so what the terminal, the scrollback ring, the headless parser, replays, and the paired phone all receive is a real clear in the real byte stream rather than a heuristic guess about which startup bytes to strip. That matters because those bytes move: pwsh 7.6 started emitting cursor-hide and erase sequences inside its escape-only startup preamble, which broke every detector keyed on them.
Command Terminals are deliberately exempt. They are your own shell, with no spawn echo to strip and no reason to wipe what you already typed.
npm .cmd shims under PowerShell and Git Bash
Section titled “npm .cmd shims under PowerShell and Git Bash”An agent installed with npm i -g resolves on Windows to its .cmd shim, and a .cmd runs through cmd.exe, whose command line ends at the first newline. Typed into a PowerShell or Git Bash session, a multi-line task prompt reached the agent as its first line and nothing else. Since v0.39.0 the spawn launches the sibling shim npm writes beside every .cmd instead, so the host shell hands the prompt to node directly:
| Host shell | Launches | Condition |
|---|---|---|
PowerShell (pwsh or powershell) | <name>.ps1 | The host’s effective execution policy allows scripts (RemoteSigned, Unrestricted, or Bypass), probed once per shell per app run. Windows PowerShell 5.1 defaults to Restricted and pwsh 7 to RemoteSigned, and the two keep separate policies |
| Git Bash | The extensionless #!/bin/sh shim | The file exists and starts with #! |
| cmd.exe | The .cmd itself | The prompt is already flattened for a cmd host |
| WSL | Unchanged | WSL cannot launch a .cmd at all (see WSL runs the Windows binary), and the sh shim would run a Windows bundle under a Linux node |
With no usable sibling, or under a Restricted or AllSigned policy, the .cmd stays and the prompt is flattened to one line, so the whole title and description still arrive. The cause is logged once per shell; for the policy case the fix is Set-ExecutionPolicy RemoteSigned -Scope CurrentUser in that host, then an app restart. The process tree becomes pwsh launching node directly rather than through cmd.exe.
Detection reads the same shims, and the lookup checks that a shim file exists, never that its target does. On Windows it searches the working directory before PATH, so a shim left in a project root by an uninstalled local package used to shadow the real global install. Detection now walks every match in order and skips a .cmd, .bat, or .ps1 shim whose node_modules target is missing without spending a probe on it.
Path handling
Section titled “Path handling”Cross-platform path handling uses two key utilities:
toForwardSlash()
Section titled “toForwardSlash()”Converts Windows backslashes to forward slashes for consistent path handling across platforms. Used when passing paths to shells and CLI tools.
quoteArg()
Section titled “quoteArg()”Properly escapes and quotes arguments for the target shell: single quotes for POSIX shells (bash, zsh, sh, WSL), double quotes for PowerShell and cmd.exe, and a form of their own for fish and nushell. The target shell is passed explicitly at every spawn call site, so quoting always matches the shell the command is about to run in. A multi-line prompt is kept on one physical input line: newlines stay literal inside the single quotes of POSIX shells and fish, become \n escapes for nushell, and become backtick-n escapes inside PowerShell’s double quotes, which PowerShell expands back into real newlines before the agent sees the argument.
v0.46.0 fixed five ways a character in a prompt or an override could break a spawn, each measured on the real shell:
| Shell | Character | What happened | What Kangentic writes now |
|---|---|---|---|
| nushell | ' | nushell’s single quotes have no escapes, so POSIX’s '\'' was a parse error and every prompt holding an apostrophe failed to start | A double-quoted string, escaping only \ and " |
| fish | \ and ' | fish reads \\ and \' inside single quotes, so the POSIX form lost a backslash and could leave the quote open | Every \ and ' backslash-escaped |
| cmd.exe | % | cmd expands %NAME% even inside double quotes, and %% is a batch-file escape that does not apply at a prompt | Each % as "^%": close the quote, caret-escape, reopen. Also applied to the head of every Windows command that runs a detected agent CLI |
| PowerShell | curly double quotes | PowerShell ends a double-quoted string at U+201C, U+201D, and U+201E too, so a description holding one split the argument, and a ; after it ran as a second command | Each one doubled |
| PowerShell and cmd.exe | " in a model or effort override | An override set from kangentic.json, a board profile, or an MCP call could hold a quote and a separator and run a second command | The quote written as ', the trade the prompt builders already make, because no escaped quote is safe on all three Windows shells |
Aider spawns under PowerShell work again: Aider’s notification command carried quotes the outer shell could not hold. Under PowerShell or cmd.exe on Windows, a path that is not one bare word now drops the notification flags, and Aider’s prompt regex stays the idle signal.
convertWindowsExePath()
Section titled “convertWindowsExePath()”Rewrites the leading executable path of a command into POSIX form for unix-like shells. It recognizes all three quote forms a command builder can emit - a bare path, a double-quoted path, and the single-quoted path quoteArg produces for unix-like shells - plus UNC paths (\\server\share\...). A single-quoted token stays single-quoted so shell-active characters in a legal Windows path remain inert. Before this handled the single-quoted case, selecting Git Bash or WSL as the terminal shell broke every agent spawn with “command not found”.
Git Bash paths
Section titled “Git Bash paths”When running under Git Bash on Windows, paths like /c/Users/... are used instead of C:\Users\.... Kangentic detects Git Bash and adjusts path formatting accordingly.
WSL paths
Section titled “WSL paths”For WSL shells, Windows paths are converted using the /mnt/ prefix:
C:\Users\project>/mnt/c/Users/project
WSL support
Section titled “WSL support”Kangentic detects WSL shells and applies special handling:
- Detection:
wsl --list --quiet, with a 5-second timeout - Docker filtering: WSL Docker Desktop distributions (distros whose name starts with
docker-) are filtered out of the shell list - Spawn: the stored shell spec (
wsl -d Ubuntu) is split into an executable and its arguments, and the executable is spawned aswsl.exe. The extension is deliberate: node-pty’s ConPTY executable search cannot resolve the bare namewsl, and a session spawned that way exits immediately with no output - Path conversion: the agent command’s leading executable path is converted to
/mnt/c/.... The command itself is written into the PTY after the shell starts, never passed in argv
WSL runs the Windows binary
Section titled “WSL runs the Windows binary”Converting the CLI path to /mnt/c/... launches the Windows agent binary through WSL’s interop layer, which can only execute PE .exe files. Two consequences follow, both documented limitations rather than bugs:
- npm-installed
.cmd/.batshims cannot be launched from inside WSL. Git Bash can run them; WSL interop cannot. On WSL you need the agent’s native.exeinstall. - A CLI installed inside the distro is not discovered. Detection resolves on the Windows host, identically for every shell. Preferring a distro-native binary would also mean converting every path argument the spawn passes (
--settings,--mcp-configare Windows paths, correct for a Windows binary and wrong for a Linux one), so it is a possible future feature, not current behavior.
Environment stripping
Section titled “Environment stripping”When spawning an agent, Kangentic strips the parent Claude Code session’s markers from the child environment: CLAUDECODE and every CLAUDE_CODE_* variable (entrypoint, session ID, execpath, and the nested-session flag). This keeps a Kangentic-spawned agent a clean top-level session. Without it, a nested Claude Code child would refuse to start or inherit the parent’s session identity and lose its own transcript on resume. The stripping is harmless for non-Claude agents, which ignore these variables, and ANTHROPIC_* keys (BYOK / API auth) are left untouched.
Two CLAUDE_CODE_* keys survive the strip, both renderer tuning flags rather than session-identity markers:
| Key | Behavior |
|---|---|
CLAUDE_CODE_ALT_SCREEN_FULL_REPAINT | Kept, and on Windows defaulted to 1 when you have not set it yourself, working around an upstream alternate-screen repaint defect. Set it yourself to override that default |
CLAUDE_CODE_SCROLL_SPEED | Kept so your own exported value reaches the agent, but deliberately never defaulted. A default of 3 was shipped and reverted the same day: wheel reports coalesce into single large jumps, and a 3x multiplier pushed those past the threshold where the fullscreen TUI’s differential renderer mis-assembles frames. The CLI’s own default of 1 matches the native terminals verified clean |
Two more adjustments happen at the same chokepoint, both so agent TUIs render in color:
TERMis defaulted toxterm-256colorwhen the merged environment has none, treating an empty string as absent the way capability detectors do. This only ever fills a gap: an inherited or caller-suppliedTERMpasses through untouched. It matters on Windows specifically, because node-pty turns its spawnnameoption into the child’sTERMonly on POSIX, so a PowerShell-launched Kangentic previously gave its children noTERMat all and CLIs that key color off it (Antigravity’sagyis the clear case) rendered monochrome.NO_COLORis stripped, but only when the merged environment also carriesCLAUDECODE. Claude Code exportsNO_COLOR=1into its tool shells, so a Kangentic launched from inside a Claude Code session was force-dimming every color-capable CLI in every agent terminal. ANO_COLORwithout that parentage marker is a deliberate preference and survives, as does one set per spawn by a caller.
AUMID handling (Windows)
Section titled “AUMID handling (Windows)”On Windows, Kangentic sets the Application User Model ID (AUMID) for proper taskbar grouping and notification association:
| Context | AUMID |
|---|---|
| Development | com.kangentic.dev |
| Production | com.kangentic.app |
This keeps development and production instances on separate taskbar entries and notification channels.
macOS title bar
Section titled “macOS title bar”On macOS, the custom title bar positions the traffic light buttons (close, minimize, maximize) with specific offsets to align with the app’s header layout. The position is defined in the main process window configuration.
See also
Section titled “See also”- Terminal and Activity tab - the user-facing terminal that uses these shells
- Contributing - shell-related development setup
- Architecture - process model and PTY layer
Kangentic is free and open source. A star helps other people find it.
Star on GitHub