Shell Exec Tool
Shell Exec Tool
Feature Information
- Feature ID: FEAT-029
- Created: 2026-03-01
- Last Updated: 2026-03-01
- Status: Draft
- Priority: P1 (Should Have)
- Owner: TBD
- Related RFC: RFC-029 (pending)
User Story
As an AI agent using OneClaw, I want a tool that executes shell commands on the Android device, so that I can interact with the device’s operating system – listing files, checking system info, running scripts, managing packages, and performing automation tasks that require direct command-line access.
Typical Scenarios
- The agent runs
ls -la /sdcard/Download/to list files in the user’s download directory, then summarizes the contents. - The agent runs
pm list packagesto enumerate installed apps on the device. - The agent runs
cat /proc/cpuinfoto check the device’s hardware specifications. - The agent chains commands like
find /sdcard -name "*.pdf" -mtime -7to locate recently modified PDF files. - The agent runs
ping -c 3 google.comto diagnose network connectivity. - The agent runs
getprop ro.build.version.releaseto check the Android version.
Feature Description
Overview
FEAT-029 adds a Kotlin built-in exec tool that runs shell commands on the Android device using Runtime.getRuntime().exec(). The tool captures stdout, stderr, and the exit code, returning them as structured text to the AI model. This gives the AI agent direct access to the device’s command-line environment, enabling file system operations, system inspection, process management, and general-purpose automation.
The tool runs commands in a non-interactive shell with a configurable timeout to prevent runaway processes. Output is captured and truncated to a configurable character limit to avoid overwhelming the AI model’s context window.
Architecture Overview
AI Model
| tool call: exec(command="ls -la /sdcard/")
v
ToolExecutionEngine (Kotlin, unchanged)
|
v
ToolRegistry
|
v
ExecTool [NEW - Kotlin built-in tool]
|
+-- ProcessBuilder / Runtime.exec()
| |
| +-- stdout capture
| +-- stderr capture
| +-- exit code
| +-- timeout enforcement
|
+-- Output formatting
|
+-- Combine stdout + stderr
+-- Truncate to max_length
+-- Return with exit code
Tool Definition
| Field | Value |
|---|---|
| Name | exec |
| Description | Execute a shell command on the device and return its output |
| Parameters | command (string, required): The shell command to execute |
timeout_seconds (integer, optional): Maximum execution time in seconds. Default: 30 |
|
working_directory (string, optional): Working directory for the command. Default: app data directory |
|
max_length (integer, optional): Maximum output length in characters. Default: 50000 |
|
| Required Permissions | None (runs within app sandbox) |
| Timeout | Controlled by timeout_seconds parameter (max 120 seconds) |
| Returns | Combined stdout/stderr with exit code, or error object |
Command Execution
- Commands are executed via
Runtime.getRuntime().exec(arrayOf("sh", "-c", command)) - The shell process runs with the app’s UID and permissions (Android app sandbox)
- No root access unless the device is rooted and the app has root permissions
- Commands run in a non-interactive shell – no stdin, no TTY
- Environment variables inherit from the app process
Output Format
The tool returns a structured text output:
[Exit Code: 0]
<stdout content here>
If stderr is non-empty:
[Exit Code: 1]
<stdout content here>
[stderr]
<stderr content here>
If the command times out:
[Exit Code: -1 (timeout)]
<partial stdout if any>
[stderr]
Process killed after 30 seconds timeout.
User Interaction Flow
1. User: "What files are in my Downloads folder?"
2. AI calls exec(command="ls -la /sdcard/Download/")
3. ExecTool:
a. Creates a process via Runtime.exec()
b. Captures stdout and stderr in separate threads
c. Waits for completion (up to timeout)
d. Formats output with exit code
4. AI receives the directory listing, summarizes for the user
5. Chat shows the exec tool call result
Acceptance Criteria
Must pass (all required):
exectool is registered as a Kotlin built-in tool inToolRegistry- Tool accepts a
commandstring parameter and executes it viaRuntime.getRuntime().exec() - Shell commands run with
sh -cwrapper for proper shell interpretation - stdout is captured and returned in the result
- stderr is captured and included in the result when non-empty
- Exit code is included in the output
timeout_secondsparameter controls maximum execution time (default: 30s, max: 120s)- Process is forcibly killed when timeout expires
working_directoryparameter sets the process working directorymax_lengthparameter controls output truncation (default: 50000)- Output truncation happens cleanly at a line boundary
- Commands that produce no output return success with empty output indication
- Invalid commands return an error with the shell’s error message
- All Layer 1A tests pass
Optional (nice to have):
- Command history tracking for debugging
- Blocklist for dangerous commands (rm -rf /, reboot, etc.)
UI/UX Requirements
This feature has no new UI. The tool operates transparently:
- Same tool call display in chat as other tools
- Output shown in the tool result area
- No additional settings screen needed for V1
Feature Boundary
Included
- Kotlin
ExecToolimplementation usingRuntime.getRuntime().exec() - stdout and stderr capture with separate reader threads
- Exit code reporting
- Configurable timeout with process kill
- Configurable working directory
- Output truncation with configurable limit
- Registration in
ToolModule
Not Included (V1)
- Interactive shell sessions (persistent shell with stdin)
- Root command execution support
- Command blocklist or allowlist
- Environment variable configuration per-command
- Background/long-running process management
- Streaming output (real-time stdout/stderr)
- Shell selection (bash, zsh, etc.) – uses
shonly - Command approval UI (user confirmation before execution)
Business Rules
- Commands are always wrapped with
sh -cfor proper shell interpretation (pipes, redirects, etc.) - Default timeout is 30 seconds; maximum allowed timeout is 120 seconds
- If
timeout_secondsexceeds 120, it is clamped to 120 - Timed-out processes are destroyed via
Process.destroyForcibly() - Default working directory is the app’s data directory (
context.filesDir) - Output truncation defaults to 50,000 characters if
max_lengthis not specified - The process inherits the app’s environment variables
- No stdin is provided to the process (non-interactive)
Non-Functional Requirements
Performance
- Process creation: < 100ms
- Output capture: Real-time via buffered reader threads
- Timeout enforcement: Accurate to within 1 second
Memory
- stdout and stderr are buffered in StringBuilder (in-memory)
- Large outputs are truncated to
max_lengthto prevent OOM - Process resources are cleaned up after execution
Compatibility
- Works on all supported Android versions (API 26+)
Runtime.getRuntime().exec()is available on all Android versions- Available shell commands depend on the device’s Android version and ROM
Dependencies
Depends On
- FEAT-004 (Tool System): Tool interface, registry, execution engine
Depended On By
- None currently
External Dependencies
- None (uses Android platform APIs only)
Error Handling
Error Scenarios
- Empty command
- Cause:
commandparameter is empty or blank - Handling: Return
ToolResult.error("validation_error", "Parameter 'command' is required and cannot be empty")
- Cause:
- Invalid working directory
- Cause: Specified
working_directorydoes not exist - Handling: Return
ToolResult.error("validation_error", "Working directory does not exist: <path>")
- Cause: Specified
- Process creation failure
- Cause: System cannot create a new process (resource limits)
- Handling: Return
ToolResult.error("execution_error", "Failed to start process: <message>")
- Timeout
- Cause: Command exceeds the timeout limit
- Handling: Kill the process, return partial output with timeout indication
- I/O error during output capture
- Cause: Stream read failure
- Handling: Return
ToolResult.error("execution_error", "Failed to read process output: <message>")
Test Points
Functional Tests
- Verify
execexecutes a simple command and returns stdout - Verify
execcaptures stderr when command produces error output - Verify exit code is correctly reported (0 for success, non-zero for failure)
- Verify commands with pipes work (
echo hello | tr a-z A-Z) - Verify commands with redirects work
- Verify
timeout_secondsparameter kills long-running commands - Verify
working_directorychanges the process working directory - Verify
max_lengthtruncates long output at line boundaries - Verify empty command returns validation error
- Verify non-existent command returns shell error message
Edge Cases
- Command that produces very large output (>1MB)
- Command that produces output on both stdout and stderr
- Command that takes exactly the timeout duration
- Command that produces binary output
- Command with special characters in arguments
- Command with very long argument string
timeout_secondsset to 0 or negative valuetimeout_secondsset above 120 (should be clamped)working_directorypointing to a non-existent pathmax_lengthset to 0 or negative value- Command that forks a background process
Change History
| Date | Version | Changes | Owner |
|---|---|---|---|
| 2026-03-01 | 0.1 | Initial version | - |