Fahad
2025-06-11 07:09:28 +04:00
parent ede44cc1d2
commit 14ccbede43
6 changed files with 42 additions and 17 deletions

View File

@@ -31,7 +31,9 @@ services:
environment:
- GEMINI_API_KEY=${GEMINI_API_KEY}
- REDIS_URL=redis://redis:6379/0
- WORKSPACE_ROOT=${WORKSPACE_ROOT:-/workspace}
# Use HOME not PWD: Claude needs access to any absolute file path, not just current project,
# and Claude Code could be running from multiple locations at the same time
- WORKSPACE_ROOT=${WORKSPACE_ROOT:-${HOME}}
volumes:
- ${HOME:-/tmp}:/workspace:ro
stdin_open: true

View File

@@ -37,8 +37,12 @@ GEMINI_API_KEY=$API_KEY_VALUE
# Redis configuration (automatically set for Docker Compose)
REDIS_URL=redis://redis:6379/0
# Workspace root (automatically set for Docker Compose)
WORKSPACE_ROOT=/workspace
# Workspace root - host path that maps to /workspace in container
# This should be the host directory path that contains all files Claude might reference
# We use $HOME (not $PWD) because Claude needs access to ANY absolute file path,
# not just files within the current project directory. Additionally, Claude Code
# could be running from multiple locations at the same time.
WORKSPACE_ROOT=$HOME
EOF
echo "✅ Created .env file with Redis configuration"
echo ""

View File

@@ -235,9 +235,9 @@ class TestCollaborationWorkflow:
)
response = json.loads(result[0].text)
assert (
response["status"] == "requires_clarification"
), "Should request clarification when asked about dependencies without package files"
assert response["status"] == "requires_clarification", (
"Should request clarification when asked about dependencies without package files"
)
clarification = json.loads(response["content"])
assert "package.json" in str(clarification["files_needed"]), "Should specifically request package.json"

View File

@@ -300,7 +300,7 @@ class TestConversationFlow:
# REQUEST 6: Try to exceed MAX_CONVERSATION_TURNS limit - should fail
turns_at_limit = [
ConversationTurn(
role="assistant" if i % 2 == 0 else "user", content=f"Turn {i+1}", timestamp="2023-01-01T00:00:30Z"
role="assistant" if i % 2 == 0 else "user", content=f"Turn {i + 1}", timestamp="2023-01-01T00:00:30Z"
)
for i in range(MAX_CONVERSATION_TURNS)
]
@@ -423,7 +423,9 @@ class TestConversationFlow:
# Mock context with current turns
turns = [
ConversationTurn(
role="user" if i % 2 == 0 else "assistant", content=f"Turn {i+1}", timestamp="2023-01-01T00:00:00Z"
role="user" if i % 2 == 0 else "assistant",
content=f"Turn {i + 1}",
timestamp="2023-01-01T00:00:00Z",
)
for i in range(turn_num)
]
@@ -445,7 +447,7 @@ class TestConversationFlow:
# Now we should be at the limit - create final context
final_turns = [
ConversationTurn(
role="user" if i % 2 == 0 else "assistant", content=f"Turn {i+1}", timestamp="2023-01-01T00:00:00Z"
role="user" if i % 2 == 0 else "assistant", content=f"Turn {i + 1}", timestamp="2023-01-01T00:00:00Z"
)
for i in range(MAX_CONVERSATION_TURNS)
]

View File

@@ -32,9 +32,9 @@ class TestThinkingModes:
]
for tool, expected_default in tools:
assert (
tool.get_default_thinking_mode() == expected_default
), f"{tool.__class__.__name__} should default to {expected_default}"
assert tool.get_default_thinking_mode() == expected_default, (
f"{tool.__class__.__name__} should default to {expected_default}"
)
@pytest.mark.asyncio
@patch("tools.base.BaseTool.create_model")

View File

@@ -28,8 +28,9 @@ from .token_utils import MAX_CONTEXT_TOKENS, estimate_tokens
logger = logging.getLogger(__name__)
# Get workspace root for Docker path translation
# When running in Docker with a mounted workspace, WORKSPACE_ROOT contains
# the host path that corresponds to /workspace in the container
# IMPORTANT: WORKSPACE_ROOT should contain the HOST path (e.g., /Users/john/project)
# that gets mounted to /workspace in the Docker container. This enables proper
# path translation between host absolute paths and container workspace paths.
WORKSPACE_ROOT = os.environ.get("WORKSPACE_ROOT")
CONTAINER_WORKSPACE = Path("/workspace")
@@ -43,6 +44,7 @@ DANGEROUS_WORKSPACE_PATHS = {
"/var",
"/root",
"/home",
"/workspace", # Container path - WORKSPACE_ROOT should be host path
"C:\\",
"C:\\Windows",
"C:\\Program Files",
@@ -54,7 +56,17 @@ if WORKSPACE_ROOT:
# Resolve to canonical path for comparison
resolved_workspace = Path(WORKSPACE_ROOT).resolve()
# Check against dangerous paths
# Special check for /workspace - common configuration mistake
if str(resolved_workspace) == "/workspace":
raise RuntimeError(
f"Configuration Error: WORKSPACE_ROOT should be set to the HOST path, not the container path. "
f"Found: WORKSPACE_ROOT={WORKSPACE_ROOT} "
f"Expected: WORKSPACE_ROOT should be set to your host directory path (e.g., $HOME) "
f"that contains all files Claude might reference. "
f"This path gets mounted to /workspace inside the Docker container."
)
# Check against other dangerous paths
if str(resolved_workspace) in DANGEROUS_WORKSPACE_PATHS:
raise RuntimeError(
f"Security Error: WORKSPACE_ROOT '{WORKSPACE_ROOT}' is set to a dangerous system directory. "
@@ -181,12 +193,17 @@ def translate_path_for_environment(path_str: str) -> str:
This is the unified path translation function that should be used by all
tools and utilities throughout the codebase. It handles:
1. Docker host-to-container path translation
1. Docker host-to-container path translation (host paths -> /workspace/...)
2. Direct mode (no translation needed)
3. Security validation and error handling
Docker Path Translation Logic:
- Input: /Users/john/project/src/file.py (host path from Claude)
- WORKSPACE_ROOT: /Users/john/project (host path in env var)
- Output: /workspace/src/file.py (container path for file operations)
Args:
path_str: Original path string from the client
path_str: Original path string from the client (absolute host path)
Returns:
Translated path appropriate for the current environment