Feature request: built-in shell tab-completion for CLI flags and subcommands
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.
This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗