Feature request: built-in shell tab-completion for CLI flags and subcommands

Resolved 💬 3 comments Opened Mar 29, 2026 by DigitalCyberSoft Closed Apr 1, 2026

Summary

Claude Code CLI has a rich set of flags and deeply nested subcommands (mcp add, plugin marketplace add, auth login, auto-mode critique, etc.) but no shell tab-completion support. Users have to reference --help to discover and type options correctly.

Proposed solution

Ship bash and zsh completion scripts that can be installed via a setup command (e.g. claude completions bash / claude completions zsh) or bundled with the package.

I've written working completion scripts for both shells that cover:

  • All top-level flags with value completion (--effort, --model, --permission-mode, --output-format, --input-format, --scope, --transport, etc.)
  • All subcommands: mcp, auth, auto-mode, agents, install, plugin/plugins, update, doctor, setup-token
  • Nested subcommands: mcp add, mcp serve, mcp add-json, auth login, auth status, auto-mode critique, plugin marketplace add, plugin install, plugin uninstall, etc.
  • File path completion where appropriate (--debug-file, --mcp-config, --settings, --plugin-dir, plugin validate)
  • Short flag aliases (-c, -d, -p, -r, -v, -w, -n, -s, -t, -e, -H, -a)

Bash completion (claude.bash)

<details>
<summary>Click to expand</summary>

# Bash completion for Claude Code CLI
# Source this file in your .bashrc:
#   source ~/.claude/completions/claude.bash

_claude_completions() {
    local cur prev words cword
    _init_completion || return

    # Build the command chain (skip the program name)
    local cmd_chain=""
    local i
    for (( i=1; i < cword; i++ )); do
        case "${words[i]}" in
            -*) ;; # skip flags
            *)  cmd_chain="${cmd_chain:+${cmd_chain} }${words[i]}" ;;
        esac
    done

    # Top-level options
    local top_opts="--add-dir --agent --agents --allow-dangerously-skip-permissions --allowedTools --allowed-tools --append-system-prompt --bare --betas --brief --chrome -c --continue --dangerously-skip-permissions -d --debug --debug-file --disable-slash-commands --disallowedTools --disallowed-tools --effort --fallback-model --file --fork-session --from-pr -h --help --ide --include-partial-messages --input-format --json-schema --max-budget-usd --mcp-config --mcp-debug --model -n --name --no-chrome --no-session-persistence --output-format --permission-mode --plugin-dir -p --print --replay-user-messages -r --resume --session-id --setting-sources --settings --strict-mcp-config --system-prompt --tmux --tools --verbose -v --version -w --worktree"

    # Top-level subcommands
    local top_cmds="agents auth auto-mode doctor install mcp plugin plugins setup-token update upgrade"

    case "$cmd_chain" in
        "mcp add")
            COMPREPLY=( $(compgen -W "--callback-port --client-id --client-secret -e --env -H --header -h --help -s --scope -t --transport" -- "$cur") )
            return
            ;;
        "mcp add-json")
            COMPREPLY=( $(compgen -W "--client-secret -h --help -s --scope" -- "$cur") )
            return
            ;;
        "mcp add-from-claude-desktop")
            COMPREPLY=( $(compgen -W "-h --help -s --scope" -- "$cur") )
            return
            ;;
        "mcp remove")
            COMPREPLY=( $(compgen -W "-h --help -s --scope" -- "$cur") )
            return
            ;;
        "mcp serve")
            COMPREPLY=( $(compgen -W "-d --debug -h --help --verbose" -- "$cur") )
            return
            ;;
        mcp*)
            COMPREPLY=( $(compgen -W "add add-from-claude-desktop add-json get help list remove reset-project-choices serve -h --help" -- "$cur") )
            return
            ;;
        "auth login")
            COMPREPLY=( $(compgen -W "--claudeai --console --email -h --help --sso" -- "$cur") )
            return
            ;;
        "auth status")
            COMPREPLY=( $(compgen -W "-h --help --json --text" -- "$cur") )
            return
            ;;
        "auth logout")
            COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
            return
            ;;
        auth*)
            COMPREPLY=( $(compgen -W "help login logout status -h --help" -- "$cur") )
            return
            ;;
        "auto-mode critique")
            COMPREPLY=( $(compgen -W "-h --help --model" -- "$cur") )
            return
            ;;
        "auto-mode config"|"auto-mode defaults")
            COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
            return
            ;;
        auto-mode*)
            COMPREPLY=( $(compgen -W "config critique defaults help -h --help" -- "$cur") )
            return
            ;;
        install*)
            COMPREPLY=( $(compgen -W "--force -h --help stable latest" -- "$cur") )
            return
            ;;
        agents*)
            COMPREPLY=( $(compgen -W "-h --help --setting-sources" -- "$cur") )
            return
            ;;
        "plugin marketplace add"|"plugins marketplace add")
            COMPREPLY=( $(compgen -W "-h --help --scope --sparse" -- "$cur") )
            return
            ;;
        "plugin marketplace list"|"plugins marketplace list")
            COMPREPLY=( $(compgen -W "-h --help --json" -- "$cur") )
            return
            ;;
        "plugin marketplace remove"|"plugins marketplace remove"|"plugin marketplace rm"|"plugins marketplace rm")
            COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
            return
            ;;
        "plugin marketplace update"|"plugins marketplace update")
            COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
            return
            ;;
        "plugin marketplace"|"plugins marketplace")
            COMPREPLY=( $(compgen -W "add help list remove rm update -h --help" -- "$cur") )
            return
            ;;
        "plugin install"|"plugins install"|"plugin i"|"plugins i")
            COMPREPLY=( $(compgen -W "-h --help -s --scope" -- "$cur") )
            return
            ;;
        "plugin uninstall"|"plugins uninstall"|"plugin remove"|"plugins remove")
            COMPREPLY=( $(compgen -W "-h --help --keep-data -s --scope" -- "$cur") )
            return
            ;;
        "plugin list"|"plugins list")
            COMPREPLY=( $(compgen -W "--available -h --help --json" -- "$cur") )
            return
            ;;
        "plugin disable"|"plugins disable")
            COMPREPLY=( $(compgen -W "-a --all -h --help -s --scope" -- "$cur") )
            return
            ;;
        "plugin enable"|"plugins enable")
            COMPREPLY=( $(compgen -W "-h --help -s --scope" -- "$cur") )
            return
            ;;
        "plugin update"|"plugins update")
            COMPREPLY=( $(compgen -W "-h --help -s --scope" -- "$cur") )
            return
            ;;
        "plugin validate"|"plugins validate")
            COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
            return
            ;;
        plugin*|plugins*)
            COMPREPLY=( $(compgen -W "disable enable help install i list marketplace uninstall remove update validate -h --help" -- "$cur") )
            return
            ;;
        update*|upgrade*)
            COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
            return
            ;;
        doctor*|setup-token*)
            COMPREPLY=( $(compgen -W "-h --help" -- "$cur") )
            return
            ;;
    esac

    # Handle option arguments that expect specific values
    case "$prev" in
        --effort)
            COMPREPLY=( $(compgen -W "low medium high max" -- "$cur") )
            return
            ;;
        --input-format)
            COMPREPLY=( $(compgen -W "text stream-json" -- "$cur") )
            return
            ;;
        --output-format)
            COMPREPLY=( $(compgen -W "text json stream-json" -- "$cur") )
            return
            ;;
        --permission-mode)
            COMPREPLY=( $(compgen -W "acceptEdits bypassPermissions default dontAsk plan auto" -- "$cur") )
            return
            ;;
        --model|--fallback-model)
            COMPREPLY=( $(compgen -W "sonnet opus haiku claude-opus-4-6 claude-sonnet-4-6 claude-haiku-4-5-20251001" -- "$cur") )
            return
            ;;
        --scope|-s)
            COMPREPLY=( $(compgen -W "local user project" -- "$cur") )
            return
            ;;
        --transport|-t)
            COMPREPLY=( $(compgen -W "stdio sse http" -- "$cur") )
            return
            ;;
        --setting-sources)
            COMPREPLY=( $(compgen -W "user project local" -- "$cur") )
            return
            ;;
        --debug-file|--mcp-config|--settings|--plugin-dir)
            _filedir
            return
            ;;
    esac

    # If current word starts with -, complete options; otherwise complete subcommands
    if [[ "$cur" == -* ]]; then
        COMPREPLY=( $(compgen -W "$top_opts" -- "$cur") )
    else
        COMPREPLY=( $(compgen -W "$top_cmds" -- "$cur") )
    fi
}

complete -F _claude_completions claude

</details>

Zsh completion (_claude)

<details>
<summary>Click to expand</summary>

#compdef claude
# Zsh completion for Claude Code CLI
# Install by adding the directory to fpath in your .zshrc:
#   fpath=(~/.claude/completions $fpath)
#   autoload -Uz compinit && compinit

_claude() {
    local -a top_commands
    top_commands=(
        'agents:List configured agents'
        'auth:Manage authentication'
        'auto-mode:Inspect auto mode classifier configuration'
        'doctor:Check health of auto-updater'
        'install:Install Claude Code native build'
        'mcp:Configure and manage MCP servers'
        'plugin:Manage Claude Code plugins'
        'plugins:Manage Claude Code plugins'
        'setup-token:Set up a long-lived authentication token'
        'update:Check for updates and install'
        'upgrade:Check for updates and install'
    )

    local curcontext="$curcontext" state line
    typeset -A opt_args

    _arguments -C \
        '--add-dir[Additional directories to allow tool access to]:directories:_files -/' \
        '--agent[Agent for the current session]:agent:' \
        '--agents[JSON object defining custom agents]:json:' \
        '--allow-dangerously-skip-permissions[Enable bypassing permission checks]' \
        '(--allowedTools --allowed-tools)'{--allowedTools,--allowed-tools}'[Tool names to allow]:tools:' \
        '--append-system-prompt[Append a system prompt]:prompt:' \
        '--bare[Minimal mode: skip hooks, LSP, plugin sync, etc]' \
        '*--betas[Beta headers to include]:betas:' \
        '--brief[Enable SendUserMessage tool]' \
        '--chrome[Enable Claude in Chrome integration]' \
        '(-c --continue)'{-c,--continue}'[Continue most recent conversation]' \
        '--dangerously-skip-permissions[Bypass all permission checks]' \
        '(-d --debug)'{-d,--debug}'[Enable debug mode]::filter:' \
        '--debug-file[Write debug logs to file]:path:_files' \
        '--disable-slash-commands[Disable all skills]' \
        '(--disallowedTools --disallowed-tools)'{--disallowedTools,--disallowed-tools}'[Tool names to deny]:tools:' \
        '--effort[Effort level]:level:(low medium high max)' \
        '--fallback-model[Fallback model when overloaded]:model:(sonnet opus haiku claude-opus-4-6 claude-sonnet-4-6 claude-haiku-4-5-20251001)' \
        '*--file[File resources to download]:specs:' \
        '--fork-session[Create new session ID when resuming]' \
        '--from-pr[Resume session linked to a PR]::value:' \
        '(-h --help)'{-h,--help}'[Display help]' \
        '--ide[Auto-connect to IDE on startup]' \
        '--include-partial-messages[Include partial message chunks]' \
        '--input-format[Input format]:format:(text stream-json)' \
        '--json-schema[JSON Schema for structured output]:schema:' \
        '--max-budget-usd[Maximum dollar amount for API calls]:amount:' \
        '*--mcp-config[Load MCP servers from JSON files]:configs:_files' \
        '--mcp-debug[Enable MCP debug mode (deprecated)]' \
        '--model[Model for current session]:model:(sonnet opus haiku claude-opus-4-6 claude-sonnet-4-6 claude-haiku-4-5-20251001)' \
        '(-n --name)'{-n,--name}'[Display name for session]:name:' \
        '--no-chrome[Disable Chrome integration]' \
        '--no-session-persistence[Disable session persistence]' \
        '--output-format[Output format]:format:(text json stream-json)' \
        '--permission-mode[Permission mode]:mode:(acceptEdits bypassPermissions default dontAsk plan auto)' \
        '*--plugin-dir[Load plugins from directory]:path:_files -/' \
        '(-p --print)'{-p,--print}'[Print response and exit]' \
        '--replay-user-messages[Re-emit user messages from stdin]' \
        '(-r --resume)'{-r,--resume}'[Resume a conversation]::value:' \
        '--session-id[Use specific session ID]:uuid:' \
        '--setting-sources[Setting sources to load]:sources:' \
        '--settings[Path to settings JSON]:file:_files' \
        '--strict-mcp-config[Only use MCP servers from --mcp-config]' \
        '--system-prompt[System prompt for session]:prompt:' \
        '--tmux[Create tmux session for worktree]' \
        '*--tools[Specify available tools]:tools:' \
        '--verbose[Override verbose mode setting]' \
        '(-v --version)'{-v,--version}'[Output version number]' \
        '(-w --worktree)'{-w,--worktree}'[Create new git worktree]::name:' \
        '1: :->command' \
        '*:: :->args' \
        && return

    case $state in
        command)
            _describe -t commands 'claude command' top_commands
            ;;
        args)
            case $line[1] in
                mcp)
                    _claude_mcp
                    ;;
                auth)
                    _claude_auth
                    ;;
                auto-mode)
                    _claude_auto_mode
                    ;;
                agents)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '--setting-sources[Setting sources to load]:sources:'
                    ;;
                install)
                    _arguments \
                        '--force[Force installation]' \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '1:target:(stable latest)'
                    ;;
                plugin|plugins)
                    _claude_plugin
                    ;;
                update|upgrade|doctor|setup-token)
                    _arguments '(-h --help)'{-h,--help}'[Display help]'
                    ;;
            esac
            ;;
    esac
}

_claude_mcp() {
    local -a mcp_commands
    mcp_commands=(
        'add:Add an MCP server'
        'add-from-claude-desktop:Import MCP servers from Claude Desktop'
        'add-json:Add MCP server with JSON string'
        'get:Get details about an MCP server'
        'help:Display help'
        'list:List configured MCP servers'
        'remove:Remove an MCP server'
        'reset-project-choices:Reset project-scoped server choices'
        'serve:Start the Claude Code MCP server'
    )

    _arguments -C \
        '(-h --help)'{-h,--help}'[Display help]' \
        '1: :->subcmd' \
        '*:: :->args' \
        && return

    case $state in
        subcmd)
            _describe -t commands 'mcp command' mcp_commands
            ;;
        args)
            case $line[1] in
                add)
                    _arguments \
                        '--callback-port[Fixed port for OAuth callback]:port:' \
                        '--client-id[OAuth client ID]:clientId:' \
                        '--client-secret[Prompt for OAuth client secret]' \
                        '(-e --env)*'{-e,--env}'[Set environment variables]:env:' \
                        '(-H --header)*'{-H,--header}'[Set WebSocket headers]:header:' \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '(-s --scope)'{-s,--scope}'[Configuration scope]:scope:(local user project)' \
                        '(-t --transport)'{-t,--transport}'[Transport type]:transport:(stdio sse http)' \
                        '1:name:' \
                        '2:commandOrUrl:_files'
                    ;;
                add-json)
                    _arguments \
                        '--client-secret[Prompt for OAuth client secret]' \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '(-s --scope)'{-s,--scope}'[Configuration scope]:scope:(local user project)' \
                        '1:name:' \
                        '2:json:'
                    ;;
                add-from-claude-desktop)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '(-s --scope)'{-s,--scope}'[Configuration scope]:scope:(local user project)'
                    ;;
                remove)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '(-s --scope)'{-s,--scope}'[Configuration scope]:scope:(local user project)' \
                        '1:name:'
                    ;;
                serve)
                    _arguments \
                        '(-d --debug)'{-d,--debug}'[Enable debug mode]' \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '--verbose[Override verbose mode]'
                    ;;
                get)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '1:name:'
                    ;;
                *)
                    _arguments '(-h --help)'{-h,--help}'[Display help]'
                    ;;
            esac
            ;;
    esac
}

_claude_auth() {
    local -a auth_commands
    auth_commands=(
        'help:Display help'
        'login:Sign in to your Anthropic account'
        'logout:Log out from your Anthropic account'
        'status:Show authentication status'
    )

    _arguments -C \
        '(-h --help)'{-h,--help}'[Display help]' \
        '1: :->subcmd' \
        '*:: :->args' \
        && return

    case $state in
        subcmd)
            _describe -t commands 'auth command' auth_commands
            ;;
        args)
            case $line[1] in
                login)
                    _arguments \
                        '--claudeai[Use Claude subscription (default)]' \
                        '--console[Use Anthropic Console]' \
                        '--email[Pre-populate email]:email:' \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '--sso[Force SSO login flow]'
                    ;;
                status)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '--json[Output as JSON]' \
                        '--text[Output as human-readable text]'
                    ;;
                *)
                    _arguments '(-h --help)'{-h,--help}'[Display help]'
                    ;;
            esac
            ;;
    esac
}

_claude_auto_mode() {
    local -a auto_mode_commands
    auto_mode_commands=(
        'config:Print effective auto mode config as JSON'
        'critique:Get AI feedback on custom auto mode rules'
        'defaults:Print default auto mode rules as JSON'
        'help:Display help'
    )

    _arguments -C \
        '(-h --help)'{-h,--help}'[Display help]' \
        '1: :->subcmd' \
        '*:: :->args' \
        && return

    case $state in
        subcmd)
            _describe -t commands 'auto-mode command' auto_mode_commands
            ;;
        args)
            case $line[1] in
                critique)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '--model[Override model]:model:(sonnet opus haiku claude-opus-4-6 claude-sonnet-4-6 claude-haiku-4-5-20251001)'
                    ;;
                *)
                    _arguments '(-h --help)'{-h,--help}'[Display help]'
                    ;;
            esac
            ;;
    esac
}

_claude_plugin() {
    local -a plugin_commands
    plugin_commands=(
        'disable:Disable an enabled plugin'
        'enable:Enable a disabled plugin'
        'help:Display help'
        'install:Install a plugin from marketplace'
        'i:Install a plugin from marketplace'
        'list:List installed plugins'
        'marketplace:Manage Claude Code marketplaces'
        'uninstall:Uninstall an installed plugin'
        'remove:Uninstall an installed plugin'
        'update:Update a plugin to latest version'
        'validate:Validate a plugin or marketplace manifest'
    )

    _arguments -C \
        '(-h --help)'{-h,--help}'[Display help]' \
        '1: :->subcmd' \
        '*:: :->args' \
        && return

    case $state in
        subcmd)
            _describe -t commands 'plugin command' plugin_commands
            ;;
        args)
            case $line[1] in
                install|i)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '(-s --scope)'{-s,--scope}'[Installation scope]:scope:(user project local)' \
                        '1:plugin:'
                    ;;
                uninstall|remove)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '--keep-data[Preserve plugin persistent data]' \
                        '(-s --scope)'{-s,--scope}'[Uninstall from scope]:scope:(user project local)' \
                        '1:plugin:'
                    ;;
                list)
                    _arguments \
                        '--available[Include available plugins from marketplaces]' \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '--json[Output as JSON]'
                    ;;
                disable)
                    _arguments \
                        '(-a --all)'{-a,--all}'[Disable all enabled plugins]' \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '(-s --scope)'{-s,--scope}'[Installation scope]:scope:(user project local)' \
                        '1:plugin:'
                    ;;
                enable)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '(-s --scope)'{-s,--scope}'[Installation scope]:scope:(user project local)' \
                        '1:plugin:'
                    ;;
                update)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '(-s --scope)'{-s,--scope}'[Installation scope]:scope:(user project local managed)' \
                        '1:plugin:'
                    ;;
                validate)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '1:path:_files'
                    ;;
                marketplace)
                    _claude_plugin_marketplace
                    ;;
                *)
                    _arguments '(-h --help)'{-h,--help}'[Display help]'
                    ;;
            esac
            ;;
    esac
}

_claude_plugin_marketplace() {
    local -a marketplace_commands
    marketplace_commands=(
        'add:Add a marketplace from URL, path, or GitHub repo'
        'help:Display help'
        'list:List all configured marketplaces'
        'remove:Remove a configured marketplace'
        'rm:Remove a configured marketplace'
        'update:Update marketplace(s) from source'
    )

    _arguments -C \
        '(-h --help)'{-h,--help}'[Display help]' \
        '1: :->subcmd' \
        '*:: :->args' \
        && return

    case $state in
        subcmd)
            _describe -t commands 'marketplace command' marketplace_commands
            ;;
        args)
            case $line[1] in
                add)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '--scope[Where to declare marketplace]:scope:(user project local)' \
                        '*--sparse[Limit checkout to specific directories]:paths:_files -/' \
                        '1:source:'
                    ;;
                list)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '--json[Output as JSON]'
                    ;;
                remove|rm)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '1:name:'
                    ;;
                update)
                    _arguments \
                        '(-h --help)'{-h,--help}'[Display help]' \
                        '1:name:'
                    ;;
                *)
                    _arguments '(-h --help)'{-h,--help}'[Display help]'
                    ;;
            esac
            ;;
    esac
}

_claude "$@"

</details>

Installation (current workaround)

Bash - add to ~/.bashrc:

source ~/.claude/completions/claude.bash

Zsh - add to ~/.zshrc:

fpath=(~/.claude/completions $fpath)
autoload -Uz compinit && compinit

Ideal built-in approach

Other CLI tools (e.g. gh completion, kubectl completion, docker completion) provide a completion subcommand that outputs the script for a given shell. A similar claude completion bash / claude completion zsh / claude completion fish command would allow users to set up completions with a one-liner and would stay in sync with CLI changes automatically.

Alternatively, since Claude Code is built with Commander.js, consider integrating a library like tabtab or generating completions from the Commander program definition at build time.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗