3.4 KiB
Permission Resolution (State-Based)
This document explains how permission mode is resolved for session messages, depending on current state in the app and CLI.
Scope
- App-side state resolution (session defaults, persisted values, outbound message metadata)
- Claude CLI resolution (startup mode, per-message updates, sandbox policy)
- Final mode sent to Claude SDK
Permission Modes
- Shared mode type:
default | acceptEdits | bypassPermissions | plan | read-only | safe-yolo | yolo - Claude SDK supports:
default | acceptEdits | bypassPermissions | plan - Mapping to Claude happens in
packages/happy-cli/src/claude/utils/permissionMode.ts:yolo -> bypassPermissionssafe-yolo -> defaultread-only -> default
App-Side Resolution
1) Session state load/merge
packages/happy-app/sources/sync/storage.ts
When sessions are merged, the app resolves session.permissionMode using this order:
- Existing in-memory session mode (if non-
default) - Persisted per-session mode from local storage (if non-
default) - Mode from server session payload (if non-
default) - Sandbox fallback:
- If
session.metadata.sandbox.enabled === true:bypassPermissions - Otherwise:
default
- If
2) New-session draft fallback
packages/happy-app/sources/sync/persistence.ts
If draft permission mode is missing:
- Draft default:
default
3) New session UI defaults
packages/happy-app/sources/app/(app)/new/index.tsx
packages/happy-app/sources/components/NewSessionWizard.tsx
Default selection:
default
If selected mode is invalid for the currently selected agent, UI resets to agent default above.
4) Outbound message mode
packages/happy-app/sources/sync/sync.ts
On send:
- If
session.permissionModeis non-default, send it. - Otherwise:
- If
session.metadata.sandbox.enabled === true: sendbypassPermissions - Else send
default
- If
This value is sent in:
- encrypted message
meta.permissionMode - socket envelope
permissionMode
Claude CLI Resolution
1) Startup resolution
packages/happy-cli/src/claude/runClaude.ts
packages/happy-cli/src/claude/utils/permissionMode.ts
Initial mode comes from:
--dangerously-skip-permissions(highest priority) ->bypassPermissions--permission-mode VALUEor--permission-mode=VALUE- Provided
options.permissionMode
Then sandbox policy is applied:
- If sandbox enabled: force
bypassPermissions - If sandbox disabled: keep resolved mode
2) Per-message updates in remote flow
packages/happy-cli/src/claude/runClaude.ts
When a user message includes meta.permissionMode:
- If sandbox enabled: forced to
bypassPermissions - If sandbox disabled: use incoming mode
3) Local Claude process
packages/happy-cli/src/claude/claudeLocal.ts
If sandbox is enabled, launcher appends --dangerously-skip-permissions before spawn.
Effective Result Matrix
Sandbox enabled
- App fallback mode is
bypassPermissionswhen session mode is default/missing - Claude CLI sandbox policy still forces
bypassPermissionsin remote flow
Sandbox disabled
- If app/session mode is non-
default: that mode is used - If app/session mode is
defaultor missing:- App sends
default - CLI uses normal mode resolution (no sandbox forcing)
- App sends
Why this is stable now
- Client fallback only forces skip-permissions for sandboxed sessions.
- CLI sandbox policy guarantees sandboxed Claude sessions cannot re-enable permission prompts via message metadata.