Command Execution Design Analysis - v0
Date: 2025-11-20 Codebase: AhaiaApp/ide/web Purpose: Document how command execution currently works and its limitations
Architecture Overview
Backend Implementation (server.js:139-182)
const { stdout, stderr } = await execAsync(command, {
cwd: actualPath,
timeout: 10000, // 10 second timeout
maxBuffer: 1024 * 1024 // 1MB max output
});
Key Components:
- Uses Node.js
child_process.exec()(promisified) - Executes in repository's working directory
- Returns stdout, stderr, and exit code
- Catches errors and returns them as failed responses
- Synchronous request/response model
How Different Command Types Are Handled
1. Quick Commands ✅ Work Well
Examples: ls, git status, gh -h, pwd, echo "hello"
Behavior:
- Execute immediately
- Complete within timeout
- Output displayed in chat bubble
- Exit code 0 returned for success
Example Flow:
User types: ls -la
→ Frontend sends POST to /api/execute
→ Backend runs: exec('ls -la', { cwd: repoPath, timeout: 10000 })
→ stdout returned in ~50-200ms
→ Displayed in chat as monospace code block
Status: ✅ Works perfectly for commands that complete quickly
2. Long-Running Commands ⚠️ TIMEOUT ISSUE
Examples: brew install, npm install, docker build, pytest, cargo build
Current Behavior:
- Timeout after 10 seconds (server.js:164)
- Process is killed by Node.js
- Returns error:
Command failed: ... ETIMEDOUT - No streaming output - you only get results when command completes
Problems:
- ❌ Commands taking >10s fail
- ❌ No progress updates (can't see what's happening)
- ❌ Output buffer limit (1MB) - truncates large outputs
- ❌ User has no idea command is still running vs. frozen
What happens:
User types: npm install
→ Command starts executing
→ At 10 seconds: KILLED
→ stderr: "Error: Command failed: npm install\nkilled"
→ Package installation incomplete
Root Cause: Using exec() which buffers all output and enforces timeout
3. Interactive Commands ❌ COMPLETELY BROKEN
Examples: vim, nano, python (REPL), claude, ssh, top, less, man
Current Behavior:
- Hangs forever (or until 10s timeout)
- No way to send input to stdin
- Terminal control codes (colors, cursor movement) don't work
- Process expects TTY but gets pipe
Why They Fail:
exec()provides no stdin interaction- No pseudo-TTY (PTY) allocation
- No way to send keyboard input (arrows, Ctrl+C, etc.)
- Commands detect non-interactive shell and may behave differently
What happens:
User types: vim myfile.txt
→ vim starts but expects interactive terminal
→ Waits for input that never comes
→ Hangs for 10 seconds
→ Killed by timeout
→ Error returned
Root Cause: HTTP request/response model has no mechanism for bidirectional communication
Current Limitations Summary
| Command Type | Status | Issue |
|---|---|---|
| Quick commands (<10s) | ✅ Works | None |
| Long commands (>10s) | ⚠️ Broken | Timeout kills process |
| Commands with large output | ⚠️ Truncated | 1MB buffer limit |
| Interactive commands | ❌ Broken | No stdin/TTY support |
| Background processes | ❌ Broken | Can't run in background |
| Streaming output | ❌ Missing | No real-time updates |
| Job management | ❌ Missing | Can't list/cancel/resume |
Technical Root Causes
1. Using exec() instead of spawn()
exec()buffers all output (1MB limit)spawn()would allow streaming- No stdin pipe with current implementation
- Timeout enforced at process level
2. No Process Management
- Commands run synchronously in request handler
- No way to check status of running commands
- Can't cancel/resume/attach to processes
- No job queue or background execution
3. No PTY (Pseudo-Terminal)
- Interactive programs need TTY
- Requires
node-ptyor similar library - Terminal control codes need interpretation
- ANSI escape sequences not handled
4. Request/Response Model
- HTTP request waits for entire command to finish
- Can't support long-running operations well
- Need WebSockets or SSE for streaming
- Timeout limits on HTTP requests
Security Considerations (Current)
Implemented:
- ✅ Path validation (ensures commands only run in workspace)
- ✅ HTML escaping to prevent XSS
- ✅ Command timeout (prevents indefinite hangs)
- ✅ Output buffer limits (prevents memory exhaustion)
- ✅ Base64 encoding for IDs (prevents path traversal)
Missing:
- ❌ No command whitelisting/blacklisting
- ❌ No shell injection protection (user can run any command)
- ❌ No rate limiting
- ❌ No process resource limits (CPU, memory)
Potential Improvements
Option A: Increase Timeout (Quick Fix)
Pros:
- Simple one-line change
- Helps with longer builds
Cons:
- Doesn't fix interactive commands
- Still no streaming output
- HTTP timeout issues
Implementation:
timeout: 300000, // 5 minutes instead of 10 seconds
Option B: Add Streaming Output
Pros:
- Real-time feedback
- Better UX for long commands
- No buffer limit
Cons:
- Requires WebSockets or SSE
- More complex architecture
- Still doesn't fix interactive commands
Stack:
- Backend:
spawn()+ WebSocket - Frontend: WebSocket client + streaming UI
- Library:
socket.ioor native WebSocket
Option C: Full Terminal Emulator
Pros:
- Supports ALL command types
- True terminal experience
- Interactive commands work
Cons:
- Significant development effort
- Complex security implications
- Requires PTY support
Stack:
- Backend:
node-ptyfor PTY - Frontend:
xterm.jsfor terminal emulator - Transport: WebSocket for bidirectional comms
- Session management for persistent shells
Option D: Background Job Queue
Pros:
- Long commands don't block
- Can check job status
- Better resource management
Cons:
- More complex state management
- Needs persistent storage
- Still no interactivity
Stack:
- Job queue: Bull or simple in-memory queue
- Status endpoint: GET /api/jobs/:id
- Cancel endpoint: DELETE /api/jobs/:id
Recommended Approach
Phase 1: Quick Wins (1-2 hours)
- Increase timeout to 5-10 minutes
- Increase buffer to 10MB
- Add timeout indicator in UI
Phase 2: Streaming (1-2 days)
- Add WebSocket support
- Switch to
spawn()for streaming - Real-time output in chat bubbles
- Job management (cancel, status)
Phase 3: Interactive (1-2 weeks)
- Implement PTY support with
node-pty - Add
xterm.jsterminal emulator - Bidirectional WebSocket communication
- Session persistence
Code Locations
Command Execution:
- Backend:
/home/ubuntu/workplace/AhaiaApp/ide/web/server.js:139-182 - Frontend:
/home/ubuntu/workplace/AhaiaApp/ide/web/app-repo.js:129-184
Key Lines:
- Timeout setting:
server.js:164 - Buffer limit:
server.js:165 - Command execution:
server.js:162 - Response handling:
app-repo.js:164-177
Notes
- Current design is optimized for quick git operations
- Trade-off was made: simplicity vs. functionality
- No authentication/authorization currently implemented
- All commands run as the server's user (security risk)
- No audit logging of executed commands
References
- Node.js child_process docs: https://nodejs.org/api/child_process.html
- node-pty: https://github.com/microsoft/node-pty
- xterm.js: https://xtermjs.org/
- Socket.IO: https://socket.io/