How Claudian’s Permission Modes (YOLO, Safe, Plan) Work: A Technical Deep Dive
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
EnterPlanModetool that suppresses normal approval dialogs until explicitly exited. The SDK usesplan.
Type Definitions and Storage
The PermissionMode type is defined as a union of string literals in src/core/types/settings.ts:
export type PermissionMode = 'yolo' | 'plan' | 'normal';
(source)
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 within the updateDisplay() method:
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)
When you click the toggle, the toggle() method switches between YOLO and Safe:
private async toggle() {
const current = this.callbacks.getSettings().permissionMode;
const newMode: PermissionMode = current === 'yolo' ? 'normal' : 'yolo';
await this.callbacks.onPermissionModeChange(newMode);
this.updateDisplay();
}
(source)
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 maps the internal PermissionMode to the Claude SDK’s expected values via applyPermissionMode:
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)
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 applies updates through applyDynamicUpdates:
if (this.currentConfig && permissionMode !== this.currentConfig.permissionMode) {
const sdkMode = this.mapToSDKPermissionMode(permissionMode);
await this.persistentQuery.setPermissionMode(sdkMode);
this.currentConfig.permissionMode = permissionMode;
}
(source)
The mapping function translates internal modes to SDK constants:
private mapToSDKPermissionMode(mode: PermissionMode): SDKPermissionMode {
if (mode === 'yolo') return 'bypassPermissions';
if (mode === 'plan') return 'plan';
return 'acceptEdits';
}
(source)
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:
tab.service.setPermissionModeSyncCallback((sdkMode) => {
// Convert SDK mode back to our enum and update UI/settings
});
(source)
The service invokes this callback when stream events indicate a mode change:
if (event.permissionMode && this.permissionModeSyncCallback) {
this.permissionModeSyncCallback(event.permissionMode);
}
(source)
Plan Mode Special Handling
Plan mode requires special logic in 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:
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)
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
// 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
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
// /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
// 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
PermissionModetype is defined insrc/core/types/settings.tsand stored in plugin settings. - The toolbar in
src/features/chat/ui/InputToolbar.tsrenders the mode state and handles toggling between YOLO and Safe. src/core/agent/QueryOptionsBuilder.tsmaps internal modes to SDK options, always enablingallowDangerouslySkipPermissionsfor dynamic switching.src/core/agent/ClaudianService.tsmanages 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, 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. 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →