git add exits 1 "The following paths are ignored by one of your .gitignore files" when an :(exclude) pathspec names an existing ignored path
git treats an exclude pathspec as naming the path. If that path exists and is also gitignored, `git add` prints the ignored-paths error and exits 1, even though you asked to skip it. It happens only when a positive pathspec makes git walk that far, e.g. a top-level directory. Stage first, then unstage the private paths with `git reset`.
Symptom
A script makes sure some private files are never committed, even if .gitignore loses its line, by adding exclude pathspecs:
git add -- home ':(exclude,glob)work/**' ':(exclude,glob)home/private.md'
This works until work/ (even an empty directory) or home/private.md exists and is listed in .gitignore. From then on:
The following paths are ignored by one of your .gitignore files:
work
hint: Use -f if you really want to add them.
The exit status is 1. A caller that treats a non-zero exit as fatal crashes, even though the files that were wanted are staged.
What triggers it (reproduced on toy repos)
- git counts the exclude pathspec as naming the ignored path, the same as
git add workwould. - It fires only when git's walk reaches that path. With a top-level positive pathspec (
home), the common prefix is empty and git walks from the repo root, so it meetswork/. Withprojects/aorhome/x, git never walks there and the add exits 0. - An exclude that names a file inside the positive pathspec (
home+:(exclude)home/private.md) also fails once that ignored file exists. :(exclude,glob)**/packet.mdwith an ignoredhome/packet.mddid not fail in the same test.git -c advice.addIgnoredFile=falseonly hides the hint. The exit status stays 1.- Tests that build a repo without a
.gitignorenever see it, because the excluded paths are then not ignored. A regression test needs the real.gitignorelines, the paths present, and the real (top-level) pathspec.
Fix
Stage, then unstage the private paths:
git add -- home
git reset -q -- ':(glob)work/**' ':(glob)home/private.md'
This keeps the same guarantee: if .gitignore loses its line, git add stages the file and git reset takes it back out. git reset -- <pathspec> exited 0 on an unborn branch (no commits yet) and when nothing matched. git reset leaves the index at HEAD for those paths, which is what the exclude did. git rm --cached would instead stage a deletion for an already-tracked file.
Check
On a toy repo with .gitignore = work/ and home/private.md, an empty work/, and home/private.md present:
- the old command exited 1;
- add, then reset, exited 0 and staged only the wanted file;
- a unit test asserting that commit fails with the old command and passes with the new one.
Conditions
- git
- 2.43.0
- os
- Ubuntu 24.04
- date
- 2026-10-04