Skip to main content

Shell Tool (run_shell_command)

The shell tool allows Qwen Code to execute system commands, run scripts, and perform command-line operations.

Overview

Tool Name: run_shell_command Display Name: Shell Kind: Other Description: Executes shell commands on the underlying system. Supports both foreground and background execution with optional interactive mode.

Parameters

interface ShellToolParams {
  command: string;          // Required: Command to execute
  description?: string;     // Optional: Command description
  directory?: string;       // Optional: Working directory
  is_background: boolean;   // Required: Background execution
  timeout?: number;         // Optional: Timeout in ms
}

Usage

Basic Command

run_shell_command({
  command: 'ls -la',
  is_background: false,
});

With Description

run_shell_command({
  command: 'npm install',
  description: 'Install project dependencies',
  is_background: false,
});

In Specific Directory

run_shell_command({
  command: './build.sh',
  directory: 'scripts',
  description: 'Run build script',
  is_background: false,
});

Background Execution

run_shell_command({
  command: 'npm run dev',
  description: 'Start development server',
  is_background: true,
});

With Timeout

run_shell_command({
  command: 'npm test',
  timeout: 60000,  // 60 seconds
  is_background: false,
});

Background vs Foreground

The is_background parameter is required and controls execution mode.

Foreground (is_background: false)

Use for:
  • One-time commands: ls, cat, grep
  • Build commands: npm run build, make
  • Installation: npm install, pip install
  • Git operations: git commit, git push
  • Tests: npm test, pytest
Behavior:
  • Blocks until command completes
  • Returns stdout, stderr, and exit code
  • Default timeout: 120 seconds
  • Can be customized with timeout parameter

Background (is_background: true)

Use for:
  • Development servers: npm run dev, npm start
  • Build watchers: npm run watch, webpack --watch
  • Database servers: mongod, redis-server
  • Web servers: python -m http.server
  • Any long-running process
Behavior:
  • Returns immediately
  • Process continues running
  • Returns process ID (PID)
  • No timeout applied
  • Adds [background] indicator to description

Command Execution Details

Shell Environment

Platform-specific shells:
  • Windows: cmd.exe /c
  • Unix/Linux/macOS: bash -c
Environment Variables:
QWEN_CODE=1  # Always set for spawned processes

Working Directory

By default, commands run in the project root directory. Use the directory parameter for relative paths:
// Runs in <project-root>/scripts
run_shell_command({
  command: './deploy.sh',
  directory: 'scripts',
  is_background: false,
});

Output Handling

The tool returns:
interface ShellResult {
  command: string;           // Executed command
  directory?: string;        // Working directory
  stdout: string;           // Standard output
  stderr: string;           // Standard error
  exitCode?: number;        // Exit code
  signal?: string;          // Signal if terminated
  backgroundPids?: number[]; // PIDs if background
  error?: string;           // Error message
}

Interactive Commands

Enabling Interactive Shell

Set in settings.json:
{
  "tools": {
    "shell": {
      "enableInteractiveShell": true,
      "showColor": true,
      "pager": "less"
    }
  }
}

Supported Interactive Commands

With enableInteractiveShell enabled:
  • Text editors: vim, nano, emacs
  • Interactive tools: htop, top
  • Version control: git rebase -i, git add -p
  • Other TUIs: Any terminal-based UI

Usage

When an interactive command is running:
  1. Press Ctrl+F to focus on the interactive shell
  2. Interact normally with the command
  3. Exit the command normally (e.g., :q for vim)
  4. Control returns to Qwen Code

Configuration

Shell Settings

{
  "tools": {
    "shell": {
      "enableInteractiveShell": boolean,  // Enable pty for interactive commands
      "showColor": boolean,               // Show ANSI colors (requires interactive)
      "pager": string                     // Pager command (default: "cat")
    }
  }
}

Command Restrictions

Restrict or block specific commands:

Allowlist (Core Tools)

Allow only specific commands:
{
  "tools": {
    "core": [
      "run_shell_command(git)",
      "run_shell_command(npm)",
      "run_shell_command(node)"
    ]
  }
}
Result:
  • git status ✅ Allowed
  • npm install ✅ Allowed
  • rm -rf / ❌ Blocked

Blocklist (Exclude Tools)

Block specific commands:
{
  "tools": {
    "core": ["run_shell_command"],  // Allow all
    "exclude": [
      "run_shell_command(rm)",
      "run_shell_command(sudo)",
      "run_shell_command(curl)"
    ]
  }
}
Result:
  • git status ✅ Allowed
  • npm install ✅ Allowed
  • rm file.txt ❌ Blocked
  • sudo apt update ❌ Blocked

Blocklist Takes Precedence

If a command is in both lists, it’s blocked:
{
  "tools": {
    "core": ["run_shell_command(git)"],
    "exclude": ["run_shell_command(git push)"]
  }
}
Result:
  • git status ✅ Allowed
  • git push ❌ Blocked (more specific)

Command Chaining

Chained commands are validated separately:
# Each part is checked
"git add . && git commit -m 'msg' && git push"

# If any part is blocked, entire command is blocked
Note: Command restrictions use simple prefix matching and are not a security mechanism. They help prevent accidental dangerous operations but should not be relied upon for security.

User Confirmation

Commands requiring user confirmation:

Auto-Approved Commands

Safe, read-only operations:
  • ls, cat, echo
  • git status, git log, git diff
  • npm list
  • Custom allowlisted commands

Requires Confirmation

Potentially dangerous operations:
  • File modifications: rm, mv, cp
  • System commands: sudo, chmod, chown
  • Network: curl, wget, ssh
  • Installation: npm install, pip install
  • Custom blocklisted commands

Confirmation Dialog

When confirmation is required:
Confirm Shell Command

Command: rm -rf dist/
Root Command: rm

Options:
  [P]roceed once
  [A]lways allow this command
  [N]ever allow this command
  [C]ancel
Choosing “Always allow” adds the command to the allowlist for the session.

Security Considerations

Path Validation

Working directories are validated:
if (!workspaceContext.isPathWithinWorkspace(directory)) {
  return 'Directory must be within workspace';
}

Command Injection

Be cautious with:
  • User-provided command strings
  • Interpolated variables
  • Shell metacharacters: ;, &&, ||, |, >, <

Best Practices

  1. Avoid user input in commands:
    // ❌ Dangerous
    `rm ${userInput}`
    
    // ✅ Safe
    'rm file.txt'
    
  2. Use blocklist for dangerous commands:
    {
      "tools": {
        "exclude": ["run_shell_command(rm)", "run_shell_command(sudo)"]
      }
    }
    
  3. Enable sandboxing:
    export QWEN_SANDBOX=docker
    
  4. Review confirmation prompts:
    • Don’t blindly approve commands
    • Understand what each command does
    • Use “Never allow” for suspicious commands

Implementation

Location: packages/core/src/tools/shell.ts

Tool Class

export class ShellTool extends BaseDeclarativeTool<
  ShellToolParams,
  ToolResult
> {
  static readonly Name = ToolNames.RUN_SHELL_COMMAND;
  private readonly allowlist = new Set<string>();

  constructor(private readonly config: Config) {
    super(
      ShellTool.Name,
      ToolDisplayNames.RUN_SHELL_COMMAND,
      'Executes shell commands...',
      Kind.Other,
      { /* schema */ },
    );
  }

  protected override validateToolParamValues(
    params: ShellToolParams,
  ): string | null {
    // Validate command
    if (!params.command || params.command.trim() === '') {
      return 'Command must not be empty';
    }

    // Validate directory
    if (params.directory) {
      const resolvedDir = path.resolve(
        this.config.getTargetDir(),
        params.directory,
      );
      if (!workspaceContext.isPathWithinWorkspace(resolvedDir)) {
        return 'Directory must be within workspace';
      }
    }

    // Check command restrictions
    const command = stripShellWrapper(params.command);
    if (!isCommandAllowed(command, this.config)) {
      return `Command not allowed: ${command}`;
    }

    return null;
  }
}

Service

Location: packages/core/src/services/shellExecutionService.ts Handles actual command execution:
export class ShellExecutionService {
  async executeCommand(
    command: string,
    options: ShellExecutionConfig,
  ): Promise<ShellResult> {
    const shell = process.platform === 'win32' ? 'cmd.exe' : 'bash';
    const shellArgs = process.platform === 'win32' ? ['/c'] : ['-c'];

    const child = spawn(shell, [...shellArgs, command], {
      cwd: options.cwd,
      env: { ...process.env, QWEN_CODE: '1' },
      signal: options.signal,
    });

    // Handle stdout, stderr, exit code
    // ...
  }
}

Examples

Development Workflow

// Install dependencies
run_shell_command({
  command: 'npm install',
  description: 'Install dependencies',
  is_background: false,
});

// Start dev server in background
run_shell_command({
  command: 'npm run dev',
  description: 'Start dev server',
  is_background: true,
});

// Run tests
run_shell_command({
  command: 'npm test',
  description: 'Run test suite',
  is_background: false,
});

Git Workflow

// Check status
run_shell_command({
  command: 'git status',
  is_background: false,
});

// Stage and commit
run_shell_command({
  command: 'git add . && git commit -m "feat: add feature"',
  description: 'Stage and commit changes',
  is_background: false,
});

// Push to remote
run_shell_command({
  command: 'git push origin main',
  is_background: false,
});

Build and Deploy

// Build project
run_shell_command({
  command: 'npm run build',
  description: 'Build for production',
  timeout: 300000,  // 5 minutes
  is_background: false,
});

// Run deployment script
run_shell_command({
  command: './deploy.sh production',
  directory: 'scripts',
  description: 'Deploy to production',
  is_background: false,
});

Troubleshooting

Command Not Found

Error: command not found: mycommand Solutions:
  1. Check if command is in PATH
  2. Use absolute path: /usr/local/bin/mycommand
  3. Check spelling and availability

Permission Denied

Error: Permission denied Solutions:
  1. Check file permissions: chmod +x script.sh
  2. Run from correct directory
  3. Don’t use sudo (blocked by default)

Timeout

Error: Command timed out Solutions:
  1. Increase timeout: timeout: 300000
  2. Use background execution: is_background: true
  3. Optimize slow command

Interactive Command Fails

Error: Input/output error Solution: Enable interactive shell:
{
  "tools": {
    "shell": {
      "enableInteractiveShell": true
    }
  }
}

Next Steps