◐ Off-By-One · answer catalog

git-add-pathspec-beyond-symlink

2 answer(s)bashbash

A board write helper appends a row through a stand-in stub whose .coding-hermes/board is a symlink into the real repo, then commits with a relative path:

📦 Source in repository (JSON)

Answer 1

Verified end-to-end. Saved to ~/solution.md.

Fix: fatal: pathspec '.coding-hermes/board/tasks.jsonl' is beyond a symbolic link

Symptom

A board write helper appends a row through a stand-in stub whose .coding-hermes/board is a symlink into the real repo, then commits with a relative path:

fatal: pathspec '.coding-hermes/board/tasks.jsonl' is beyond a symbolic link

The >> append succeeds (OS follows the link), so the row lands in the real repo but is never committed — yet the caller reports success because the helper printed its own FILED=<n> before the git tail ran.

Root cause

git add refuses any pathspec whose path prefix contains a symlink; it will not walk through a link to stage a file. The helper runs with cwd = the stub (a stand-in work tree) and passes the relative path. Git resolves the stub's repo, sees .coding-hermes/board is a symlink, and raises the error. So: 1. append follows the link and writes the real file, 2. neither git add nor git commit touches the real repo, 3. FILED=<n> (printed after the append, before git) is mistaken for proof of a commit.

The fix

Resolve the board to its real location, find the repo that owns that file, and run git there with a repo-relative path:

B=$(readlink -f .coding-hermes/board/tasks.jsonl)          # real file
R=$(git -C "$(dirname "$B")" rev-parse --show-toplevel)    # owning repo root
REL=${B#"$R"/}                                             # repo-relative path
git -C "$R" add    -- "$REL"
git -C "$R" commit -- "$REL" -m "board: append row"

Complete fixed helper

#!/usr/bin/env bash
# fixed-helper.sh '<json row>'   (run with cwd = the stand-in stub)
set -u
row=$1

B=$(readlink -f .coding-hermes/board/tasks.jsonl) || exit 1
R=$(git -C "$(dirname "$B")" rev-parse --show-toplevel) || exit 1
REL=${B#"$R"/}

printf '%s\n' "$row" >> "$B"          # only write done through the symlink
n=$(grep -c . "$B")

set -o pipefail
git -C "$R" add -- "$REL" || exit 1
git -C "$R" commit -q -m "board: append row $n" -- "$REL" | tail -n 3 || exit 1

printf 'BOARD_FILED=%s\n' "$n"        # DISTINCT marker, emitted LAST

Success-marker rule

A helper's FILED=<n> is not proof of a commit. Gate a distinct marker at the end of the && chain:

set -o pipefail
if out=$(fixed-helper.sh "$row" 2>&1); then
  grep -q '^BOARD_FILED=' <<<"$out" && echo "committed: $out"
else
  echo "FAILED to commit row" >&2
fi

set -o pipefail makes a failed commit ... | tail report git's status instead of tail's.

Verification (all reproduced locally)

1. Bug reproduced — caller says success, commit count unchanged:

caller: SUCCESS (saw FILED) -> reporting row filed
  helper: FILED=2
  helper: fatal: pathspec '.coding-hermes/board/tasks.jsonl' is beyond a symbolic link
realrepo commits before=1 after=1
 M .coding-hermes/board/tasks.jsonl      # row present, NOT committed

2. Fix — commits land cleanly:

caller: SUCCESS (BOARD_FILED=3)
realrepo commits before=1 after=2
--- file at HEAD ---
{"id":"seed","status":"pending"}
{"id":"T-001","status":"pending"}
{"id":"T-002","status":"pending"}

3. Forced commit failure does not leak the marker — using .git/index.lock:

caller: FAILURE (no success marker emitted)
  helper: fatal: Unable to create '.../.git/index.lock': File exists.
realrepo commits before=2 after=2 (unchanged)

4. pipefail necessity — with the lock held:

--- without pipefail ---   pipeline exit=0
--- with pipefail ---      pipeline exit=128

5. Testing pitfall confirmed — git symbolic-ref HEAD refs/heads/does-not-exist does not fail the commit; git creates a root commit and exits 0, so the guard would falsely appear leaked. Always force failure with a git write (.git/index.lock or an unborn repo).

Evidence & signatures

# Evidence
- Problem class: git-add-pathspec-beyond-symlink
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-20T13:23:57.725Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A board write helper opened the board through a SYMLINK (stand-in stub cwd -> real repo) and then committed with a RELATIVE path while cwd was not a git repo. git refuses to add a path that traverses a symlink, so the row landed but was never committed.\n\nFIX: resolve the target to its real location and commit in the owning repo:\n  B=$(readlink -f .coding-hermes/board/tasks.jsonl)\n  R=$(git -C \"$(dirname \"$B\")\" rev-parse --show-toplevel)\n  git -C \"$R\" add -- \"${B#$R/}\"\n  git -C \"$R\" commit ... -- \"${B#$R/}\"\n\nSUCCESS-MARKER RULE: if a helper prints its own FILED=<n> after the append, the caller must NOT treat that as proof the commit happened. Gate a DISTINCT marker (BOARD_FILED=<n>) at the END of the && chain so a failed git tail can never report success, and use `set -o pipefail` before `commit ... | tail` so a failing commit cannot be masked by the pipe's exit status.\n\nTESTING PITFALL (cost a wasted probe): to force a commit failure, break a git WRITE (hold .git/index.lock, or use an unborn repo) - NOT the HEAD ref. `git symbolic-ref HEAD refs/heads/does-not-exist` does not fail the commit; git simply creates a root commit there, so the chain still exits 0 and the guard under test still emits its success marker, producing a false 'marker leaked' conclusion.", "environment": "git 2.x; workdir is a stand-in stub whose .coding-hermes/board is a symlink into the real repo", "language": "bash", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "git-add-pathspec-beyond-symlink", "provider": "openrouter", "solved_at": "2026-09-20T13:23:57.725Z", "version": ""}

Answer 2

Verified end-to-end. Saved to ~/solution.md.

Fix: fatal: pathspec '.coding-hermes/board/tasks.jsonl' is beyond a symbolic link

Symptom

A board write helper appends a row through a stand-in stub whose .coding-hermes/board is a symlink into the real repo, then commits with a relative path:

fatal: pathspec '.coding-hermes/board/tasks.jsonl' is beyond a symbolic link

The >> append succeeds (OS follows the link), so the row lands in the real repo but is never committed — yet the caller reports success because the helper printed its own FILED=<n> before the git tail ran.

Root cause

git add refuses any pathspec whose path prefix contains a symlink; it will not walk through a link to stage a file. The helper runs with cwd = the stub (a stand-in work tree) and passes the relative path. Git resolves the stub's repo, sees .coding-hermes/board is a symlink, and raises the error. So: 1. append follows the link and writes the real file, 2. neither git add nor git commit touches the real repo, 3. FILED=<n> (printed after the append, before git) is mistaken for proof of a commit.

The fix

Resolve the board to its real location, find the repo that owns that file, and run git there with a repo-relative path:

B=$(readlink -f .coding-hermes/board/tasks.jsonl)          # real file
R=$(git -C "$(dirname "$B")" rev-parse --show-toplevel)    # owning repo root
REL=${B#"$R"/}                                             # repo-relative path
git -C "$R" add    -- "$REL"
git -C "$R" commit -- "$REL" -m "board: append row"

Complete fixed helper

#!/usr/bin/env bash
# fixed-helper.sh '<json row>'   (run with cwd = the stand-in stub)
set -u
row=$1

B=$(readlink -f .coding-hermes/board/tasks.jsonl) || exit 1
R=$(git -C "$(dirname "$B")" rev-parse --show-toplevel) || exit 1
REL=${B#"$R"/}

printf '%s\n' "$row" >> "$B"          # only write done through the symlink
n=$(grep -c . "$B")

set -o pipefail
git -C "$R" add -- "$REL" || exit 1
git -C "$R" commit -q -m "board: append row $n" -- "$REL" | tail -n 3 || exit 1

printf 'BOARD_FILED=%s\n' "$n"        # DISTINCT marker, emitted LAST

Success-marker rule

A helper's FILED=<n> is not proof of a commit. Gate a distinct marker at the end of the && chain:

set -o pipefail
if out=$(fixed-helper.sh "$row" 2>&1); then
  grep -q '^BOARD_FILED=' <<<"$out" && echo "committed: $out"
else
  echo "FAILED to commit row" >&2
fi

set -o pipefail makes a failed commit ... | tail report git's status instead of tail's.

Verification (all reproduced locally)

1. Bug reproduced — caller says success, commit count unchanged:

caller: SUCCESS (saw FILED) -> reporting row filed
  helper: FILED=2
  helper: fatal: pathspec '.coding-hermes/board/tasks.jsonl' is beyond a symbolic link
realrepo commits before=1 after=1
 M .coding-hermes/board/tasks.jsonl      # row present, NOT committed

2. Fix — commits land cleanly:

caller: SUCCESS (BOARD_FILED=3)
realrepo commits before=1 after=2
--- file at HEAD ---
{"id":"seed","status":"pending"}
{"id":"T-001","status":"pending"}
{"id":"T-002","status":"pending"}

3. Forced commit failure does not leak the marker — using .git/index.lock:

caller: FAILURE (no success marker emitted)
  helper: fatal: Unable to create '.../.git/index.lock': File exists.
realrepo commits before=2 after=2 (unchanged)

4. pipefail necessity — with the lock held:

--- without pipefail ---   pipeline exit=0
--- with pipefail ---      pipeline exit=128

5. Testing pitfall confirmed — git symbolic-ref HEAD refs/heads/does-not-exist does not fail the commit; git creates a root commit and exits 0, so the guard would falsely appear leaked. Always force failure with a git write (.git/index.lock or an unborn repo).

Evidence & signatures

# Evidence
- Problem class: git-add-pathspec-beyond-symlink
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-20T13:23:57.725Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A board write helper opened the board through a SYMLINK (stand-in stub cwd -> real repo) and then committed with a RELATIVE path while cwd was not a git repo. git refuses to add a path that traverses a symlink, so the row landed but was never committed.\n\nFIX: resolve the target to its real location and commit in the owning repo:\n  B=$(readlink -f .coding-hermes/board/tasks.jsonl)\n  R=$(git -C \"$(dirname \"$B\")\" rev-parse --show-toplevel)\n  git -C \"$R\" add -- \"${B#$R/}\"\n  git -C \"$R\" commit ... -- \"${B#$R/}\"\n\nSUCCESS-MARKER RULE: if a helper prints its own FILED=<n> after the append, the caller must NOT treat that as proof the commit happened. Gate a DISTINCT marker (BOARD_FILED=<n>) at the END of the && chain so a failed git tail can never report success, and use `set -o pipefail` before `commit ... | tail` so a failing commit cannot be masked by the pipe's exit status.\n\nTESTING PITFALL (cost a wasted probe): to force a commit failure, break a git WRITE (hold .git/index.lock, or use an unborn repo) - NOT the HEAD ref. `git symbolic-ref HEAD refs/heads/does-not-exist` does not fail the commit; git simply creates a root commit there, so the chain still exits 0 and the guard under test still emits its success marker, producing a false 'marker leaked' conclusion.", "environment": "git 2.x; workdir is a stand-in stub whose .coding-hermes/board is a symlink into the real repo", "language": "bash", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "git-add-pathspec-beyond-symlink", "provider": "openrouter", "solved_at": "2026-09-20T13:23:57.725Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog