# How Claudian’s Permission Modes (YOLO, Safe, Plan) Work: A Technical Deep Dive

> Understand Claudian's permission modes YOLO Safe and Plan. This deep dive explains how Claude SDK tool calls are managed through auto-approval manual approval or workflow-based execution.

- Repository: [YishenTu/claudian](https://github.com/YishenTu/claudian)
- Tags: deep-dive
- Published: 2026-03-17

---

**Claudian implements three permission modes—YOLO (auto-approve), Safe (manual approval), and Plan (workflow-based)—that control how tool calls are handled by the Claude SDK, with the active mode stored in `settings.permissionMode` and dynamically synced between the UI and runtime.**

Claudian is an Obsidian plugin that integrates Claude’s agentic capabilities into your note-taking workflow. At the heart of its safety system lies a **permission mode** architecture that determines whether AI tool calls execute automatically or require your explicit consent. This article examines how these modes are defined in the type system, rendered in the UI, and mapped to the Claude SDK’s runtime behavior.

## The Three Permission Modes Explained

Claudian’s permission modes govern the approval flow for every tool call the Claude agent attempts:

- **YOLO Mode**: Every tool call is **auto-approved** without user intervention. The SDK flag is set to `bypassPermissions`.
- **Safe Mode** (default): Each tool call triggers an approval modal where you must explicitly accept or reject the operation. The SDK uses `acceptEdits`.
- **Plan Mode**: A special workflow state entered via the `EnterPlanMode` tool that suppresses normal approval dialogs until explicitly exited. The SDK uses `plan`.

## Type Definitions and Storage

The `PermissionMode` type is defined as a union of string literals in **[`src/core/types/settings.ts`](https://github.com/YishenTu/claudian/blob/main/src/core/types/settings.ts)**:

```typescript
export type PermissionMode = 'yolo' | 'plan' | 'normal';

```

([source](https://github.com/YishenTu/claudian/blob/main/src/core/types/settings.ts#L121-L122))

This type is stored in the plugin’s settings object and persists across sessions. The value `'normal'` represents Safe mode in the internal API, while the UI displays it as "Safe".

## UI Implementation in the Toolbar

The chat input toolbar reflects the current mode through a toggle switch and label. The display logic resides in **[`src/features/chat/ui/InputToolbar.ts`](https://github.com/YishenTu/claudian/blob/main/src/features/chat/ui/InputToolbar.ts)** within the `updateDisplay()` method:

```typescript
updateDisplay() {
  const mode = this.callbacks.getSettings().permissionMode;
  if (mode === 'plan') {
    this.toggleEl.style.display = 'none';
    this.labelEl.setText('PLAN');
    this.labelEl.addClass('plan‑active');
  } else {
    this.toggleEl.style.display = '';
    this.labelEl.removeClass('plan‑active');
    if (mode === 'yolo') {
      this.toggleEl.addClass('active');
      this.labelEl.setText('YOLO');
    } else {
      this.toggleEl.removeClass('active');
      this.labelEl.setText('Safe');
    }
  }
}

```

([source](https://github.com/YishenTu/claudian/blob/main/src/features/chat/ui/InputToolbar.ts#L64-L78))

When you click the toggle, the `toggle()` method switches between YOLO and Safe:

```typescript
private async toggle() {
  const current = this.callbacks.getSettings().permissionMode;
  const newMode: PermissionMode = current === 'yolo' ? 'normal' : 'yolo';
  await this.callbacks.onPermissionModeChange(newMode);
  this.updateDisplay();
}

```

([source](https://github.com/YishenTu/claudian/blob/main/src/features/chat/ui/InputToolbar.ts#L84-L88))

In Plan mode, the toggle is hidden entirely, and the label changes to "PLAN" with a distinct CSS class.

## SDK Integration and Runtime Handling

### Mapping Internal Modes to SDK Options

When constructing a query, **[`src/core/agent/QueryOptionsBuilder.ts`](https://github.com/YishenTu/claudian/blob/main/src/core/agent/QueryOptionsBuilder.ts)** maps the internal `PermissionMode` to the Claude SDK’s expected values via `applyPermissionMode`:

```typescript
private static applyPermissionMode(
  options: Options,
  permissionMode: PermissionMode,
  canUseTool?: CanUseTool
) {
  options.allowDangerouslySkipPermissions = true;   // enables runtime changes

  if (canUseTool) options.canUseTool = canUseTool;

  if (permissionMode === 'yolo')       options.permissionMode = 'bypassPermissions';
  else if (permissionMode === 'plan')   options.permissionMode = 'plan';
  else                                 options.permissionMode = 'acceptEdits';
}

```

([source](https://github.com/YishenTu/claudian/blob/main/src/core/agent/QueryOptionsBuilder.ts#L30-L36))

The `allowDangerouslySkipPermissions` flag is always set to `true`, which allows Claudian to switch modes dynamically without restarting the Claude process.

### Dynamic Runtime Updates

When settings change, **[`src/core/agent/ClaudianService.ts`](https://github.com/YishenTu/claudian/blob/main/src/core/agent/ClaudianService.ts)** applies updates through `applyDynamicUpdates`:

```typescript
if (this.currentConfig && permissionMode !== this.currentConfig.permissionMode) {
  const sdkMode = this.mapToSDKPermissionMode(permissionMode);
  await this.persistentQuery.setPermissionMode(sdkMode);
  this.currentConfig.permissionMode = permissionMode;
}

```

([source](https://github.com/YishenTu/claudian/blob/main/src/core/agent/ClaudianService.ts#L42-L49))

The mapping function translates internal modes to SDK constants:

```typescript
private mapToSDKPermissionMode(mode: PermissionMode): SDKPermissionMode {
  if (mode === 'yolo') return 'bypassPermissions';
  if (mode === 'plan') return 'plan';
  return 'acceptEdits';
}

```

([source](https://github.com/YishenTu/claudian/blob/main/src/core/agent/ClaudianService.ts#L61-L64))

### Bidirectional UI Sync

The SDK can emit `permissionMode` events (for example, when exiting Plan mode). Claudian forwards these to the UI via a callback registered in **[`src/features/chat/tabs/Tab.ts`](https://github.com/YishenTu/claudian/blob/main/src/features/chat/tabs/Tab.ts)**:

```typescript
tab.service.setPermissionModeSyncCallback((sdkMode) => {
  // Convert SDK mode back to our enum and update UI/settings
});

```

([source](https://github.com/YishenTu/claudian/blob/main/src/features/chat/tabs/Tab.ts#L1183-L1185))

The service invokes this callback when stream events indicate a mode change:

```typescript
if (event.permissionMode && this.permissionModeSyncCallback) {
  this.permissionModeSyncCallback(event.permissionMode);
}

```

([source](https://github.com/YishenTu/claudian/blob/main/src/core/agent/ClaudianService.ts#L656-L658))

### Plan Mode Special Handling

Plan mode requires special logic in **[`src/core/agent/ClaudianService.ts`](https://github.com/YishenTu/claudian/blob/main/src/core/agent/ClaudianService.ts)**. Entering Plan mode happens via the `EnterPlanMode` tool, which is auto-approved by the SDK. Exiting requires handling the `ExitPlanMode` tool through a dedicated callback:

```typescript
if (toolName === TOOL_EXIT_PLAN_MODE && this.exitPlanModeCallback) {
  const decision = await this.exitPlanModeCallback(input, options.signal);
  // ... return PermissionResult with updatedPermissions { type: 'setMode', mode: sdkMode }
}

```

([source](https://github.com/YishenTu/claudian/blob/main/src/core/agent/ClaudianService.ts#L75-L97))

When the exit callback resolves, the service sends an `updatedPermissions` entry to the SDK, restoring the previous mode (YOLO or Safe) without prompting the user again.

## Practical Implementation Examples

### Switching to YOLO Programmatically

```typescript
// Assume `plugin` is the main Claudian plugin instance
await plugin.settings.update((s) => {
  s.permissionMode = 'yolo';   // switch to YOLO (auto‑approve)
});
await plugin.service.applyDynamicUpdates(); // triggers setPermissionMode on SDK

```

### Toggling Between YOLO and Safe from the UI

```typescript
const current = this.callbacks.getSettings().permissionMode;
const newMode: PermissionMode = current === 'yolo' ? 'normal' : 'yolo';
await this.callbacks.onPermissionModeChange(newMode);

```

This mirrors the logic found in `InputToolbar.toggle()`.

### Entering Plan Mode via Command

```typescript
// /plan invokes the built‑in EnterPlanMode tool
await this.plugin.service.sendMessage('/plan');

```

The SDK auto-approves the tool, `ClaudianService` receives a `permissionMode: 'plan'` stream event, and the toolbar switches to the "PLAN" label.

### Exiting Plan Mode with a Custom Callback

```typescript
// Register a callback that asks the user whether to keep the plan or revert
plugin.service.setExitPlanModeCallback(async (input, signal) => {
  const answer = await askUser('Keep plan changes?', ['Yes', 'No']);
  return answer === 'Yes' ? { type: 'accept' } : { type: 'reject' };
});

```

When the user runs the `ExitPlanMode` tool, the callback executes, the service updates the SDK mode back to the stored UI mode, and the toolbar restores the previous label.

## Summary

- **Three distinct modes** control tool approval: YOLO (`bypassPermissions`), Safe (`acceptEdits`), and Plan (`plan`).
- The `PermissionMode` type is defined in **[`src/core/types/settings.ts`](https://github.com/YishenTu/claudian/blob/main/src/core/types/settings.ts)** and stored in plugin settings.
- The toolbar in **[`src/features/chat/ui/InputToolbar.ts`](https://github.com/YishenTu/claudian/blob/main/src/features/chat/ui/InputToolbar.ts)** renders the mode state and handles toggling between YOLO and Safe.
- **[`src/core/agent/QueryOptionsBuilder.ts`](https://github.com/YishenTu/claudian/blob/main/src/core/agent/QueryOptionsBuilder.ts)** maps internal modes to SDK options, always enabling `allowDangerouslySkipPermissions` for dynamic switching.
- **[`src/core/agent/ClaudianService.ts`](https://github.com/YishenTu/claudian/blob/main/src/core/agent/ClaudianService.ts)** manages runtime synchronization, bidirectional UI updates, and special handling for Plan mode entry and exit.
- Mode changes apply immediately without restarting the Claude connection, thanks to `persistentQuery.setPermissionMode()`.

## Frequently Asked Questions

### What is the difference between YOLO and Safe mode in Claudian?

**YOLO mode** sets the SDK’s `permissionMode` to `bypassPermissions`, which automatically approves every tool call without showing a modal. **Safe mode** (internally `'normal'`) uses `acceptEdits`, requiring you to manually approve each tool call through an approval dialog. Safe mode is the default setting when you first install the plugin.

### How does Plan mode differ from YOLO and Safe?

**Plan mode** is a temporary workflow state entered via the `EnterPlanMode` tool. Unlike YOLO or Safe, it hides the permission toggle in the UI and suppresses normal approval dialogs while active. It is designed for multi-step planning workflows where you want the agent to execute a series of operations without interruption. Exiting Plan mode requires running the `ExitPlanMode` tool, which triggers a callback that can restore the previous YOLO or Safe setting.

### Can I switch permission modes without restarting the Claude connection?

Yes. Claudian sets `allowDangerouslySkipPermissions` to `true` in **[`src/core/agent/QueryOptionsBuilder.ts`](https://github.com/YishenTu/claudian/blob/main/src/core/agent/QueryOptionsBuilder.ts)**, which allows the SDK to accept permission mode changes at runtime. When you toggle the mode in the UI or update settings programmatically, `ClaudianService.applyDynamicUpdates()` calls `persistentQuery.setPermissionMode()` to sync the change immediately without restarting the underlying Claude process.

### Where is the permission mode setting stored?

The active permission mode is stored in the plugin’s settings object under `settings.permissionMode` as defined in **[`src/core/types/settings.ts`](https://github.com/YishenTu/claudian/blob/main/src/core/types/settings.ts)**. This value persists across Obsidian sessions and is read by the UI components and the agent service whenever constructing new queries or applying dynamic updates.