Files
Aperant/tests/test_prompt_generator.py
T
kaigler fe5cc582b8 fix: add worktree isolation warning to prevent agent escape (#1528)
* fix: add worktree isolation warning to prevent agent escape

When agents run in isolated worktrees, they would sometimes escape
isolation by running `cd /path/to/main/project` after seeing absolute
paths in spec.md or context.json files.

This fix:
- Adds worktree detection in prompt_generator.py
- Generates prominent isolation warning when in worktree mode
- Shows forbidden parent path explicitly
- Adds "Isolation Mode: WORKTREE" indicator to environment context
- Adds worktree isolation section to coder.md prompt

Fixes #1444

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test: add unit tests for detect_worktree_isolation and PR review pattern

- Add TestDetectWorktreeIsolation class with 9 tests covering:
  - New worktree pattern (.auto-claude/worktrees/tasks/) on Unix/Windows
  - Legacy worktree pattern (.worktrees/) on Unix/Windows
  - PR review worktree pattern (.auto-claude/github/pr/worktrees/)
  - Non-worktree paths (direct mode)
  - Edge cases (root path, regular .auto-claude dir)
- Addresses PR review feedback for missing test coverage

* fix: address PR #1528 review findings

- Remove dead code detect_worktree_mode() superseded by detect_worktree_isolation()
- Remove TestDetectWorktreeMode test class and import for removed function
- Fix terminology FORBIDDEN → FORBIDDEN PATH in coder.md and qa_fixer.md
  to match generate_worktree_isolation_warning() output

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Andy <119136210+AndyMik90@users.noreply.github.com>
2026-01-30 11:44:02 +01:00

186 lines
7.1 KiB
Python

"""
Tests for prompt_generator module functions.
Tests for worktree detection and environment context generation.
"""
from pathlib import Path
# Note: sys.path manipulation is handled by conftest.py line 46
from prompts_pkg.prompt_generator import (
detect_worktree_isolation,
generate_environment_context,
)
class TestDetectWorktreeIsolation:
"""Tests for detect_worktree_isolation function (core worktree detection)."""
def test_new_worktree_pattern_unix(self):
"""Test detection of .auto-claude/worktrees/tasks/ pattern on Unix."""
project_dir = Path("/opt/dev/project/.auto-claude/worktrees/tasks/001-feature")
is_worktree, parent_path = detect_worktree_isolation(project_dir)
assert is_worktree is True
assert parent_path is not None
# Parent should be the project root before .auto-claude
assert str(parent_path).endswith("project") or "project" in str(parent_path)
def test_new_worktree_pattern_windows(self):
"""Test detection of .auto-claude/worktrees/tasks/ pattern on Windows."""
project_dir = Path("E:/projects/myapp/.auto-claude/worktrees/tasks/009-audit")
is_worktree, parent_path = detect_worktree_isolation(project_dir)
assert is_worktree is True
assert parent_path is not None
def test_legacy_worktree_pattern_unix(self):
"""Test detection of .worktrees/ legacy pattern on Unix."""
project_dir = Path("/opt/dev/project/.worktrees/001-feature")
is_worktree, parent_path = detect_worktree_isolation(project_dir)
assert is_worktree is True
assert parent_path is not None
def test_legacy_worktree_pattern_windows(self):
"""Test detection of .worktrees/ legacy pattern on Windows."""
project_dir = Path("C:/projects/myapp/.worktrees/009-audit")
is_worktree, parent_path = detect_worktree_isolation(project_dir)
assert is_worktree is True
assert parent_path is not None
def test_pr_review_worktree_pattern(self):
"""Test detection of PR review worktree pattern (.auto-claude/github/pr/worktrees/)."""
project_dir = Path("/opt/dev/project/.auto-claude/github/pr/worktrees/pr-123")
is_worktree, parent_path = detect_worktree_isolation(project_dir)
assert is_worktree is True
assert parent_path is not None
# Parent should be before the .auto-claude marker
assert "project" in str(parent_path) or str(parent_path).endswith("project")
def test_pr_review_worktree_pattern_windows(self):
"""Test PR review worktree pattern on Windows."""
project_dir = Path("E:/projects/myapp/.auto-claude/github/pr/worktrees/pr-456")
is_worktree, parent_path = detect_worktree_isolation(project_dir)
assert is_worktree is True
assert parent_path is not None
def test_not_in_worktree(self):
"""Test when not in any worktree (direct mode)."""
project_dir = Path("/opt/dev/project")
is_worktree, parent_path = detect_worktree_isolation(project_dir)
assert is_worktree is False
assert parent_path is None
def test_regular_auto_claude_dir(self):
"""Test that regular .auto-claude dir is NOT detected as worktree."""
# Just having .auto-claude in path doesn't make it a worktree
project_dir = Path("/opt/dev/project/.auto-claude/specs/001-feature")
is_worktree, parent_path = detect_worktree_isolation(project_dir)
assert is_worktree is False
assert parent_path is None
def test_empty_or_root_path(self):
"""Test edge case with minimal paths."""
# Root path
project_dir = Path("/")
is_worktree, parent_path = detect_worktree_isolation(project_dir)
assert is_worktree is False
assert parent_path is None
class TestGenerateEnvironmentContext:
"""Tests for generate_environment_context function."""
def test_context_includes_worktree_warning(self):
"""Test that worktree isolation warning is included when in worktree."""
spec_dir = Path("/opt/dev/project/.auto-claude/worktrees/tasks/001-feature/.auto-claude/specs/001-feature")
project_dir = Path("/opt/dev/project/.auto-claude/worktrees/tasks/001-feature")
context = generate_environment_context(project_dir, spec_dir)
# Verify worktree warning is present
assert "ISOLATED WORKTREE - CRITICAL" in context
assert "FORBIDDEN PATH:" in context
assert "escape isolation" in context.lower()
def test_context_no_worktree_warning_in_direct_mode(self):
"""Test that worktree warning is NOT included in direct mode."""
spec_dir = Path("/opt/dev/project/.auto-claude/specs/001-feature")
project_dir = Path("/opt/dev/project")
context = generate_environment_context(project_dir, spec_dir)
# Verify worktree warning is NOT present
assert "ISOLATED WORKTREE - CRITICAL" not in context
assert "FORBIDDEN PATH:" not in context
def test_context_includes_basic_environment(self):
"""Test that basic environment information is always included."""
spec_dir = Path("/opt/dev/project/.auto-claude/specs/001-feature")
project_dir = Path("/opt/dev/project")
context = generate_environment_context(project_dir, spec_dir)
# Verify basic sections
assert "## YOUR ENVIRONMENT" in context
assert "**Working Directory:**" in context
assert "**Spec Location:**" in context
assert "implementation_plan.json" in context
assert "PATH CONFUSION PREVENTION" in context
def test_context_windows_worktree(self):
"""Test worktree warning with Windows paths (from ticket ACS-394)."""
# This is the exact scenario from the bug report
spec_dir = Path(
"E:/projects/x/.auto-claude/worktrees/tasks/009-audit"
"/.auto-claude/specs/009-audit"
)
project_dir = Path(
"E:/projects/x/.auto-claude/worktrees/tasks/009-audit"
)
context = generate_environment_context(project_dir, spec_dir)
# Verify worktree warning includes the Windows path
# Note: Path resolution on Windows converts forward slashes to backslashes
assert "ISOLATED WORKTREE - CRITICAL" in context
assert "projects" in context and "x" in context
def test_context_forbidden_path_examples(self):
"""Test that forbidden path is shown and critical rules are included."""
spec_dir = Path(
"/opt/dev/project/.auto-claude/worktrees/tasks/001-feature"
"/.auto-claude/specs/001-feature"
)
project_dir = Path(
"/opt/dev/project/.auto-claude/worktrees/tasks/001-feature"
)
context = generate_environment_context(project_dir, spec_dir)
# Verify forbidden parent path is shown
assert "FORBIDDEN PATH:" in context
# Verify rules section exists with prohibition
assert "Rules:" in context
assert "**NEVER**" in context # Explicit prohibition
# Verify consequences are explained
assert "WRONG branch" in context