diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts index 5422a8730f27..c4136156df10 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.test.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.test.ts @@ -422,6 +422,29 @@ describe("ClaudeAdapterLive", () => { ); }); + it.effect("lets a launch-arg permission flag win over the thread runtime mode", () => { + const harness = makeHarness({ + claudeConfig: { launchArgs: "--dangerously-skip-permissions --verbose" }, + }); + return Effect.gen(function* () { + const adapter = yield* ClaudeAdapter; + yield* adapter.startSession({ + threadId: THREAD_ID, + provider: ProviderDriverKind.make("claudeAgent"), + runtimeMode: "auto-accept-edits", + }); + + const createInput = harness.getLastCreateQueryInput(); + assert.equal(createInput?.options.permissionMode, "bypassPermissions"); + assert.equal(createInput?.options.allowDangerouslySkipPermissions, true); + // The honored flag is dropped from extraArgs so the CLI sees it once. + assert.deepEqual(createInput?.options.extraArgs, { verbose: null }); + }).pipe( + Effect.provideService(Random.Random, makeDeterministicRandomService()), + Effect.provide(harness.layer), + ); + }); + it.effect("loads Claude filesystem settings sources for SDK sessions", () => { const harness = makeHarness(); return Effect.gen(function* () { diff --git a/apps/server/src/provider/Layers/ClaudeAdapter.ts b/apps/server/src/provider/Layers/ClaudeAdapter.ts index 81a4a197e9db..bc021a06e761 100644 --- a/apps/server/src/provider/Layers/ClaudeAdapter.ts +++ b/apps/server/src/provider/Layers/ClaudeAdapter.ts @@ -4608,7 +4608,11 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( ) => runPromise(handleResumeDialog(request, callbackOptions)); const claudeBinaryPath = claudeSdkExecutablePath; - const extraArgs = parseCliArgs(claudeSettings.launchArgs).flags; + const { + "permission-mode": launchArgPermissionMode, + "dangerously-skip-permissions": launchArgSkipPermissions, + ...extraArgs + } = parseCliArgs(claudeSettings.launchArgs).flags; const selectedModel = input.modelSelection?.instanceId === boundInstanceId ? input.modelSelection : undefined; const modelSelection = selectedModel @@ -4649,7 +4653,14 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( auto: "auto", "full-access": "bypassPermissions", }; - const permissionMode = runtimeModeToPermission[input.runtimeMode]; + // A permission launch arg is folded into the mode T3 sends rather than + // passed through: the CLI resolves both inputs together, so argv order + // never let the user's flag win. + const permissionMode = + (launchArgPermissionMode as PermissionMode | null | undefined) ?? + (launchArgSkipPermissions === null || launchArgSkipPermissions === "true" + ? "bypassPermissions" + : runtimeModeToPermission[input.runtimeMode]); const settings = { ...(typeof thinking === "boolean" ? { alwaysThinkingEnabled: thinking } : {}), ...(fastMode ? { fastMode: true } : {}),