fix: make sandboxed sessions usable for real git workflows

Address three integration gaps found in review:

- Bind ~/.gitconfig read-only in the sandbox base layer so `git commit`
  finds user.name/email inside the jail (spec §3).
- Bind a worktree session's repo common .git dir writable: the worktree's
  real gitdir lives at <repo>/.git/worktrees/<branch>, outside the bound
  cwd, so git was broken in sandboxed worktree sessions.
- doctor now gates on sandbox.is_available() (the single source of truth
  shared with the New Session dialog) rather than a raw shutil.which, so
  it agrees with the service on non-Linux platforms (spec §6).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 08:16:48 -04:00
parent 7d851dcb7e
commit 5f29418186
7 changed files with 130 additions and 26 deletions
+10 -5
View File
@@ -3,6 +3,7 @@ from unittest.mock import patch
from click.testing import CliRunner
from hqt import cli as cli_module
from hqt import sandbox
from hqt.cli import main
@@ -12,9 +13,11 @@ def _which(found: set[str]):
def test_doctor_reports_bwrap_present():
"""Test that doctor reports bubblewrap when present."""
# Patch the single shutil.which used by both cli and registry
with patch.object(
cli_module.shutil, "which", side_effect=_which({"tmux", "bwrap"})
# tmux is discovered via cli.shutil.which; bwrap availability comes from the
# shared sandbox.is_available() helper (the single source of truth).
with (
patch.object(cli_module.shutil, "which", side_effect=_which({"tmux", "bwrap"})),
patch.object(sandbox, "is_available", return_value=True),
):
result = CliRunner().invoke(main, ["doctor"])
assert result.exit_code == 0
@@ -23,8 +26,10 @@ def test_doctor_reports_bwrap_present():
def test_doctor_reports_bwrap_missing():
"""Test that doctor reports bubblewrap as missing when not present."""
# Patch the single shutil.which used by both cli and registry
with patch.object(cli_module.shutil, "which", side_effect=_which({"tmux"})):
with (
patch.object(cli_module.shutil, "which", side_effect=_which({"tmux"})),
patch.object(sandbox, "is_available", return_value=False),
):
result = CliRunner().invoke(main, ["doctor"])
assert result.exit_code == 0
assert "bubblewrap: ✗" in result.output