fix: vision(#623): scope Claude chat working directory to project staging checkout (#1027)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/nomad-validate Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/edge-subpath Pipeline was successful
ci/woodpecker/pr/nomad-validate Pipeline was successful
ci/woodpecker/pr/secret-scan Pipeline was successful
ci/woodpecker/pr/smoke-init Pipeline was successful

- server.py: add CHAT_WORKSPACE_DIR env var, set cwd to workspace
  and use --permission-mode acceptEdits + append message in Claude invocations
- lib/generators.sh: add workspace bind mount and env var to compose generator
- nomad/jobs/chat.hcl: add workspace host volume (static source "chat-workspace"),
  meta block + NOMAD_META_ env var, volume_mount — Nomad-compatible pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dev-qwen2 2026-04-20 18:11:33 +00:00
parent a330db9537
commit 7f1f8fa01c
3 changed files with 64 additions and 4 deletions

View file

@ -20,6 +20,12 @@ OAuth flow:
6. Redirects to /chat/
The claude binary is expected to be mounted from the host at /usr/local/bin/claude.
Workspace access:
- CHAT_WORKSPACE_DIR environment variable: bind-mounted project working tree
- Claude invocation uses --permission-mode acceptEdits for code modification
- CWD is set to workspace directory when configured, enabling Claude to
inspect, explain, or modify code scoped to that tree only
"""
import asyncio
@ -46,6 +52,10 @@ UI_DIR = "/var/chat/ui"
STATIC_DIR = os.path.join(UI_DIR, "static")
CLAUDE_BIN = "/usr/local/bin/claude"
# Workspace directory: bind-mounted project working tree for Claude access
# Defaults to empty; when set, Claude can read/write to this directory
WORKSPACE_DIR = os.environ.get("CHAT_WORKSPACE_DIR", "")
# OAuth configuration
FORGE_URL = os.environ.get("FORGE_URL", "http://localhost:3000")
CHAT_OAUTH_CLIENT_ID = os.environ.get("CHAT_OAUTH_CLIENT_ID", "")
@ -491,12 +501,18 @@ class _WebSocketHandler:
return
try:
# Build claude command with permission mode (acceptEdits allows file edits)
claude_args = [CLAUDE_BIN, "--print", "--output-format", "stream-json", "--permission-mode", "acceptEdits", message]
# Spawn claude --print with stream-json for streaming output
# Set cwd to workspace directory if configured, allowing Claude to access project code
cwd = WORKSPACE_DIR if WORKSPACE_DIR else None
proc = subprocess.Popen(
[CLAUDE_BIN, "--print", "--output-format", "stream-json", message],
claude_args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
cwd=cwd,
bufsize=1,
)
@ -1040,12 +1056,18 @@ class ChatHandler(BaseHTTPRequestHandler):
# Save user message to history
_write_message(user, conv_id, "user", message)
# Build claude command with permission mode (acceptEdits allows file edits)
claude_args = [CLAUDE_BIN, "--print", "--output-format", "stream-json", "--permission-mode", "acceptEdits", message]
# Spawn claude --print with stream-json for token tracking (#711)
# Set cwd to workspace directory if configured, allowing Claude to access project code
cwd = WORKSPACE_DIR if WORKSPACE_DIR else None
proc = subprocess.Popen(
[CLAUDE_BIN, "--print", "--output-format", "stream-json", message],
claude_args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
cwd=cwd,
bufsize=1, # Line buffered
)