Skip to main content
GitHub Desktop can launch your preferred terminal shell to work with Git repositories from the command line. Shells can be opened from the Repository menu or toolbar.

Requirements

For a shell to be supported by GitHub Desktop, it must meet these criteria:
1

Discoverable

GitHub Desktop must be able to detect if the shell is installed on the user’s machine.
2

Launchable

The shell must be launchable using the operating system’s APIs.
3

Stable interface

The shell must have stable command-line arguments that don’t change between updates.

Supported Shells

Windows

Command Prompt (cmd)
PowerShell
PowerShell Core
These are defined in the shell enumeration:
app/src/lib/shells/win32.ts
export enum Shell {
  Cmd = 'Command Prompt',
  PowerShell = 'PowerShell',
  PowerShellCore = 'PowerShell Core',
  Hyper = 'Hyper',
  GitBash = 'Git Bash',
  Cygwin = 'Cygwin',
  WSL = 'WSL',
  WindowsTerminal = 'Windows Terminal',
  Alacritty = 'Alacritty',
  FluentTerminal = 'Fluent Terminal',
}

macOS

Supported terminal applications on macOS:
  • Terminal (default macOS terminal)
  • iTerm2
  • Hyper
  • PowerShell Core
  • Kitty
  • Alacritty
  • Tabby
  • WezTerm
  • Warp
  • Ghostty
app/src/lib/shells/darwin.ts
export enum Shell {
  Terminal = 'Terminal',
  Hyper = 'Hyper',
  iTerm2 = 'iTerm2',
  PowerShellCore = 'PowerShell Core',
  Kitty = 'Kitty',
  Alacritty = 'Alacritty',
  Tabby = 'Tabby',
  WezTerm = 'WezTerm',
  Warp = 'Warp',
  Ghostty = 'Ghostty',
}

Linux

Linux users can choose from these terminal emulators:
  • GNOME Terminal
  • Ptyxis
  • MATE Terminal
  • Tilix
  • Terminator
  • Rxvt Unicode (urxvt)
  • Konsole (KDE)
  • XTerm
  • Terminology
  • Ghostty
app/src/lib/shells/linux.ts
export enum Shell {
  Gnome = 'GNOME Terminal',
  Ptyxis = 'Ptyxis',
  Mate = 'MATE Terminal',
  Tilix = 'Tilix',
  Terminator = 'Terminator',
  Urxvt = 'URxvt',
  Konsole = 'Konsole',
  Xterm = 'XTerm',
  Terminology = 'Terminology',
  Ghostty = 'Ghostty',
}

Shell Detection

Windows: Registry and File System

On Windows, shells are detected using registry lookups and file system checks:
app/src/lib/shells/win32.ts
async function findGitBash(): Promise<string | null> {
  const registryPath = enumerateValues(
    HKEY.HKEY_LOCAL_MACHINE,
    'SOFTWARE\\GitForWindows'
  )

  if (registryPath.length === 0) {
    return null
  }

  const installPathEntry = registryPath.find(e => e.name === 'InstallPath')
  if (installPathEntry && installPathEntry.type === RegistryValueType.REG_SZ) {
    const path = Path.join(installPathEntry.data, 'git-bash.exe')

    if (await pathExists(path)) {
      return path
    }
  }

  return null
}
Built-in shells like Command Prompt and PowerShell are always available on Windows and don’t require detection.

macOS: Bundle Identifiers

On macOS, shells are discovered using their application bundle identifiers:
app/src/lib/shells/darwin.ts
function getBundleIDs(shell: Shell): ReadonlyArray<string> {
  switch (shell) {
    case Shell.Terminal:
      return ['com.apple.Terminal']
    case Shell.iTerm2:
      return ['com.googlecode.iterm2']
    case Shell.Hyper:
      return ['co.zeit.hyper']
    case Shell.Kitty:
      return ['net.kovidgoyal.kitty']
    case Shell.Alacritty:
      return ['org.alacritty', 'io.alacritty']
    case Shell.WezTerm:
      return ['com.github.wez.wezterm']
    case Shell.Ghostty:
      return ['com.mitchellh.ghostty']
  }
}
The detection process:
app/src/lib/shells/darwin.ts
async function getShellInfo(
  shell: Shell
): Promise<{ path: string; bundleID: string } | null> {
  const bundleIds = getBundleIDs(shell)
  for (const id of bundleIds) {
    try {
      const path = await appPath(id)
      return { path, bundleID: id }
    } catch (error) {
      log.debug(
        `Unable to locate ${shell} installation with bundle id ${id}`,
        error
      )
    }
  }
  return null
}

Linux: Executable Paths

Linux shells are located by checking known executable paths:
app/src/lib/shells/linux.ts
function getShellPath(shell: Shell): Promise<string | null> {
  switch (shell) {
    case Shell.Gnome:
      return getPathIfAvailable('/usr/bin/gnome-terminal')
    case Shell.Mate:
      return getPathIfAvailable('/usr/bin/mate-terminal')
    case Shell.Tilix:
      return getPathIfAvailable('/usr/bin/tilix')
    case Shell.Terminator:
      return getPathIfAvailable('/usr/bin/terminator')
    case Shell.Urxvt:
      return getPathIfAvailable('/usr/bin/urxvt')
    case Shell.Konsole:
      return getPathIfAvailable('/usr/bin/konsole')
    case Shell.Xterm:
      return getPathIfAvailable('/usr/bin/xterm')
  }
}

Launching Shells

Windows Launch Arguments

Each shell requires specific command-line arguments to open in the repository directory:
app/src/lib/shells/win32.ts
export function launch(
  foundShell: FoundShell<Shell>,
  path: string
): ChildProcess {
  const shell = foundShell.shell
  switch (shell) {
    case Shell.GitBash:
      const gitBashPath = `"${foundShell.path}"`
      return spawn(gitBashPath, [`--cd="${path}"`], {
        shell: true,
        cwd: path,
      })

    case Shell.PowerShell:
    case Shell.PowerShellCore:
      return spawn(foundShell.path, ['-NoExit', '-Command', `cd "${path}"`], {
        cwd: path,
      })

    case Shell.Cmd:
      return spawn('cmd.exe', ['/K', `cd /D "${path}"`])

    case Shell.Hyper:
      return spawn(foundShell.path, [`"${path}"`], {
        cwd: path,
      })

    case Shell.WindowsTerminal:
      return spawn('wt.exe', ['-d', `"${path}"`])

    case Shell.Alacritty:
      return spawn(foundShell.path, ['--working-directory', path])
  }
}
Path arguments are properly quoted to handle directories with spaces.

macOS Launch with open

On macOS, shells are launched using the open command with the bundle identifier:
app/src/lib/shells/darwin.ts
export function launch(
  foundShell: FoundShell<Shell>,
  path: string
): ChildProcess {
  return spawn('open', ['-b', foundShell.bundleID, path])
}
This approach allows macOS to:
  • Use the default application for that bundle ID
  • Handle application launching properly
  • Support multiple versions of the same shell

Linux Launch Arguments

Linux shells use various working directory flags:
app/src/lib/shells/linux.ts
export function launch(
  foundShell: FoundShell<Shell>,
  path: string
): ChildProcess {
  const shell = foundShell.shell
  switch (shell) {
    case Shell.Gnome:
    case Shell.Mate:
    case Shell.Tilix:
    case Shell.Terminator:
      return spawn(foundShell.path, ['--working-directory', path])

    case Shell.Urxvt:
      return spawn(foundShell.path, ['-cd', path])

    case Shell.Konsole:
      return spawn(foundShell.path, ['--workdir', path])

    case Shell.Xterm:
      return spawn(foundShell.path, ['-e', '/bin/bash'], { cwd: path })

    case Shell.Terminology:
      return spawn(foundShell.path, ['-d', path])
  }
}

Getting Available Shells

GitHub Desktop detects all installed shells at startup:
export async function getAvailableShells(): Promise<
  ReadonlyArray<FoundShell<Shell>>
> {
  const shells: Array<FoundShell<Shell>> = [
    { shell: Shell.Cmd, path: 'cmd.exe' },
  ]

  const powerShellPath = await findPowerShell()
  if (powerShellPath) {
    shells.push({ shell: Shell.PowerShell, path: powerShellPath })
  }

  const gitBashPath = await findGitBash()
  if (gitBashPath) {
    shells.push({ shell: Shell.GitBash, path: gitBashPath })
  }

  const windowsTerminalPath = await findWindowsTerminal()
  if (windowsTerminalPath) {
    shells.push({ shell: Shell.WindowsTerminal, path: windowsTerminalPath })
  }

  return shells
}

Custom Shell Integration

GitHub Desktop supports custom shell configurations:
app/src/lib/shells/darwin.ts
interface ICustomIntegration {
  path: string           // Path to shell executable
  arguments: string      // Launch arguments
  bundleID?: string      // macOS bundle ID (optional)
}
Custom integrations use argument expansion:
const argv = parseCustomIntegrationArguments(customShell.arguments)
const args = expandTargetPathArgument(argv, repositoryPath)
return spawnCustomIntegration(customShell.path, args)

Shell Parsing

The parse() function converts shell names to enum values:
export function parse(label: string): Shell {
  if (label === Shell.Cmd) return Shell.Cmd
  if (label === Shell.PowerShell) return Shell.PowerShell
  if (label === Shell.GitBash) return Shell.GitBash
  // ...
  return Default // Falls back to Command Prompt
}

Adding a New Shell

To add support for a new shell:
1

Add enum entry

Add a new entry to the Shell enum in the appropriate platform file with a user-friendly name.
2

Implement detection

Windows: Add a finder function that checks registry or file system:
async function findMyShell(): Promise<string | null> {
  // Check registry or known paths
  return shellPath
}
macOS: Add bundle ID to getBundleIDs():
case Shell.MyShell:
  return ['com.example.myshell']
Linux: Add path to getShellPath():
case Shell.MyShell:
  return getPathIfAvailable('/usr/bin/myshell')
3

Update getAvailableShells()

Add your shell to the detection logic in getAvailableShells().
4

Implement launch logic

Add launch arguments to the launch() function:
case Shell.MyShell:
  return spawn(foundShell.path, ['--directory', path])
5

Update parse function

Add parsing logic if needed (usually automatic with parseEnumValue).

Contributing

Want to add support for your favorite shell? Check out the shell integration technical documentation for detailed contribution guidelines.

Build docs developers (and LLMs) love