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>
Adds a non-fatal bubblewrap check to the doctor command that reports whether
the bwrap binary is available. This is necessary for sandboxed sessions and
the check is displayed alongside other system requirement checks.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds sandbox_available param to NewSessionScreen, renders Switch/Select/
Switch widgets for sandbox on/off, filesystem access (rw/ro), and network
toggle. When bwrap is unavailable the switch is disabled and a warning label
is shown. The _policy_from staticmethod maps widget values to SandboxPolicy.
The result tuple gains SandboxPolicy | None as its fifth element. app.py
passes sandbox_available=sandbox.is_available() when opening the dialog and
unpacks the new element to forward sandbox= to create_session.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Import `hqt.sandbox` module and `SandboxPolicy` in service.py
- Add `_sandbox_policy` helper to decode persisted JSON policy from a session row
- Add `_wrap_command` helper that calls `sandbox.is_available` / `sandbox.wrap` and raises `ServiceError` when bwrap is unavailable for a sandboxed session
- Add `sandbox: SandboxPolicy | None = None` parameter to `create_session`; persist as `sandbox_json`, pass `sandboxed=` to `build_spawn_config`, wrap command before spawning
- Update `_get_resume_config` to pass `sandboxed=` to `build_resume_config` and wrap the resulting command
- Update rung-2 path in `_respawn_with_fallback` to pass `sandboxed=` and wrap the fresh-spawn command
- Add three new tests covering: sandboxed create wraps command, unsandboxed create does not wrap, and ServiceError on missing bwrap
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a nullable sandbox_json TEXT column to the sessions table via a new
version-3 migration. Updates LATEST_VERSION to 3 and adjusts existing
migration tests to account for the new column and version number.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add sandbox_skip_permission_flags class attr and sandbox_binds() method
to HarnessConfigurator ABC; thread sandboxed: bool = False through all
build_spawn_config / build_resume_config signatures. Claude and Codex
append their respective skip-permission flags when sandboxed=True; Kiro
and Generic accept the param but add no flags.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Core sandbox infrastructure for isolating harness sessions. Bind defines
read-only/writable path bindings, SandboxPolicy specifies filesystem and
network isolation modes, and is_available() is the single source of truth
for bubblewrap availability across the application.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ProjectService now rejects non-existent paths (path-validation feature), so
the TUI and integration tests must seed projects with real tmp_path dirs.
Completes the path-validation test updates beyond test_services.py.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Drive TmuxRunner against a throwaway tmux server (isolated via TMUX_TMPDIR)
so the tests never touch the user's live tmux. Register the 'tmux' pytest marker.
Skips when no tmux binary is present.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lead status-right with the focused pane's terminal title (truncated to 21
cols) and widen status-right-length to 64, restoring the preview tmux shows by
default.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a single tmux Alt+p fzf palette that opens nvim/lazygit/shell as styled
tool windows for the current session's project, or clones a fresh harness with
the same project+model. Bridges via run-shell (expands #{window_name}) into a
new 'hqt palette' CLI subcommand, since display-popup does not format-expand its
command (verified on tmux 3.6b).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hold an asyncio.Lock across the spawn->capture window for harnesses that
capture a session id, so two concurrent hqt starts in the same project
never produce overlapping capture windows. Completes the P2 capture-race
fix alongside the ambiguity guard.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When more than one rollout matches the project cwd within the capture
window, return None and warn instead of guessing the earliest. The worst
case is now a kept placeholder id, never a wrong one. Part of the P2
capture-race fix.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reject non-existent or non-directory paths with ServiceError (already
surfaced as a TUI notification), and store the resolved absolute path.
Closes the P2 finding where bad paths became dead sessions later.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add worktree support to SessionService via the hqt.git.worktree module:
- create_session(worktree_branch=...): creates the worktree before the row
(ServiceError → no row, no spawn); spawn/capture use the worktree path;
blank nickname defaults to the branch name.
- _session_path / _repo_path_for_worktree helpers route every harness-facing
path through the worktree when set, with a parent.parent repo fallback when
the project row is gone.
- attach_session rung 0: recreate a missing worktree from its branch, else
raise "delete the session".
- delete_session(remove_worktree=, force=): remove worktree before deleting
the row; removal failure keeps the row.
- worktree_state_for(session_id): None for plain/missing sessions, else the
module's WorktreeState.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add worktree_path and worktree_branch nullable columns to the Session
model, with a migration v2 that ALTERs the existing sessions table.
Tests cover fresh DB stamping at v2, v1→v2 upgrade path, and row-level
defaults (None when unset, populated when provided).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolve all three P1 items: per-operation DB sessions, ServiceError
user-facing failures, and user_version schema migrations. Add the
implementation plan doc.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>