From 3dc1810540050abb8c23c48ab827e8d3e9966959 Mon Sep 17 00:00:00 2001 From: Tam Nhu Tran Date: Fri, 22 May 2026 11:14:17 -0400 Subject: [PATCH] feat: report permission mode persist receipt status --- src/commands/persist-command.ts | 36 +++++++- .../commands/persist-command-handler.test.ts | 82 +++++++++++++++++++ 2 files changed, 116 insertions(+), 2 deletions(-) diff --git a/src/commands/persist-command.ts b/src/commands/persist-command.ts index 0b1f2f82..911bde14 100644 --- a/src/commands/persist-command.ts +++ b/src/commands/persist-command.ts @@ -42,6 +42,8 @@ interface PersistReceipt { clearedCodexTranslatorUrlKeys: string[]; writtenKeys: string[]; unchangedWrittenKeys: string[]; + writtenSettings: string[]; + unchangedSettings: string[]; codexTranslatorUrlPaths: string[]; } @@ -553,8 +555,10 @@ function findCodexTranslatorUrlPaths(value: unknown, path = ''): string[] { function buildPersistReceipt( existingEnv: Record, + existingSettings: Record, mergedSettings: Record, - resolved: ResolvedEnv + resolved: ResolvedEnv, + resolvedPermissionMode?: PermissionMode ): PersistReceipt { const mergedEnv = typeof mergedSettings.env === 'object' && @@ -577,12 +581,28 @@ function buildPersistReceipt( .filter(([key, value]) => existingEnv[key] === value) .map(([key]) => key) .sort((left, right) => left.localeCompare(right)); + const existingPermissions = + typeof existingSettings.permissions === 'object' && + existingSettings.permissions !== null && + !Array.isArray(existingSettings.permissions) + ? (existingSettings.permissions as Record) + : {}; + const writtenSettings = + resolvedPermissionMode && existingPermissions.defaultMode !== resolvedPermissionMode + ? ['permissions.defaultMode'] + : []; + const unchangedSettings = + resolvedPermissionMode && existingPermissions.defaultMode === resolvedPermissionMode + ? ['permissions.defaultMode'] + : []; return { clearedKeys, clearedCodexTranslatorUrlKeys, writtenKeys, unchangedWrittenKeys, + writtenSettings, + unchangedSettings, codexTranslatorUrlPaths: findCodexTranslatorUrlPaths(mergedSettings), }; } @@ -607,6 +627,12 @@ function printPersistReceipt(receipt: PersistReceipt): void { if (receipt.unchangedWrittenKeys.length > 0) { console.log(` Already current keys: ${formatKeyList(receipt.unchangedWrittenKeys)}`); } + if (receipt.writtenSettings.length > 0 || receipt.unchangedSettings.length > 0) { + console.log(` Written/rewritten managed settings: ${formatKeyList(receipt.writtenSettings)}`); + if (receipt.unchangedSettings.length > 0) { + console.log(` Already current settings: ${formatKeyList(receipt.unchangedSettings)}`); + } + } const hadCodexTranslatorCleanup = receipt.clearedCodexTranslatorUrlKeys.length > 0; if (receipt.codexTranslatorUrlPaths.length > 0) { @@ -1044,7 +1070,13 @@ export async function handlePersistCommand(args: string[]): Promise { await writeClaudeSettings(mergedSettings); const persistedSettings = await readClaudeSettings(); - const receipt = buildPersistReceipt(existingEnv, persistedSettings, resolved); + const receipt = buildPersistReceipt( + existingEnv, + existingSettings, + persistedSettings, + resolved, + resolvedPermissionMode + ); console.log(''); console.log( diff --git a/tests/unit/commands/persist-command-handler.test.ts b/tests/unit/commands/persist-command-handler.test.ts index 99be6605..f80ad8c5 100644 --- a/tests/unit/commands/persist-command-handler.test.ts +++ b/tests/unit/commands/persist-command-handler.test.ts @@ -498,6 +498,88 @@ describe('persist command Claude extension parity', () => { expect(renderedLogs).not.toContain('Native Codex target:'); }); + it('reports written permission defaultMode without exposing the mode value', async () => { + await writeUnifiedConfig(); + + const settingsPath = path.join(tempRoot, '.claude', 'settings.json'); + await fs.promises.mkdir(path.dirname(settingsPath), { recursive: true }); + await fs.promises.writeFile( + settingsPath, + JSON.stringify( + { + env: { + KEEP_ME: 'still-here', + }, + }, + null, + 2 + ) + '\n', + 'utf8' + ); + + const originalConsoleLog = console.log; + const capturedLogs: string[] = []; + console.log = (...args: unknown[]) => { + capturedLogs.push(args.map((arg) => String(arg)).join(' ')); + }; + + try { + await withScopedHome(() => + handlePersistCommand(['glm', '--yes', '--permission-mode', 'acceptEdits']) + ); + } finally { + console.log = originalConsoleLog; + } + + const renderedLogs = capturedLogs.join('\n'); + expect(renderedLogs).toContain('Written/rewritten managed settings: permissions.defaultMode'); + expect(renderedLogs).not.toContain('Already current settings: permissions.defaultMode'); + expect(renderedLogs).not.toContain('permissions.defaultMode: acceptEdits'); + }); + + it('reports already-current permission defaultMode without exposing the mode value', async () => { + await writeUnifiedConfig(); + + const settingsPath = path.join(tempRoot, '.claude', 'settings.json'); + await fs.promises.mkdir(path.dirname(settingsPath), { recursive: true }); + await fs.promises.writeFile( + settingsPath, + JSON.stringify( + { + env: { + KEEP_ME: 'still-here', + }, + permissions: { + defaultMode: 'acceptEdits', + allow: ['Bash(ls:*)'], + }, + }, + null, + 2 + ) + '\n', + 'utf8' + ); + + const originalConsoleLog = console.log; + const capturedLogs: string[] = []; + console.log = (...args: unknown[]) => { + capturedLogs.push(args.map((arg) => String(arg)).join(' ')); + }; + + try { + await withScopedHome(() => + handlePersistCommand(['glm', '--yes', '--permission-mode', 'acceptEdits']) + ); + } finally { + console.log = originalConsoleLog; + } + + const renderedLogs = capturedLogs.join('\n'); + expect(renderedLogs).toContain('Written/rewritten managed settings: none'); + expect(renderedLogs).toContain('Already current settings: permissions.defaultMode'); + expect(renderedLogs).not.toContain('permissions.defaultMode: acceptEdits'); + }); + it('does not print native Codex target guidance for non-Codex profile persistence', async () => { await writeUnifiedConfig();