skills/infrastructure/fish-shell-config/SKILL.md
Fish shell configuration and PATH management.
npx skillsauth add notque/claude-code-toolkit fish-shell-configInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Fish is not POSIX. Every pattern here targets Fish 3.0+ (supports $(), &&, ||). Fish 4.0 (Rust rewrite) has no syntax changes. All generated code must use Fish-native syntax exclusively — never emit Bash constructs (VAR=value, [[ ]], export, heredocs) in Fish contexts.
| Signal | Load These Files | Why |
|---|---|---|
| migrations | bash-migration.md | Loads detailed guidance from bash-migration.md. |
| editing fish config: variable assignment, PATH management, conditionals, tool integration | fish-preferred-patterns.md | Loads detailed guidance from fish-preferred-patterns.md. |
| syntax lookup: variable scope, PATH, functions, abbreviations, conditionals, debugging | fish-quick-reference.md | Loads detailed guidance from fish-quick-reference.md. |
| wiring dev tools into fish: Go, Rust, Node, Python, Docker, PATH and init hooks | tool-integrations.md | Loads detailed guidance from tool-integrations.md. |
Before writing any shell code, confirm the target is Fish:
$SHELL contains fish, or.fish extension, or~/.config/fish/If none of these hold, stop — this skill does not apply to Bash, Zsh, or POSIX shells.
Place configuration in conf.d/ modules with numeric prefixes for ordering — keep config.fish minimal. A monolithic config.fish with hundreds of lines is slow to load, hard to maintain, and impossible to selectively disable.
Directory layout:
~/.config/fish/
├── config.fish # Minimal — interactive-only init
├── fish_variables # Auto-managed by Fish (never edit)
├── conf.d/ # Auto-sourced in alphabetical order
│ ├── 00-path.fish
│ ├── 10-env.fish
│ └── 20-abbreviations.fish
├── functions/ # Autoloaded functions (one per file)
│ ├── fish_prompt.fish
│ └── mkcd.fish
└── completions/ # Custom completions
└── mycommand.fish
Decision tree:
| What you're writing | Where it goes |
|---------------------|---------------|
| PATH additions | conf.d/00-path.fish |
| Environment variables | conf.d/10-env.fish |
| Abbreviations | conf.d/20-abbreviations.fish |
| Tool integrations | conf.d/30-tools.fish |
| Named function | functions/<name>.fish |
| Custom prompt | functions/fish_prompt.fish |
| Completions | completions/<command>.fish |
| One-time interactive init | config.fish (inside status is-interactive) |
Variable assignment is always set VAR value — never VAR=value (syntax error in Fish) or export VAR=value.
set -l VAR value # Local — current block only
set -f VAR value # Function — entire function scope
set -g VAR value # Global — current session
set -U VAR value # Universal — persists across sessions (use sparingly)
set -x VAR value # Export — visible to child processes
set -gx VAR value # Global + Export (typical for env vars)
set -e VAR # Erase variable
set -q VAR # Test if set (silent, for conditionals)
Every Fish variable is a list. Never use colon-separated strings for PATH or similar variables — set PATH "$PATH:/new/path" creates a single malformed element because Fish PATH is a list, not a colon-delimited string.
Use fish_add_path for PATH manipulation — it handles deduplication and persistence automatically. Manual set PATH only for session-scoped overrides.
# CORRECT: fish_add_path handles deduplication and persistence
fish_add_path ~/.local/bin
fish_add_path ~/.cargo/bin
fish_add_path -P ~/go/bin # -P = session only, no persist
# CORRECT: Direct manipulation when needed (session only)
set -gx PATH ~/custom/bin $PATH
# WRONG: Colon-separated string — Fish PATH is a list
# set PATH "$PATH:/new/path"
The autoloaded function filename must match the function name exactly — functions/foo.fish must contain function foo. A mismatch causes "Unknown command" errors.
# ~/.config/fish/functions/mkcd.fish
function mkcd --description "Create directory and cd into it"
mkdir -p $argv[1]
and cd $argv[1]
end
Functions with argument parsing:
function backup --description "Create timestamped backup"
argparse 'd/dest=' 'h/help' -- $argv
or return
if set -q _flag_help
echo "Usage: backup [-d destination] file..."
return 0
end
set -l dest (set -q _flag_dest; and echo $_flag_dest; or echo ".")
for file in $argv
set -l ts (date +%Y%m%d_%H%M%S)
cp $file $dest/(basename $file).$ts.bak
end
end
| Use Case | Mechanism | Why |
|----------|-----------|-----|
| Simple shortcut | abbr -a g git | Expands in-place, visible in history |
| Needs arguments/logic | function in functions/ | Full programming, works in scripts |
| Wrapping a command | alias ll "ls -la" | Convenience; creates function internally |
Abbreviations are interactive-only — they do not work in scripts. Always wrap them in an interactive guard because they have no effect during non-interactive sourcing:
# Always guard abbreviations
if status is-interactive
abbr -a g git
abbr -a ga "git add"
abbr -a gc "git commit"
abbr -a gst "git status"
abbr -a dc "docker compose"
end
Use the test builtin for conditionals — never [[ ]] (syntax error in Fish) or [ ] (calls external /bin/[, slower than the builtin). Fish has no word splitting, so $var and "$var" behave identically — quote only when you need to prevent list expansion or preserve empty strings.
# Conditionals — use 'test', not [[ ]]
if test -f config.json
echo "exists"
else if test -d config
echo "is directory"
end
# Command chaining (both styles work in Fish 3.0+)
mkdir build && cd build && cmake ..
mkdir build; and cd build; and cmake ..
# Loops
for file in *.fish
echo "Processing $file"
end
# Switch
switch $argv[1]
case start
echo "Starting..."
case stop
echo "Stopping..."
case "*"
echo "Unknown: $argv[1]"
return 1
end
Guard every tool integration with type -q so the config works on machines where the tool is not installed:
# ~/.config/fish/conf.d/30-tools.fish
if type -q starship
starship init fish | source
end
if type -q direnv
direnv hook fish | source
end
if type -q fzf
fzf --fish | source
end
# Homebrew (macOS)
if test -x /opt/homebrew/bin/brew
eval (/opt/homebrew/bin/brew shellenv)
end
# Nix
if test -e /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.fish
source /nix/var/nix/profiles/default/etc/profile.d/nix-daemon.fish
end
fish -n <file> (parse without executing)functions/status is-interactive guards on abbreviations and key bindings in conf.d/fish --no-config then source <file> to confirm isolated correctnessUser says: "Set up my Fish shell config"
~/.config/fish/conf.d/00-path.fish, conf.d/10-env.fish, conf.d/20-abbreviations.fishUser says: "Convert my .bash_aliases to Fish"
.bash_aliases, confirm Fish targetconf.d/, functions to functions/Cause: Filename does not match function name
Solution: Ensure functions/foo.fish contains exactly function foo. Check for typos in both the filename and the function declaration.
Cause: Used set -gx PATH (session-only) instead of fish_add_path (writes to universal fish_user_paths)
Solution: Use fish_add_path /new/path which persists by default, or use set -U fish_user_paths /path $fish_user_paths explicitly.
Cause: Abbreviations are interactive-only by design
Solution: Use a function instead. Move the logic from abbr to a file in functions/.
Cause: Missing -x (export) flag on set
Solution: Use set -gx VAR value to make variable visible to subprocesses. Check with set --show VAR to inspect current scope and export status.
| Task Signal | Load | Why |
|-------------|------|-----|
| Migrating from Bash, converting .bashrc/.bash_aliases, source, export, [[ in Fish file | bash-migration.md | Full Bash-to-Fish syntax translation table |
| Variable scoping, PATH management, fish_add_path, set flags, abbr, completions | fish-quick-reference.md | Variable scope guide, special variables, control flow cheatsheet |
| Error audit, "unknown command", PATH not persisting, abbreviation not working, syntax error, broken conf.d | fish-preferred-patterns.md | Failure modes with grep detection commands and error-fix mappings |
| Go, Rust, Docker, Node.js, Python, pyenv, fnm, starship, direnv, fzf, zoxide, mise, tool setup | tool-integrations.md | Concrete integration patterns for common dev tools |
${CLAUDE_SKILL_DIR}/references/bash-migration.md: Complete Bash-to-Fish syntax translation table${CLAUDE_SKILL_DIR}/references/fish-quick-reference.md: Variable scoping, special variables, and command cheatsheet${CLAUDE_SKILL_DIR}/references/fish-preferred-patterns.md: Failure mode catalog with grep detection commands and error-fix mappings${CLAUDE_SKILL_DIR}/references/tool-integrations.md: Concrete integration patterns for Go, Rust, Docker, Node.js, Python, and shell enhancersdata-ai
Extract video transcripts: yt-dlp subtitles to clean paragraphs.
tools
Collect, filter, and freshness-qualify news items.
development
Convert PDF, Office, HTML, data, media, ZIP to Markdown.
testing
Verify factual claims against sources before publish.