#7596·superset

Changes pane can't render Staged/Unstaged diffs when git emits non-default diff prefixes (diff.mnemonicPrefix, diff.noprefix)

Author: aponcedeleonchCreated Sep 16, 2026Updated Sep 16, 2026

Where did this happen?

Desktop app (macOS)

Version

1.29.0

What happened?

With diff.mnemonicPrefix = true in my git config, every file in the Staged and Unstaged sections renders the Unable to load diff placeholder. The sidebar lists the files correctly with accurate +/- counts. Against base and Commits render fine, which is what made this confusing to diagnose.

The Changes pane parses patch text with a regex that hardcodes git's a//b/ prefixes, but git diff only emits those for some comparisons. DevTools console:

useDiffCodeViewItems.ts:256 parsePatchContent: invalid git diff header diff --git i/path/to/file.sh w/path/to/file.sh
    at useDiffCodeViewItems (useDiffCodeViewItems.ts:243)
    at DiffPane (DiffPane.tsx:228)

The regex is in the bundled parser, @pierre/[email protected]:

// package/dist/constants.js:18
const ALTERNATE_FILE_NAMES_GIT = /^diff --git (?:"a\/(.+?)"|a\/(.+?)) (?:"b\/(.+?)"|b\/(.+?))$/;

When it fails, the file gets no section in the parsed patch and useDiffCodeViewItems falls through to reason: "error".

BASE_ARGS in packages/host-service/src/trpc/router/git/utils/diff-patch.ts is ["--no-color", "--no-ext-diff", "--find-renames", "--unified=3"], which does not pin the prefixes, so git honours the user's config.

Every header form, run against that exact regex:

Section git command header emitted parses
Against base / Commits git diff <ref> HEAD diff --git a/f.txt b/f.txt yes
Unstaged git diff diff --git i/f.txt w/f.txt no
Staged git diff --cached diff --git c/f.txt i/f.txt no
Untracked git diff --no-index diff --git 1/u.txt 2/u.txt no
any, with diff.noprefix=true diff --git f.txt f.txt no

Comparing two commits is the one case git leaves at a//b/, which is exactly why Against base and Commits still work. diff.noprefix = true, a common dotfiles setting, breaks the same three sections with no prefix at all.

Secondary: the failure is unrecoverable from the UI. The placeholder offers a Retry button, but onRequest only adds the item to requestedItemIds, which ungates generated files. It never refetches. Combined with the query's staleTime: Infinity and refetchOnWindowFocus: false, an errored getDiffPatch stays errored until something else fires git:changed. Happy to split this into its own issue if you'd rather.

Steps to reproduce

  1. git config --global diff.mnemonicPrefix true (or diff.noprefix true)
  2. Open any workspace and modify a tracked file, leaving it unstaged
  3. Open the Changes pane. The file is listed under Unstaged with correct +/- counts
  4. Click it

Expected: the diff renders. Actual: Unable to load diff, and Retry does nothing.

git add the file and click it under Staged for the same result. Against base keeps working throughout.

Logs, screenshots, or recordings

Regression window: #6968 ("render the Changes pane from patches", 2026-08-30, shipped in 1.26.0) switched the pane from per-file git show blobs to parsing patch text. The old path never read a diff header, so this could not happen before 1.26.0.

Suggested fix. Pin the output-affecting config at the invocation rather than teaching the parser each user's settings:

git -c diff.mnemonicPrefix=false \
    -c diff.noprefix=false \
    -c diff.srcPrefix=a/ \
    -c diff.dstPrefix=b/ \
    diff --no-color --no-ext-diff --find-renames --unified=3

--default-prefix is the tidier one-liner and overrides all four knobs, but it needs git >= 2.45. On older git it is an unrecognised option that makes git exit non-zero, producing the same Unable to load diff from a different cause. Unknown config keys are silently ignored by every git version, so the -c form has no version floor.

diff-patch.ts is not the only programmatic git caller, so the durable place for this is the shared seam createGitEnvResolver already occupies, rather than each call site remembering. The --name-status / --numstat calls already sidestep path quoting via -z; the patch path never got an equivalent.

Possibly related, not verified: the regex does accept quoted headers, but captures the escaped literal (caf\303\251.txt) rather than the raw path that --name-status -z reports. That would make the parsed.byPath.get(file.path) lookup miss for non-ASCII filenames, giving the same placeholder with no console error. I have not reproduced it end to end.

Environment: macOS (Darwin 25.6.0), git 2.50.1 (Apple Git-155), desktop + CLI + host-service all 1.29.0.