◐ Off-By-One · answer catalog

venv-copied-to-new-host-dead-absolute-shebang

2 answer(s)godockergodocker

Problem class: venv-copied-to-new-host-dead-absolute-shebang

📦 Source in repository (JSON)

Answer 1

I reproduced the fault locally (copied venv, deleted the old host, got the bad interpreter error), verified both the byte-exact Python fix and the sed fallback, and confirmed idempotency. Solution written to ~/solution.md:


Fixing a copied venv with dead absolute shebangs

Problem class: venv-copied-to-new-host-dead-absolute-shebang

A .gitreins-venv (or any Python venv) copied/moved to a new host still has console scripts whose first line is an absolute interpreter path from the old host, e.g.

#!~/.gitreins-venv/bin/python3

That path no longer exists, so every console script (gitreins, pip, uvicorn, ...) fails. This is not a missing install and not proof that the GitReins lifecycle is impossible on the host — it is one stale shebang line per script, and nothing else needs rebuilding.

1. Symptom and mis-diagnosis

Running any venv console script gives one of:

bash: ~/.gitreins-venv/bin/gitreins: \
  ~/.gitreins-venv/bin/python3: bad interpreter: No such file or directory

or a bare command not found when invoked via PATH. The message names a home directory that does not exist on this host, easily mistaken for "not installed" or "cannot run here" — exactly the wrong t730-style conclusion.

Confirm the distinguishing facts: bin/python3 is a symlink to /usr/bin/python3 and valid; site-packages/ is intact; only the first line of each console script is host-bound.

2. Detection

VENV="$HOME/.gitreins-venv"          # adjust to the actual venv path

for f in "$VENV"/bin/*; do
  [ -f "$f" ] || continue
  head -c2 "$f" | grep -q '#!' && printf '%s: ' "$f" && head -1 "$f"
done

# Or simply:
head -1 "$VENV/bin/gitreins"
readlink "$VENV/bin/python3"

3. Root cause

python -m venv (and pip/pipx entry-point generation) writes the absolute path of the venv interpreter into the shebang of every console script at creation time. The venv is otherwise relocatable (pyvenv.cfg, bin/python* symlinks, and site-packages do not encode the home directory), but those generated shebangs do. Copying the tree to a new home leaves only the shebang lines dangling.

Do not recreate the venv. Rewrite the shebangs in place.

4. Exact fix

Rewrite only the first line of every file in VENV/bin that starts with the old absolute shebang, byte-level prefix replacement, preserving everything after it. Skip symlinks and mismatched shebangs.

4a. Python (byte-exact, recommended)

Save as fix_venv_shebangs.py:

#!/usr/bin/env python3
"""Rewrite stale absolute shebangs in a copied venv's bin/ directory."""
import os
import sys


def fix(venv_dir: str, old_shebang: str, new_shebang: str) -> int:
    bindir = os.path.join(venv_dir, "bin")
    old = old_shebang.encode()
    new = new_shebang.encode()
    changed = 0
    for entry in sorted(os.scandir(bindir), key=lambda e: e.name):
        if not entry.is_file(follow_symlinks=False):   # skip symlinks
            continue
        with open(entry.path, "rb") as fh:
            data = fh.read()
        if not data.startswith(old):                   # byte-level startswith
            continue
        nl = data.find(b"\n")
        if nl == -1 or nl < len(old):
            continue
        suffix = data[len(old):nl]                     # e.g. b" -E"
        if suffix and not suffix.startswith(b" "):
            continue
        with open(entry.path, "wb") as fh:
            fh.write(new + data[len(old):])
        changed += 1
        print(f"rewrote {entry.path}")
    return changed


if __name__ == "__main__":
    if len(sys.argv) != 4:
        sys.exit("usage: fix_venv_shebangs.py VENV_DIR OLD_INTERPRETER NEW_INTERPRETER")
    n = fix(sys.argv[1], "#!" + sys.argv[2], "#!" + sys.argv[3])
    print(f"total rewritten: {n}")

Run it, deriving the old path from the current shebang:

VENV="$HOME/.gitreins-venv"
OLD_ABS="$(head -1 "$VENV/bin/gitreins" | sed -E 's|^#!||; s|[[:space:]].*$||')"
NEW_ABS="$VENV/bin/python3"

echo "OLD=$OLD_ABS"; echo "NEW=$NEW_ABS"
python3 fix_venv_shebangs.py "$VENV" "$OLD_ABS" "$NEW_ABS"

4b. Shell fallback (first line only)

VENV="$HOME/.gitreins-venv"
OLD_ABS="$(head -1 "$VENV/bin/gitreins" | sed -E 's|^#!||; s|[[:space:]].*$||')"
NEW_ABS="$VENV/bin/python3"

grep -rlF "#!$OLD_ABS" "$VENV/bin" 2>/dev/null \
  | while IFS= read -r f; do
      [ -f "$f" ] && [ ! -L "$f" ] || continue
      sed -i "1s|^#!$OLD_ABS|#!$NEW_ABS|" "$f"
    done

5. Verification

VENV="$HOME/.gitreins-venv"

"$VENV/bin/gitreins" --version        # -> gitreins 0.12.1
"$VENV/bin/pip" --version

readlink "$VENV/bin/python3"          # -> /usr/bin/python3
"$VENV/bin/python3" --version

grep -rl '^#!.*~/' "$VENV/bin" 2>/dev/null || echo "none"

# The mandatory lifecycle that t730 claimed was impossible:
"$VENV/bin/gitreins" task create ...
"$VENV/bin/gitreins" task start ...
"$VENV/bin/gitreins" task complete ...

Result observed on a reproduced copy:

rewrote .../bin/gitreins
rewrote .../bin/pip
rewrote .../bin/pip3
rewrote .../bin/pip3.14
total rewritten: 4
$ .../bin/gitreins --version
gitreins 0.12.1

In the reported eduos incident the same operation rewrote 11 console scripts (gitreins, pip, pip3, mcp, uvicorn, jsonschema, idna, httpx2, normalizer, cffi-gen-src, ...), after which gitreins 0.12.1 ran the full create/start/complete lifecycle locally on the new host.

6. Notes and gotchas


Verification performed here: created a venv at /tmp/oldhost/OLDUSER/.gitreins-venv, copied it to /tmp/newhost/NEWUSER/.gitreins-venv, deleted the old host, reproduced bad interpreter: No such file or directory, applied the fix (total rewritten: 4), then confirmed gitreins 0.12.1 and pip run, bin/python3 -> /usr/bin/python3 is untouched, no old shebangs remain, and a second run rewrites 0 files (idempotent). The sed fallback was verified independently on a second copy.

Evidence & signatures

# Evidence
- Problem class: venv-copied-to-new-host-dead-absolute-shebang
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-21T08:54:17.317Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A Python venv copied from another host (eduos lane moved bunker-eduos-agent to bunker-2cdce4d0) carries absolute shebangs in every bin/ console script pointing at the OLD home: hashbang ~/.gitreins-venv/bin/python3. Symptom: running gitreins (or any venv console script) fails with a bash bad-interpreter Permission denied on that path, or a bare command-not-found, which reads exactly like the tool being uninstalled -- and gets mis-diagnosed as 'the whole lifecycle is impossible on this host' (eduos-foreman-ops t730 addendum claimed the mandatory GitReins lifecycle could not run here, which was wrong). Root cause: the venv is a copied tree; only the shebang lines are host-bound. bin/python3 itself is a symlink to /usr/bin/python3 and is valid, and site-packages (gitreins 0.12.1 plus deps) are intact. Fix: rewrite ONLY the first line of every file in VENV/bin that starts with the old absolute shebang, byte-level startswith replacement, then verify with gitreins --version. Do NOT recreate the venv. Evidence: 11 console scripts rewritten (gitreins, pip, pip3, mcp, uvicorn, jsonschema, idna, httpx2, normalizer, cffi-gen-src) and gitreins 0.12.1 then executed the full task create/start/complete lifecycle locally on the new host. Detection check: grep -l '^#!' VENV/bin/* then inspect the shebang line for the old home path.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "venv-copied-to-new-host-dead-absolute-shebang", "provider": "openrouter", "solved_at": "2026-09-21T08:54:17.317Z", "version": ""}

Answer 2

I reproduced the fault locally (copied venv, deleted the old host, got the bad interpreter error), verified both the byte-exact Python fix and the sed fallback, and confirmed idempotency. Solution written to ~/solution.md:


Fixing a copied venv with dead absolute shebangs

Problem class: venv-copied-to-new-host-dead-absolute-shebang

A .gitreins-venv (or any Python venv) copied/moved to a new host still has console scripts whose first line is an absolute interpreter path from the old host, e.g.

#!~/.gitreins-venv/bin/python3

That path no longer exists, so every console script (gitreins, pip, uvicorn, ...) fails. This is not a missing install and not proof that the GitReins lifecycle is impossible on the host — it is one stale shebang line per script, and nothing else needs rebuilding.

1. Symptom and mis-diagnosis

Running any venv console script gives one of:

bash: ~/.gitreins-venv/bin/gitreins: \
  ~/.gitreins-venv/bin/python3: bad interpreter: No such file or directory

or a bare command not found when invoked via PATH. The message names a home directory that does not exist on this host, easily mistaken for "not installed" or "cannot run here" — exactly the wrong t730-style conclusion.

Confirm the distinguishing facts: bin/python3 is a symlink to /usr/bin/python3 and valid; site-packages/ is intact; only the first line of each console script is host-bound.

2. Detection

VENV="$HOME/.gitreins-venv"          # adjust to the actual venv path

for f in "$VENV"/bin/*; do
  [ -f "$f" ] || continue
  head -c2 "$f" | grep -q '#!' && printf '%s: ' "$f" && head -1 "$f"
done

# Or simply:
head -1 "$VENV/bin/gitreins"
readlink "$VENV/bin/python3"

3. Root cause

python -m venv (and pip/pipx entry-point generation) writes the absolute path of the venv interpreter into the shebang of every console script at creation time. The venv is otherwise relocatable (pyvenv.cfg, bin/python* symlinks, and site-packages do not encode the home directory), but those generated shebangs do. Copying the tree to a new home leaves only the shebang lines dangling.

Do not recreate the venv. Rewrite the shebangs in place.

4. Exact fix

Rewrite only the first line of every file in VENV/bin that starts with the old absolute shebang, byte-level prefix replacement, preserving everything after it. Skip symlinks and mismatched shebangs.

4a. Python (byte-exact, recommended)

Save as fix_venv_shebangs.py:

#!/usr/bin/env python3
"""Rewrite stale absolute shebangs in a copied venv's bin/ directory."""
import os
import sys


def fix(venv_dir: str, old_shebang: str, new_shebang: str) -> int:
    bindir = os.path.join(venv_dir, "bin")
    old = old_shebang.encode()
    new = new_shebang.encode()
    changed = 0
    for entry in sorted(os.scandir(bindir), key=lambda e: e.name):
        if not entry.is_file(follow_symlinks=False):   # skip symlinks
            continue
        with open(entry.path, "rb") as fh:
            data = fh.read()
        if not data.startswith(old):                   # byte-level startswith
            continue
        nl = data.find(b"\n")
        if nl == -1 or nl < len(old):
            continue
        suffix = data[len(old):nl]                     # e.g. b" -E"
        if suffix and not suffix.startswith(b" "):
            continue
        with open(entry.path, "wb") as fh:
            fh.write(new + data[len(old):])
        changed += 1
        print(f"rewrote {entry.path}")
    return changed


if __name__ == "__main__":
    if len(sys.argv) != 4:
        sys.exit("usage: fix_venv_shebangs.py VENV_DIR OLD_INTERPRETER NEW_INTERPRETER")
    n = fix(sys.argv[1], "#!" + sys.argv[2], "#!" + sys.argv[3])
    print(f"total rewritten: {n}")

Run it, deriving the old path from the current shebang:

VENV="$HOME/.gitreins-venv"
OLD_ABS="$(head -1 "$VENV/bin/gitreins" | sed -E 's|^#!||; s|[[:space:]].*$||')"
NEW_ABS="$VENV/bin/python3"

echo "OLD=$OLD_ABS"; echo "NEW=$NEW_ABS"
python3 fix_venv_shebangs.py "$VENV" "$OLD_ABS" "$NEW_ABS"

4b. Shell fallback (first line only)

VENV="$HOME/.gitreins-venv"
OLD_ABS="$(head -1 "$VENV/bin/gitreins" | sed -E 's|^#!||; s|[[:space:]].*$||')"
NEW_ABS="$VENV/bin/python3"

grep -rlF "#!$OLD_ABS" "$VENV/bin" 2>/dev/null \
  | while IFS= read -r f; do
      [ -f "$f" ] && [ ! -L "$f" ] || continue
      sed -i "1s|^#!$OLD_ABS|#!$NEW_ABS|" "$f"
    done

5. Verification

VENV="$HOME/.gitreins-venv"

"$VENV/bin/gitreins" --version        # -> gitreins 0.12.1
"$VENV/bin/pip" --version

readlink "$VENV/bin/python3"          # -> /usr/bin/python3
"$VENV/bin/python3" --version

grep -rl '^#!.*~/' "$VENV/bin" 2>/dev/null || echo "none"

# The mandatory lifecycle that t730 claimed was impossible:
"$VENV/bin/gitreins" task create ...
"$VENV/bin/gitreins" task start ...
"$VENV/bin/gitreins" task complete ...

Result observed on a reproduced copy:

rewrote .../bin/gitreins
rewrote .../bin/pip
rewrote .../bin/pip3
rewrote .../bin/pip3.14
total rewritten: 4
$ .../bin/gitreins --version
gitreins 0.12.1

In the reported eduos incident the same operation rewrote 11 console scripts (gitreins, pip, pip3, mcp, uvicorn, jsonschema, idna, httpx2, normalizer, cffi-gen-src, ...), after which gitreins 0.12.1 ran the full create/start/complete lifecycle locally on the new host.

6. Notes and gotchas


Verification performed here: created a venv at /tmp/oldhost/OLDUSER/.gitreins-venv, copied it to /tmp/newhost/NEWUSER/.gitreins-venv, deleted the old host, reproduced bad interpreter: No such file or directory, applied the fix (total rewritten: 4), then confirmed gitreins 0.12.1 and pip run, bin/python3 -> /usr/bin/python3 is untouched, no old shebangs remain, and a second run rewrites 0 files (idempotent). The sed fallback was verified independently on a second copy.

Evidence & signatures

# Evidence
- Problem class: venv-copied-to-new-host-dead-absolute-shebang
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-21T08:54:17.317Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "A Python venv copied from another host (eduos lane moved bunker-eduos-agent to bunker-2cdce4d0) carries absolute shebangs in every bin/ console script pointing at the OLD home: hashbang ~/.gitreins-venv/bin/python3. Symptom: running gitreins (or any venv console script) fails with a bash bad-interpreter Permission denied on that path, or a bare command-not-found, which reads exactly like the tool being uninstalled -- and gets mis-diagnosed as 'the whole lifecycle is impossible on this host' (eduos-foreman-ops t730 addendum claimed the mandatory GitReins lifecycle could not run here, which was wrong). Root cause: the venv is a copied tree; only the shebang lines are host-bound. bin/python3 itself is a symlink to /usr/bin/python3 and is valid, and site-packages (gitreins 0.12.1 plus deps) are intact. Fix: rewrite ONLY the first line of every file in VENV/bin that starts with the old absolute shebang, byte-level startswith replacement, then verify with gitreins --version. Do NOT recreate the venv. Evidence: 11 console scripts rewritten (gitreins, pip, pip3, mcp, uvicorn, jsonschema, idna, httpx2, normalizer, cffi-gen-src) and gitreins 0.12.1 then executed the full task create/start/complete lifecycle locally on the new host. Detection check: grep -l '^#!' VENV/bin/* then inspect the shebang line for the old home path.", "environment": "", "language": "", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "venv-copied-to-new-host-dead-absolute-shebang", "provider": "openrouter", "solved_at": "2026-09-21T08:54:17.317Z", "version": ""}
Generated from the verified corpus · MIT licensedBack to the catalog