---
name: fish-helper
description: Current Fish shell scripting, functions, abbreviations, completions, variables, events, configuration, plugins, testing, and safety guidance. Use when writing or reviewing Fish config, `.fish` scripts, completions, functions, prompts, or plugin setup.
---

# Fish Helper

Write Fish as Fish rather than translated POSIX shell. Preserve argv boundaries, propagate statuses, make configuration idempotent, and distinguish optional integrations from required tools.

## Current baseline

Verified against Fish 4.8.1 on 2026-08-03:

```fish
fish --version
```

Fish 4.8 changed installed/embedded completion and function layout, added `cd -L/-P`, and removed automatic `__fish_initialized` universal creation. Fish 4.7 changed noninteractive theme initialization; Fish 4.6 changed emoji width and added prompt environment controls; Fish 4.5 mainly fixed Vi-mode regressions.

Read [references/releases.md](references/releases.md) for the 51-page research ledger. Read [references/syntax-and-safety.md](references/syntax-and-safety.md) for variables, argv, statuses, reading, tracing, temp directories, and shell boundaries. Read [references/functions-completions-config.md](references/functions-completions-config.md) for functions, events, abbreviations, completions, startup, prompts, and themes. Read [references/plugins-and-testing.md](references/plugins-and-testing.md) for Fisher, popular plugins, testing, and installation security.

## Variables and environment

Use exported global variables in version-controlled config for child-process environment:

```fish
set -gx EDITOR nvim
fish_add_path --global $HOME/.local/bin
```

Universal variables remain supported, but Fish 4.3 stopped creating several user-facing defaults as universal values. Use universal state only when cross-session persistence is intentional. `set -U EDITOR vim` is not exported; `set -Ux` is persistent and exported but can create hidden machine state.

Use `fish_add_path --global` for idempotent, version-controlled path changes. Do not replace `PATH` with a short hard-coded list or prepend the same directory every time config is sourced.

Fish supports command-scoped environment overrides:

```fish
MODE=test command --flag
```

Standalone assignment still uses `set`.

## Preserve argv

Accept commands as the remaining arguments and invoke them directly:

```fish
function retry --description 'Retry a command with exact arguments'
    argparse --stop-nonopt 'n/max-attempts=' -- $argv
    or return

    set -l max_attempts 3
    if set -q _flag_max_attempts
        set max_attempts $_flag_max_attempts
    end
    if not string match --quiet --regex '^[1-9][0-9]*$' -- $max_attempts
        echo 'retry: max-attempts must be a positive integer' >&2
        return 2
    end
    if test (count $argv) -eq 0
        echo 'retry: missing command' >&2
        return 2
    end

    set -l attempt 1
    while test $attempt -le $max_attempts
        $argv
        set -l command_status $status
        if test $command_status -eq 0
            return 0
        end
        if test $attempt -eq $max_attempts
            return $command_status
        end
        set attempt (math $attempt + 1)
        or return
    end
end
```

Do not turn command arguments into source text with `eval`. Invoke the argv list directly or call an exact function.

## Error propagation

Fish functions return the status of their last command unless overridden. A helper named `die` that only executes `return 1` returns from itself; its caller continues unless it propagates the status.

```fish
require_tool rg
or return
```

Check `mktemp`, `pushd`, reads, generated init commands, and cleanup explicitly. Required tools should fail fast. Only optional integrations may be conditionally absent, and the config should label them optional.

## Reading input

`read` normally reads one line. It does not turn a whole file into an array by adding list flags:

```fish
while read -l line
    process_line $line
end < file.txt
```

Use `string split` when delimiter-based parsing is deliberate. Use `read --silent` for interactive secrets, or a tool-specific credential file/secret manager; do not write credential-shaped literals into shell config.

## Abbreviations

Use abbreviations for interactive expansion and functions for reusable logic. Cursor markers default to `%`:

```fish
abbr --add L --position anywhere --set-cursor '% | less'
```

The expansion needs the marker for cursor movement.

## Transient prompts and themes

Enable transient prompts with a variable, not a function:

```fish
set -g fish_transient_prompt 1
```

Fish reruns `fish_prompt`, `fish_right_prompt`, and `fish_mode_prompt` with `--final-rendering`.

Prefer `fish_config theme choose THEME` for adaptive theme behavior. `fish_config theme save` stores universal colors and disables dynamic light/dark switching. Theme files use `fish_color_command blue`, not assignment syntax.

## Startup and configuration

Fish searches user `conf.d`, system configuration, and user/vendor data directories according to documented priority; snippets are naturally sorted, and only the first same-named file is run. Do not describe a simple system-then-user order.

Put environment and path setup needed by noninteractive Fish before an interactive-only guard:

```fish
fish_add_path --global $HOME/.local/bin
set -gx EDITOR nvim

status is-interactive
or return
```

Use `fish --profile-startup <file> -ic exit` to profile startup. Plain `--profile` excludes startup/config loading.

## Completions and events

Fish 4.8 embeds bundled completions/functions; use `status list-files` to inspect embedded files rather than assuming `/usr/share/fish/completions`.

Register multiple commands with repeated `--command` or brace expansion:

```fish
complete --command={docker,podman} --long-option help --description 'Show help'
```

Dynamic completion generators must be fast, bounded, side-effect-free, and must not interpret untrusted source. Avoid network calls on each Tab press.

Variable event handlers can coalesce updates, can run on same-value sets, and have unspecified ordering across handlers. Universal updates from another shell have distinct delivery behavior. Use events for notifications, not ordering-critical state machines.

## Tracing and introspection

`type --type name` prints classifications such as function, builtin, or file. `type --short` only suppresses full function definitions.

Tracing is enabled when `fish_trace` is set and non-empty. Disable it by erasing the variable:

```fish
set -e fish_trace
```

Use `$version` or compatibility variable `$FISH_VERSION`; `$fish_version` does not exist in Fish 4.8.1.

## Security

- Avoid `eval` and remote `curl | source` installation patterns.
- Pin and inspect a downloaded plugin installer before sourcing it.
- Resolve source paths with `type --path`, verify exactly one intended file, then source it.
- Keep dynamic completions and event handlers free of destructive side effects.
- Use secret managers, protected credential files, or `read --silent`; environment variables can still leak through child processes and diagnostics.
- Preserve exact arguments rather than re-parsing command text.
- Create and clean temporary directories only after each operation succeeds, preserving the wrapped command status.

## Review checklist

- Verify Fish 4.8.1 behavior and the project's minimum version.
- Use exported globals and `fish_add_path --global` for version-controlled environment setup.
- Preserve argv and avoid `eval`.
- Propagate failures through functions and cleanup.
- Read complete files with a loop or explicit splitting.
- Use the current transient-prompt variable and adaptive theme workflow.
- Put noninteractive environment setup before the interactive guard.
- Treat completion subprocesses and event handlers as bounded, side-effect-free hooks.
- Inspect embedded completion paths with `status list-files`.
- Pin and review third-party plugin installation.
