Skip to main content
Permission modes control how the SDK handles tool execution approval. They provide different levels of control over which operations the AI can perform automatically.

Permission Modes Overview

The SDK supports four built-in permission modes:
ModeDescriptionUse Case
defaultWrite tools denied unless approved via canUseTool or allowedTools. Read-only tools execute automatically.Most interactive applications
planBlocks all write tools. AI presents a plan instead of executing.Planning and analysis workflows
auto-editAuto-approves edit tools (edit, write_file). Other tools require confirmation.Code editing assistants
yoloAll tools execute automatically without confirmation.Trusted automation scripts

Default Mode

The safest interactive mode. Write operations require explicit approval.

Behavior

  • Read-only tools: Execute automatically (e.g., read_file, list_directory)
  • Write tools: Denied unless approved via canUseTool callback or in allowedTools
  • Shell commands: Require approval

Example

import { query } from '@qwen-code/sdk';

const result = query({
  prompt: 'Read package.json and create a backup',
  options: {
    permissionMode: 'default',
    canUseTool: async (toolName, input) => {
      // Read operations: auto-approve
      if (toolName === 'read_file') {
        return { behavior: 'allow', updatedInput: input };
      }
      
      // Write operations: ask user
      const approved = await askUser(`Allow ${toolName} on ${input.path}?`);
      if (approved) {
        return { behavior: 'allow', updatedInput: input };
      }
      
      return { behavior: 'deny', message: 'User denied permission' };
    },
  },
});

Without canUseTool

If no canUseTool callback is provided, write tools are automatically denied:
const result = query({
  prompt: 'Create a new file',
  options: {
    permissionMode: 'default',
    // No canUseTool - write operations will be denied
  },
});

Plan Mode

Blocks all write operations. The AI explains what it would do instead of executing.

Behavior

  • Read-only tools: Execute normally
  • Write tools: Blocked (AI instructed to present a plan)
  • Special: exit_plan_mode tool allowed (if available)

Example

const result = query({
  prompt: 'How would you refactor this codebase?',
  options: {
    permissionMode: 'plan',
  },
});

for await (const message of result) {
  if (message.type === 'assistant') {
    // AI will explain the refactoring plan instead of executing
    console.log(message.message.content);
  }
}

Use Cases

  • Getting implementation plans before execution
  • Code review and suggestions
  • Architecture analysis
  • Exploration without modification

Auto-Edit Mode

Auto-approves file editing operations while requiring confirmation for other tools.

Behavior

  • Edit tools (edit, write_file): Execute automatically
  • Other write tools: Require approval via canUseTool or allowedTools
  • Read-only tools: Execute automatically

Example

const result = query({
  prompt: 'Fix all TypeScript errors in the src/ directory',
  options: {
    permissionMode: 'auto-edit',
    // File edits happen automatically
    // Other operations (like shell commands) would need approval
  },
});

With Additional Approvals

const result = query({
  prompt: 'Fix errors and run tests',
  options: {
    permissionMode: 'auto-edit',
    canUseTool: async (toolName, input) => {
      // Edits already auto-approved by mode
      // This only gets called for non-edit tools
      if (toolName === 'run_terminal_cmd' && input.command?.startsWith('npm test')) {
        return { behavior: 'allow', updatedInput: input };
      }
      return { behavior: 'deny', message: 'Not allowed' };
    },
  },
});

Use Cases

  • Code refactoring assistants
  • Automated fix tools
  • Documentation generators
  • Bulk code updates

YOLO Mode

Auto-approves all tools without confirmation. Use with extreme caution.

Behavior

  • All tools: Execute automatically
  • No callbacks: canUseTool is never called
  • No restrictions: Except tools in excludeTools
YOLO mode grants the AI full access to file system operations and shell commands. Only use in trusted, isolated environments.

Example

const result = query({
  prompt: 'Set up the project structure and install dependencies',
  options: {
    permissionMode: 'yolo',
    cwd: '/safe/isolated/directory',
  },
});

Safe YOLO with Exclusions

Combine with excludeTools to prevent dangerous operations:
const result = query({
  prompt: 'Analyze and refactor the code',
  options: {
    permissionMode: 'yolo',
    excludeTools: [
      'delete_file',
      'run_terminal_cmd',
      'ShellTool(rm )',
      'ShellTool(git push)',
    ],
  },
});

Use Cases

  • Automated CI/CD pipelines in isolated environments
  • Batch processing scripts
  • Development environment setup
  • Testing and experimentation in sandboxes

Permission Priority Chain

Understanding the order of permission checks:
1. excludeTools          -> Blocks completely (highest priority)

2. permissionMode: 'plan' -> Blocks non-read-only tools

3. permissionMode: 'yolo' -> Auto-approves all tools

4. allowedTools          -> Auto-approves matching tools

5. canUseTool callback   -> Custom approval logic

6. Default behavior      -> Auto-deny in SDK mode

Example with Multiple Controls

const result = query({
  prompt: 'Modify the project',
  options: {
    permissionMode: 'default',
    
    // Highest priority - always blocked
    excludeTools: ['delete_file', 'ShellTool(rm )'],
    
    // Auto-approve these tools
    allowedTools: ['ShellTool(git status)', 'ShellTool(npm test)'],
    
    // Custom logic for everything else
    canUseTool: async (toolName, input) => {
      // This won't be called for:
      // - Tools in excludeTools (already blocked)
      // - Tools in allowedTools (already approved)
      // - Read-only tools (auto-approved in default mode)
      
      if (toolName === 'write_file') {
        return { behavior: 'allow', updatedInput: input };
      }
      
      return { behavior: 'deny', message: 'Not allowed' };
    },
  },
});

Custom Permission Handler

The canUseTool callback provides fine-grained control over tool execution.

Signature

type CanUseTool = (
  toolName: string,
  input: Record<string, unknown>,
  options: {
    signal: AbortSignal;
    suggestions?: PermissionSuggestion[] | null;
  }
) => Promise<PermissionResult>;

type PermissionResult =
  | {
      behavior: 'allow';
      updatedInput: Record<string, unknown>;
    }
  | {
      behavior: 'deny';
      message: string;
      interrupt?: boolean;
    };

Parameters

toolName
string
required
Name of the tool requesting permission.
input
Record<string, unknown>
required
Input parameters for the tool.
options.signal
AbortSignal
required
Signal for cancelling the permission request.
options.suggestions
PermissionSuggestion[] | null
Suggested actions from the CLI (if available).

Return Value

behavior
'allow' | 'deny'
required
Whether to allow or deny the tool execution.
updatedInput
Record<string, unknown>
Modified input parameters (only for 'allow'). Return the original input if no modifications needed.
message
string
Explanation for denial (only for 'deny').
interrupt
boolean
Whether to interrupt the entire session on denial (only for 'deny').

Examples

Allow with Modified Input

canUseTool: async (toolName, input) => {
  if (toolName === 'write_file') {
    // Redirect all writes to a safe directory
    return {
      behavior: 'allow',
      updatedInput: {
        ...input,
        path: `/safe/dir/${input.path}`,
      },
    };
  }
  return { behavior: 'deny', message: 'Not allowed' };
}

Deny with Interrupt

canUseTool: async (toolName, input) => {
  if (toolName === 'delete_file') {
    return {
      behavior: 'deny',
      message: 'File deletion is not allowed',
      interrupt: true, // Stop the entire session
    };
  }
  return { behavior: 'allow', updatedInput: input };
}

Path-Based Approval

canUseTool: async (toolName, input) => {
  if (toolName === 'write_file' || toolName === 'edit') {
    const path = input.path as string;
    
    // Only allow writes to specific directories
    if (path.startsWith('src/') || path.startsWith('tests/')) {
      return { behavior: 'allow', updatedInput: input };
    }
    
    return {
      behavior: 'deny',
      message: `Cannot modify files outside src/ and tests/: ${path}`,
    };
  }
  
  return { behavior: 'allow', updatedInput: input };
}

Interactive Approval

import * as readline from 'readline';

function askUser(question: string): Promise<boolean> {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  
  return new Promise((resolve) => {
    rl.question(`${question} (y/n): `, (answer) => {
      rl.close();
      resolve(answer.toLowerCase() === 'y');
    });
  });
}

const result = query({
  prompt: 'Modify the codebase',
  options: {
    permissionMode: 'default',
    canUseTool: async (toolName, input) => {
      // Auto-approve reads
      if (toolName.startsWith('read_') || toolName === 'list_directory') {
        return { behavior: 'allow', updatedInput: input };
      }
      
      // Ask user for writes
      const approved = await askUser(
        `Allow ${toolName}?\nInput: ${JSON.stringify(input, null, 2)}`
      );
      
      if (approved) {
        return { behavior: 'allow', updatedInput: input };
      }
      
      return { behavior: 'deny', message: 'User denied permission' };
    },
  },
});

Allowed and Excluded Tools

allowedTools

Auto-approve specific tools without calling canUseTool. Pattern matching:
  • Tool name: 'write_file', 'run_shell_command'
  • Tool class: 'WriteTool', 'ShellTool'
  • Shell command prefix: 'ShellTool(git status)'
options: {
  permissionMode: 'default',
  allowedTools: [
    'read_file',                    // Specific tool
    'ShellTool(git status)',        // Git status commands
    'ShellTool(npm test)',          // npm test commands
    'ShellTool(ls)',                // ls commands
  ],
}

excludeTools

Block specific tools completely (highest priority).
options: {
  permissionMode: 'yolo',
  excludeTools: [
    'delete_file',                  // Block file deletion
    'ShellTool(rm )',               // Block rm commands
    'ShellTool(git push)',          // Block git push
    'run_terminal_cmd',             // Block all terminal commands
  ],
}

Timeout Configuration

The canUseTool callback must respond within a timeout period (default: 60 seconds).
options: {
  permissionMode: 'default',
  canUseTool: async (toolName, input) => {
    // Must respond within timeout
    return { behavior: 'allow', updatedInput: input };
  },
  timeout: {
    canUseTool: 120000, // 2 minutes
  },
}
If the timeout is exceeded, the tool request is automatically denied with a timeout error.

Best Practices

1. Start with Default Mode

Use default mode with a custom canUseTool handler for most applications:
options: {
  permissionMode: 'default',
  canUseTool: async (toolName, input) => {
    // Implement your approval logic
  },
}

2. Use Plan Mode for Analysis

When you want suggestions without execution:
options: {
  permissionMode: 'plan',
}

3. Combine Modes with Tool Lists

Use allowedTools and excludeTools to fine-tune any mode:
options: {
  permissionMode: 'auto-edit',
  allowedTools: ['ShellTool(git status)'],
  excludeTools: ['delete_file', 'ShellTool(rm )'],
}

4. Validate Tool Input

Always validate and sanitize tool inputs:
canUseTool: async (toolName, input) => {
  if (toolName === 'write_file') {
    const path = input.path as string;
    
    // Validate path
    if (path.includes('..') || path.startsWith('/')) {
      return { behavior: 'deny', message: 'Invalid path' };
    }
    
    return { behavior: 'allow', updatedInput: input };
  }
  return { behavior: 'allow', updatedInput: input };
}

5. Log Permission Decisions

Log all permission decisions for audit trails:
canUseTool: async (toolName, input) => {
  const decision = /* your logic */;
  
  console.log({
    timestamp: new Date().toISOString(),
    toolName,
    input,
    decision: decision.behavior,
  });
  
  return decision;
}

See Also