opinionated-fish-shell/skills/fish-shell-scripting/SKILL.md
Fish shell scripting judgment frameworks and critical idioms. Use when writing Fish scripts or shell automation. Focuses on when to use Fish vs bash, macOS/Fedora compatibility requirements, and Fish-specific patterns that prevent bugs (universal variable anti-patterns, wrapper functions, interactive guards).
npx skillsauth add pyroxin/opinionated-claude-skills fish-shell-scriptingInstall 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.
Related skills:
software-engineer - General scripting design principlespython-programmer - When shell complexity exceeds ~100 lines, consider Python<core_philosophy> Fish is a user-friendly shell that prioritizes correctness over POSIX compatibility. Its lists-not-strings semantics eliminates entire classes of bugs common in bash/zsh. The limitation is portability, not capability.
Key insight: Fish handles sophisticated automation well. Choose Fish when you control the environment; choose bash when you don't. </core_philosophy>
<platform_requirements>
Compatibility: All Fish scripts MUST work on both macOS and Fedora Linux. Handle platform differences via uname detection.
Quote paths with spaces: Always quote file paths even though Fish has no word splitting. Fish doesn't need quotes technically (no word splitting), but quoting anyway: (1) makes intent explicit for readability, (2) builds consistent habits when switching between shells, (3) ensures compatibility when passing to external commands that may interpret spaces.
Shebang: Use #!/usr/bin/env fish for portability across installation locations.
</platform_requirements>
<fish_vs_alternatives> Use Fish for:
argparse + fish_opt)Use bash for:
/bin/sh compatibilityUse a programming language for:
<posix_differences>
No word splitting: Variables don't split on spaces. set name "foo bar"; echo $name is one argument. This eliminates a major source of bugs in bash/zsh.
All variables are lists: A "string" is a one-element list. Indexing is 1-based: $PATH[1], $PATH[-1].
No VAR=value syntax: Use set command. set -gx VAR value (global exported), set -lx VAR value (local exported).
Command substitution splits on newlines only: set lines (cat file) creates one element per line. For space-splitting: string split " ".
</posix_differences>
<scoping_decision> Choosing a scope:
| Need | Scope | Example Use Case |
|------|-------|------------------|
| Temporary within function/block | -l | Loop variables, intermediate results |
| Shared across session | -g | Current project settings, temporary overrides |
| Available to child processes | -gx or -lx | PATH, EDITOR, build flags |
| Persist across sessions (Fish UI only) | -U | fish_color_*, key bindings, prompt config |
Flag combinations:
-l (local): Dies when block ends-g (global): Session-scoped, not inherited by child processes-x (exported): Available to child processes (combine with -g or -l)-U (universal): Persists across all sessions, survives rebootsExample: set -gx EDITOR vim (global + exported), set -lx DEBUG 1 (local + exported to children).
CRITICAL: Environment variables (PATH, EDITOR, etc.) should NEVER be universal. Universal scope is for Fish UI config only. </scoping_decision>
<universal_variable_antipattern> NEVER use universal variables for PATH or environment variables. Universal scope is for Fish UI config only (colors, key bindings, prompt).
Why this matters: Universal variables persist to disk and are shared across all Fish sessions. If you append to PATH in config.fish using universal variables, it grows indefinitely on every shell start because:
# WRONG: Grows PATH indefinitely on every shell start
set -U fish_user_paths ~/bin $fish_user_paths
# RIGHT: Session-scoped (recalculated fresh each session)
set -gx PATH ~/bin $PATH
# RIGHT: Use fish_add_path once interactively (idempotent, uses universal internally)
fish_add_path ~/bin
Safe universal variable uses: fish_color_*, fish_key_bindings, fish_prompt, fish_greeting.
</universal_variable_antipattern>
<cross_platform>
OS detection: switch (uname); case Darwin; ...; case Linux; ...; end
Conditional PATH: test -d ~/.cargo/bin; and fish_add_path ~/.cargo/bin
Platform-specific utilities to watch for:
sed, find, date, stat, readlink behave differently/usr/local/bin (macOS Homebrew) vs /usr/bin (Fedora)
</cross_platform><critical_idioms>
Wrapper functions require --wraps and $argv:
function ls --wraps=ls --description "ls with color"
command ls --color=auto $argv
end
--wraps=ls: Completions break (Fish doesn't know what command to complete for)$argv: Arguments are swallowed (user's flags/paths ignored)Argument parsing: Use argparse + fish_opt for complex CLI tools. Check flags with set -q _flag_name.
Guard interactive code: Non-interactive shells (SSH, rsync) execute config.fish. Guard output:
if status is-interactive
echo "Welcome!"
end
Without this guard: SSH file transfers, rsync, and scp can fail because unexpected output corrupts the protocol.
Use string builtin: Prefer string match, string split over external grep/cut. Benefits: faster execution (no fork), consistent cross-platform behavior, proper exit codes.
and/or for control flow: Chain commands with and/or, not just &&/||. Both work, but and/or are Fish's native idiom.
</critical_idioms>
<organization_decisions>
| Content Type | Location | Why |
|--------------|----------|-----|
| Functions (reusable commands) | ~/.config/fish/functions/name.fish | Autoloaded on first use, not at startup—keeps shell fast |
| Topical config (per-tool setup) | ~/.config/fish/conf.d/tool.fish | Auto-sourced alphabetically, keeps concerns separated |
| Interactive-only setup | config.fish with status is-interactive | Guards against breaking non-interactive use |
| Environment variables | conf.d/ with set -gx | Session-scoped, recalculated fresh each session |
Minimal config.fish: Keep it to ~15 lines of orchestration. Put actual config in conf.d/ files.
Autoload advantage: Functions in functions/ are loaded only when first called, not at startup. This keeps shell startup fast even with many custom functions.
</organization_decisions>
<common_mistakes>
<from_bash>
VAR=value syntax: Fish uses set VAR value. The VAR=value command pattern doesn't exist.set x "foo bar"; echo $x is ONE argument in Fish. This is a feature, not a bug.export VAR=value: Fish uses set -gx VAR value (global exported).$(...): Fish uses (...) for command substitution, not $(...).&&/|| exclusively: Works in Fish, but and/or are the native idiom.[[...]] tests: Fish uses test or [...], not bash's [[...]].
</from_bash><from_zsh>
$array[1] not $array[1] or ${array[1]}.setopt/unsetopt: Fish configuration works differently—use set -U for persistent settings.<from_any_shell>
set -gx or one-time fish_add_path.$argv in wrapper functions: Silently swallows all user arguments.(whoami) instead of $USER: Unnecessary subshell; environment variable is faster.(hostname) instead of $hostname: Same issue—use the variable.alias instead of abbr: Abbreviations expand visibly in history, making commands reproducible. Aliases hide what actually ran.Local Documentation:
Typical locations (check these paths directly):
/opt/homebrew/share/doc/fish//usr/local/share/doc/fish//usr/share/doc/fish/Man pages:
/opt/homebrew/share/man/man1/fish*.1/usr/local/share/man/man1/fish*.1/usr/share/man/man1/fish*.1To find dynamically (from Fish shell):
ls "$(dirname (realpath (command -s fish)))/../share/doc/fish"
</resources>data-ai
--- name: interactive-research description: Multi-source research orchestrated across a persistent agent team. Use for queries asking for research-style investigation of a topic — comparing, contrasting, surveying, investigating, evaluating, deeply researching, doing a literature review, or any similar research intent — or that ask what people are saying publicly about a topic (discourse, expert opinion, industry consensus, and related framings). Example phrasings (illustrative, not exhaustive):
development
Expert-level guidance for creating high-quality Claude Code skills. Use alongside Anthropic's skill-creator when creating new skills, improving existing skills, or needing guidance on skill content quality. Complements basic skill mechanics with research-driven content development, XML tag structuring, decision frameworks over mechanics, cross-references between skills, and systematic validation.
development
--- name: deep-research description: Deprecated name; the skill formerly here is now opinionated-research:interactive-research. Retained as a context-level notice because Claude Code now ships a built-in deep-research workflow that runs one-shot, non-interactive fan-out research (no conversation after the report). interactive-research instead runs a persistent agent team you can keep questioning once the report lands. Guidance for Claude: when the user makes a research request, ask whether they
development
Pedagogical framework for teaching programming through Socratic dialogue. Use when a learner wants to LEARN programming rather than have code written for them. Triggers include "teach me", "help me understand", "I'm learning", "tutor mode", or requests to not provide solutions. Emphasizes productive struggle, graduated hints, metacognitive scaffolding, and emotional support.