+ "default" | "acceptEdits" | "auto" | "bypassPermissions"
> = ["default", "acceptEdits", "auto", "bypassPermissions"]
+function isOfferedClaudePermissionMode(
+ mode: ClaudePermissionMode
+): mode is (typeof CLAUDE_PERMISSION_MODE_VALUES)[number] {
+ return (CLAUDE_PERMISSION_MODE_VALUES as readonly string[]).includes(mode)
+}
+
export function normalizeClaudePermissionMode(
value: unknown
): ClaudePermissionMode {
@@ -663,7 +675,9 @@ export function normalizeClaudePermissionMode(
normalized === "default" ||
normalized === "acceptEdits" ||
normalized === "auto" ||
- normalized === "bypassPermissions"
+ normalized === "bypassPermissions" ||
+ normalized === "plan" ||
+ normalized === "dontAsk"
) {
return normalized
}
@@ -682,10 +696,12 @@ export function readClaudePermissionMode(
/**
* Write or clear `permissions.defaultMode` in Claude's native settings.json
- * text, preserving other permission rules (allow/deny/ask). Setting
- * `bypassPermissions` also stamps `skipDangerousModePermissionPrompt` so
- * Claude does not pop the extra "are you sure" dialog. Clearing the mode
- * leaves that skip flag alone. Pure — shared by the settings handler and tests.
+ * text, preserving other permission rules (allow/deny/ask). Never writes
+ * `skipDangerousModePermissionPrompt`: that flag is a record of the user
+ * accepting Claude Code's own bypass dialog, not a behavior switch, and
+ * forging it from this dropdown would suppress a safety interlock outside
+ * codeg. Clearing the mode also leaves any existing skip flag alone.
+ * Pure, shared by the settings handler and tests.
*/
export function applyClaudePermissionModeToConfigText(
configText: string,
@@ -704,9 +720,6 @@ export function applyClaudePermissionModeToConfigText(
if (mode) {
existingPerms.defaultMode = mode
config.permissions = existingPerms
- if (mode === "bypassPermissions") {
- config.skipDangerousModePermissionPrompt = true
- }
} else {
delete existingPerms.defaultMode
if (Object.keys(existingPerms).length === 0) {
@@ -11848,6 +11861,16 @@ supports_websockets = true`}
{t(`claude.permission_${value}`)}
))}
+ {selectedDraft.claudePermissionMode &&
+ !isOfferedClaudePermissionMode(
+ selectedDraft.claudePermissionMode
+ ) ? (
+
+ {selectedDraft.claudePermissionMode}
+
+ ) : null}
@@ -12005,6 +12028,21 @@ supports_websockets = true`}
)
)
.then(() => {
+ // A chosen Claude default must also become CodeG's
+ // new-chat composer default. Connect applies
+ // selector-prefs after the adapter reads
+ // settings.json, so without this write-through a
+ // stickier last-used mode silently wins. Unset
+ // leaves the composer last-used alone.
+ if (
+ selectedAgent.agent_type === "claude_code" &&
+ selectedDraft.claudePermissionMode
+ ) {
+ saveModeIdPreference(
+ "claude_code",
+ selectedDraft.claudePermissionMode
+ )
+ }
// Reflect the provider-authoritative rewrite AND the
// materialized hardening flags in the editors so the
// textareas don't show stale values until reload —
diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json
index 2f4c69c60b..3652d9196c 100644
--- a/src/i18n/messages/ar.json
+++ b/src/i18n/messages/ar.json
@@ -1289,13 +1289,13 @@
"disableNonessentialTraffic": "تعطيل القياس عن بُعد أو طلبات الشبكة غير الضرورية",
"disableNonessentialTrafficAria": "تعطيل القياس عن بُعد أو طلبات الشبكة غير الضرورية في Claude Code",
"permissionModeLabel": "وضع الأذونات",
- "permissionModeHint": "تبدأ محادثات Claude الجديدة بهذا الوضع. تُحفظ كـ permissions.defaultMode في ~/.claude/settings.json.",
+ "permissionModeHint": "يكتب permissions.defaultMode لجلسات CLI الجديدة. الوضع المختار يُحفظ أيضاً كافتراضي composer في CodeG حتى لا يفوز اختيار دردشة سابق بصمت عند الاتصال. استخدام الافتراضي يُبقي آخر قيمة في composer كما هي.",
"permissionModeAria": "وضع أذونات Claude Code",
"permissionUnset": "استخدام الافتراضي",
- "permission_default": "السؤال في كل مرة",
- "permission_acceptEdits": "الموافقة التلقائية على التعديلات",
- "permission_auto": "موافقة تلقائية ذكية",
- "permission_bypassPermissions": "السماح دائماً"
+ "permission_default": "يدوي",
+ "permission_acceptEdits": "قبول التعديلات",
+ "permission_auto": "تلقائي",
+ "permission_bypassPermissions": "تجاوز الأذونات"
},
"dialogs": {
"confirmDeleteProvider": "حذف المزود {providerId}؟",
diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json
index fb160b7060..471a15b28a 100644
--- a/src/i18n/messages/de.json
+++ b/src/i18n/messages/de.json
@@ -1289,13 +1289,13 @@
"disableNonessentialTraffic": "Telemetrie oder überflüssige Netzwerkanfragen deaktivieren",
"disableNonessentialTrafficAria": "Telemetrie oder überflüssige Netzwerkanfragen von Claude Code deaktivieren",
"permissionModeLabel": "Berechtigungsmodus",
- "permissionModeHint": "Neue Claude-Chats starten in diesem Modus. Wird als permissions.defaultMode in ~/.claude/settings.json gespeichert.",
+ "permissionModeHint": "Schreibt permissions.defaultMode für neue CLI-Sitzungen. Ein gewählter Modus wird auch als CodeG-Composer-Standard gespeichert, damit eine frühere Chat-Wahl beim Verbinden nicht still gewinnt. Standard verwenden lässt den zuletzt genutzten Composer-Wert unverändert.",
"permissionModeAria": "Claude-Code-Berechtigungsmodus",
"permissionUnset": "Standard verwenden",
- "permission_default": "Jedes Mal fragen",
- "permission_acceptEdits": "Bearbeitungen automatisch genehmigen",
- "permission_auto": "Intelligente automatische Genehmigung",
- "permission_bypassPermissions": "Immer zulassen"
+ "permission_default": "Manuell",
+ "permission_acceptEdits": "Änderungen akzeptieren",
+ "permission_auto": "Auto",
+ "permission_bypassPermissions": "Berechtigungen umgehen"
},
"dialogs": {
"confirmDeleteProvider": "Provider {providerId} löschen?",
diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json
index cc6ac019a3..615bf3c208 100644
--- a/src/i18n/messages/en.json
+++ b/src/i18n/messages/en.json
@@ -1289,13 +1289,13 @@
"disableNonessentialTraffic": "Disable telemetry or redundant network requests",
"disableNonessentialTrafficAria": "Disable Claude Code telemetry or redundant network requests",
"permissionModeLabel": "Permission mode",
- "permissionModeHint": "New Claude chats start in this mode. Saved as permissions.defaultMode in ~/.claude/settings.json.",
+ "permissionModeHint": "Writes permissions.defaultMode for new CLI sessions. A chosen mode is also saved as CodeG's composer default so a previous chat pick cannot silently win. Use default leaves the composer last-used as-is.",
"permissionModeAria": "Claude Code permission mode",
"permissionUnset": "Use default",
- "permission_default": "Ask every time",
- "permission_acceptEdits": "Auto-approve edits",
- "permission_auto": "Smart auto-approve",
- "permission_bypassPermissions": "Always approve"
+ "permission_default": "Manual",
+ "permission_acceptEdits": "Accept Edits",
+ "permission_auto": "Auto",
+ "permission_bypassPermissions": "Bypass Permissions"
},
"dialogs": {
"confirmDeleteProvider": "Delete Provider {providerId}?",
diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json
index 75fa031386..956d9f77e6 100644
--- a/src/i18n/messages/es.json
+++ b/src/i18n/messages/es.json
@@ -1289,13 +1289,13 @@
"disableNonessentialTraffic": "Desactivar la telemetría o las solicitudes de red innecesarias",
"disableNonessentialTrafficAria": "Desactivar la telemetría o las solicitudes de red innecesarias de Claude Code",
"permissionModeLabel": "Modo de permisos",
- "permissionModeHint": "Los chats nuevos de Claude empiezan en este modo. Se guarda como permissions.defaultMode en ~/.claude/settings.json.",
+ "permissionModeHint": "Escribe permissions.defaultMode para las sesiones nuevas de la CLI. El modo elegido también queda como valor predeterminado del composer de CodeG, para que una elección anterior no gane en silencio al conectar. Usar el predeterminado deja el último valor del composer como está.",
"permissionModeAria": "Modo de permisos de Claude Code",
"permissionUnset": "Usar el predeterminado",
- "permission_default": "Preguntar cada vez",
- "permission_acceptEdits": "Aprobar ediciones automáticamente",
- "permission_auto": "Aprobación automática inteligente",
- "permission_bypassPermissions": "Aprobar siempre"
+ "permission_default": "Manual",
+ "permission_acceptEdits": "Aceptar ediciones",
+ "permission_auto": "Auto",
+ "permission_bypassPermissions": "Omitir permisos"
},
"dialogs": {
"confirmDeleteProvider": "¿Eliminar proveedor {providerId}?",
diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json
index 58c9e2c340..8edfdac9f6 100644
--- a/src/i18n/messages/fr.json
+++ b/src/i18n/messages/fr.json
@@ -1289,13 +1289,13 @@
"disableNonessentialTraffic": "Désactiver la télémétrie ou les requêtes réseau superflues",
"disableNonessentialTrafficAria": "Désactiver la télémétrie ou les requêtes réseau superflues de Claude Code",
"permissionModeLabel": "Mode d'autorisation",
- "permissionModeHint": "Les nouveaux chats Claude démarrent dans ce mode. Enregistré comme permissions.defaultMode dans ~/.claude/settings.json.",
+ "permissionModeHint": "Écrit permissions.defaultMode pour les nouvelles sessions CLI. Le mode choisi devient aussi le défaut du composer CodeG, pour qu'un choix de chat précédent ne l'emporte pas en silence à la connexion. Utiliser la valeur par défaut laisse le dernier choix du composer inchangé.",
"permissionModeAria": "Mode d'autorisation de Claude Code",
"permissionUnset": "Utiliser la valeur par défaut",
- "permission_default": "Demander à chaque fois",
- "permission_acceptEdits": "Approuver automatiquement les modifications",
- "permission_auto": "Approbation automatique intelligente",
- "permission_bypassPermissions": "Toujours autoriser"
+ "permission_default": "Manuel",
+ "permission_acceptEdits": "Accepter les modifications",
+ "permission_auto": "Auto",
+ "permission_bypassPermissions": "Ignorer les autorisations"
},
"dialogs": {
"confirmDeleteProvider": "Supprimer le provider {providerId} ?",
diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json
index 23cd767e72..0cf17c7ea0 100644
--- a/src/i18n/messages/ja.json
+++ b/src/i18n/messages/ja.json
@@ -1289,13 +1289,13 @@
"disableNonessentialTraffic": "テレメトリや不要なネットワークリクエストを無効化",
"disableNonessentialTrafficAria": "Claude Code のテレメトリや不要なネットワークリクエストを無効化",
"permissionModeLabel": "権限モード",
- "permissionModeHint": "新しい Claude チャットはこのモードで始まります。~/.claude/settings.json の permissions.defaultMode に保存されます。",
+ "permissionModeHint": "CLI の新規セッション向けに permissions.defaultMode を書き込みます。選んだモードは CodeG の composer 既定値にもなり、以前のチャットで選んだモードが接続時に黙って勝つことはありません。「デフォルトを使用」は composer の前回値をそのままにします。",
"permissionModeAria": "Claude Code の権限モード",
"permissionUnset": "デフォルトを使用",
- "permission_default": "毎回確認",
- "permission_acceptEdits": "編集を自動承認",
- "permission_auto": "スマート自動承認",
- "permission_bypassPermissions": "常に許可"
+ "permission_default": "手動",
+ "permission_acceptEdits": "編集を許可",
+ "permission_auto": "自動",
+ "permission_bypassPermissions": "権限をバイパス"
},
"dialogs": {
"confirmDeleteProvider": "Provider {providerId} を削除しますか?",
diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json
index 51eb1b1df2..5cb47d6666 100644
--- a/src/i18n/messages/ko.json
+++ b/src/i18n/messages/ko.json
@@ -1289,13 +1289,13 @@
"disableNonessentialTraffic": "텔레메트리 또는 불필요한 네트워크 요청 비활성화",
"disableNonessentialTrafficAria": "Claude Code 텔레메트리 또는 불필요한 네트워크 요청 비활성화",
"permissionModeLabel": "권한 모드",
- "permissionModeHint": "새 Claude 채팅이 이 모드로 시작됩니다. ~/.claude/settings.json 의 permissions.defaultMode 에 저장됩니다.",
+ "permissionModeHint": "CLI 새 세션의 permissions.defaultMode 를 기록합니다. 고른 모드는 CodeG composer 기본값에도 반영되어, 이전 채팅에서 고른 모드가 연결 시 조용히 이기지 않습니다. 기본값 사용은 composer 마지막 값을 그대로 둡니다.",
"permissionModeAria": "Claude Code 권한 모드",
"permissionUnset": "기본값 사용",
- "permission_default": "매번 묻기",
- "permission_acceptEdits": "편집 자동 승인",
- "permission_auto": "스마트 자동 승인",
- "permission_bypassPermissions": "항상 허용"
+ "permission_default": "수동",
+ "permission_acceptEdits": "편집 수락",
+ "permission_auto": "자동",
+ "permission_bypassPermissions": "권한 우회"
},
"dialogs": {
"confirmDeleteProvider": "Provider {providerId}를 삭제하시겠습니까?",
diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json
index 3e51edb1b5..e2d8b7a164 100644
--- a/src/i18n/messages/pt.json
+++ b/src/i18n/messages/pt.json
@@ -1289,13 +1289,13 @@
"disableNonessentialTraffic": "Desativar a telemetria ou solicitações de rede desnecessárias",
"disableNonessentialTrafficAria": "Desativar a telemetria ou solicitações de rede desnecessárias do Claude Code",
"permissionModeLabel": "Modo de permissão",
- "permissionModeHint": "Novos chats do Claude começam neste modo. Gravado como permissions.defaultMode em ~/.claude/settings.json.",
+ "permissionModeHint": "Grava permissions.defaultMode para novas sessões da CLI. O modo escolhido também vira o padrão do composer do CodeG, para que uma escolha anterior de chat não vença em silêncio na conexão. Usar o padrão deixa o último valor do composer como está.",
"permissionModeAria": "Modo de permissão do Claude Code",
"permissionUnset": "Usar o padrão",
- "permission_default": "Perguntar sempre",
- "permission_acceptEdits": "Aprovar edições automaticamente",
- "permission_auto": "Aprovação automática inteligente",
- "permission_bypassPermissions": "Sempre aprovar"
+ "permission_default": "Manual",
+ "permission_acceptEdits": "Aceitar edições",
+ "permission_auto": "Auto",
+ "permission_bypassPermissions": "Ignorar permissões"
},
"dialogs": {
"confirmDeleteProvider": "Excluir o provedor {providerId}?",
diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json
index a9d1c2a196..421b0e0887 100644
--- a/src/i18n/messages/zh-CN.json
+++ b/src/i18n/messages/zh-CN.json
@@ -1289,13 +1289,13 @@
"disableNonessentialTraffic": "禁用遥测或多余的网络请求",
"disableNonessentialTrafficAria": "禁用 Claude Code 遥测或多余的网络请求",
"permissionModeLabel": "权限模式",
- "permissionModeHint": "新的 Claude 会话以此模式启动。写入 ~/.claude/settings.json 的 permissions.defaultMode。",
+ "permissionModeHint": "写入 CLI 新会话的 permissions.defaultMode。选定的模式同时会成为 CodeG 的 composer 默认值,避免之前聊天里选过的模式在连接时悄悄覆盖。使用默认则不改动 composer 上次使用的值。",
"permissionModeAria": "Claude Code 权限模式",
"permissionUnset": "使用默认",
- "permission_default": "每次询问",
- "permission_acceptEdits": "自动批准编辑",
- "permission_auto": "智能自动批准",
- "permission_bypassPermissions": "始终允许"
+ "permission_default": "手动",
+ "permission_acceptEdits": "接受编辑",
+ "permission_auto": "自动",
+ "permission_bypassPermissions": "绕过权限"
},
"dialogs": {
"confirmDeleteProvider": "确认删除 Provider {providerId}?",
diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json
index d7160c0b41..838184e5fb 100644
--- a/src/i18n/messages/zh-TW.json
+++ b/src/i18n/messages/zh-TW.json
@@ -1289,13 +1289,13 @@
"disableNonessentialTraffic": "停用遙測或多餘的網路請求",
"disableNonessentialTrafficAria": "停用 Claude Code 遙測或多餘的網路請求",
"permissionModeLabel": "權限模式",
- "permissionModeHint": "新的 Claude 工作階段以此模式啟動。寫入 ~/.claude/settings.json 的 permissions.defaultMode。",
+ "permissionModeHint": "寫入 CLI 新工作階段的 permissions.defaultMode。選定的模式同時會成為 CodeG 的 composer 預設值,避免先前聊天選過的模式在連線時悄悄覆蓋。使用預設則不改動 composer 上次使用的值。",
"permissionModeAria": "Claude Code 權限模式",
"permissionUnset": "使用預設",
- "permission_default": "每次詢問",
- "permission_acceptEdits": "自動核准編輯",
- "permission_auto": "智慧自動核准",
- "permission_bypassPermissions": "永遠允許"
+ "permission_default": "手動",
+ "permission_acceptEdits": "接受編輯",
+ "permission_auto": "自動",
+ "permission_bypassPermissions": "略過權限"
},
"dialogs": {
"confirmDeleteProvider": "確認刪除 Provider {providerId}?",
diff --git a/src/lib/selector-prefs-storage.test.ts b/src/lib/selector-prefs-storage.test.ts
index bda1b1fa95..e2b27a02bc 100644
--- a/src/lib/selector-prefs-storage.test.ts
+++ b/src/lib/selector-prefs-storage.test.ts
@@ -4,6 +4,7 @@ import {
getSavedModeId,
getSavedPrefsForConnect,
saveConfigPreference,
+ saveModeIdPreference,
saveModePreference,
} from "./selector-prefs-storage"
@@ -150,3 +151,34 @@ describe("selector-prefs-storage", () => {
})
})
})
+
+describe("saveModeIdPreference", () => {
+ beforeEach(() => localStorage.clear())
+
+ it("overwrites a stickier last-used mode so Settings=default wins over saved bypass", () => {
+ saveModeIdPreference("claude_code", "bypassPermissions")
+ expect(getSavedModeId("claude_code")).toBe("bypassPermissions")
+ saveModeIdPreference("claude_code", "default")
+ expect(getSavedModeId("claude_code")).toBe("default")
+ })
+
+ it("overwrites the inverse: Settings=bypass wins over saved default", () => {
+ saveModeIdPreference("claude_code", "default")
+ saveModeIdPreference("claude_code", "bypassPermissions")
+ expect(getSavedModeId("claude_code")).toBe("bypassPermissions")
+ })
+
+ it("ignores empty so Use default does not wipe the composer last-used", () => {
+ saveModeIdPreference("claude_code", "acceptEdits")
+ saveModeIdPreference("claude_code", " ")
+ expect(getSavedModeId("claude_code")).toBe("acceptEdits")
+ })
+
+ it("is the same store saveModePreference writes", () => {
+ saveModePreference("claude_code", {
+ current_mode_id: "auto",
+ available_modes: [],
+ })
+ expect(getSavedModeId("claude_code")).toBe("auto")
+ })
+})
diff --git a/src/lib/selector-prefs-storage.ts b/src/lib/selector-prefs-storage.ts
index 64790dc979..5d340673ac 100644
--- a/src/lib/selector-prefs-storage.ts
+++ b/src/lib/selector-prefs-storage.ts
@@ -164,9 +164,20 @@ export function saveModePreference(
agentType: string,
modes: SessionModeStateInfo
) {
+ saveModeIdPreference(agentType, modes.current_mode_id)
+}
+
+/**
+ * Write a mode id as the agent's sticky new-chat default without needing a
+ * live `SessionModeStateInfo`. Empty / whitespace is ignored: callers that
+ * mean "leave the composer last-used alone" simply skip this.
+ */
+export function saveModeIdPreference(agentType: string, modeId: string) {
+ const id = modeId.trim()
+ if (!id) return
updatePrefs(agentType, (prefs) => ({
...prefs,
- modeId: modes.current_mode_id,
+ modeId: id,
}))
}