# fish docs index: initialization files (config.fish is always sourced) ## Metadata | Field | Value | |---|---| | Institute | Molodetz Institute — Research & Retrieval Division | | Subject | shell-multiplexer-auto-attach | | Document type | Source Dossier | | Tier | 1 | | Source title | fish documentation index (3.4) | | Source URL | https://fishshell.com/docs/3.4/index.html | | Source publisher / author | fish-shell project (fishshell.com) | | Source type | vendor docs | | Source date (published) | Unknown — not stated | | Date researched | 2026-10-10 | | Researched by | tai (agent), model: Claude Sonnet 5, on behalf of retoor | | Research method | web search synthesis | | Confidence | High | | Status | Active | ## Summary fish's official docs state plainly that its startup files are read "on the startup of every shell, not just interactive ones" — unlike bash's `.bashrc` (interactive-only, non-login) or `.profile`/`.bash_profile` (login-only). `~/.config/fish/config.fish` (respecting `$XDG_CONFIG_HOME`) is read on *every* fish invocation, interactive or not, login or not, and is explicitly the right place to put code that should run unconditionally — with `status is-interactive`/`status is-login` as the documented way to scope behavior within it. Snippets in `~/.config/fish/conf.d/` run before `config.fish`, which runs last so it can override them. ## Key findings - This is the single most consequential difference from bash/zsh for this project: `.bashrc` and `.zshrc` are themselves gated to interactive shells by the shell's own startup logic (roughly), so the bash/zsh plexer block's `case "$-" in *i*)` guard is a belt-and-suspenders check on top of a file that mostly only gets read in the right context anyway. `config.fish` has no such built-in gating — it runs unconditionally, so the `status is-interactive` guard inside `FISH_PLEXER_BLOCK` is doing *all* of the interactivity-scoping work, not just reinforcing it. - The fish FAQ (per the search tool's synthesis) explicitly calls out this exact contrast with `.bashrc`/`.profile`, confirming it's a documented, intentional design difference in fish, not an edge case this research happened to stumble on. - `~/.config/fish/config.fish`'s location follows `$XDG_CONFIG_HOME` — directly informing `shell_rc_path()` in tai.py, which checks `os.environ.get("XDG_CONFIG_HOME")` before falling back to `~/.config`. ## Pros / strengths - Official documentation stating the behavior plainly and explicitly contrasting it with bash/zsh conventions, removing any ambiguity about whether the interactivity guard is optional. ## Cons / limitations / caveats - A third-party pyenv wiki page (github-pyenv-wiki-unix-shell-initialization.md, also Tier 1) lists a fuller startup-file ordering (system/user `conf.d`, `vendor_conf.d`, `/etc/fish/config.fish`, then `~/.config/fish/config.fish`) that this search could not independently confirm against the official docs; treated as secondary/unconfirmed per that dossier's own caveat, not relied on for anything load-bearing here. ## Relevance to tai This is the finding that made the `status is-interactive` guard non-optional in `FISH_PLEXER_BLOCK`: without it, installing the plexer hook into `config.fish` would attempt to `exec tai.py --plexer-wrap` on *every* fish invocation, including scripts, `fish -c`, and tooling that shells out to fish non-interactively — exactly the failure mode the user explicitly wanted to avoid. Confirmed end-to-end by the `FishNonInteractiveSkipTest` integration test in `tests/bashrc.py`, which runs a plain `fish -c 'echo marker'` (no explicit sourcing needed, since config.fish always runs) and asserts no plexer socket gets created. ## Changelog | Date | Change | |---|---| | 2026-10-10 | Initial dossier created. |