authorgravatar for maria@kuuro.netmaria <maria@kuuro.net> 2026-09-10 23:39:31-03:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-10 23:39:31-03:00
log5735693d4acb949c2b0daf1219b4f62845115435
tree1e836dc9067be7e1511fc6f60eae482bbd7e387f
parent48654c1182c41dbd5e99ef0d6620a13ba2400f26
signature Signed by PGP key B5690EEEBB952194

fix(server): let Claude launch args override the derived permission mode (#11026)

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>

2 files changed, 36 insertions(+), 2 deletions(-)

apps/server/src/provider/Layers/ClaudeAdapter.test.ts+23
......@@ -422,6 +422,29 @@ describe("ClaudeAdapterLive", () => {
422422 );
423423 });
424424
425 it.effect("lets a launch-arg permission flag win over the thread runtime mode", () => {
426 const harness = makeHarness({
427 claudeConfig: { launchArgs: "--dangerously-skip-permissions --verbose" },
428 });
429 return Effect.gen(function* () {
430 const adapter = yield* ClaudeAdapter;
431 yield* adapter.startSession({
432 threadId: THREAD_ID,
433 provider: ProviderDriverKind.make("claudeAgent"),
434 runtimeMode: "auto-accept-edits",
435 });
436
437 const createInput = harness.getLastCreateQueryInput();
438 assert.equal(createInput?.options.permissionMode, "bypassPermissions");
439 assert.equal(createInput?.options.allowDangerouslySkipPermissions, true);
440 // The honored flag is dropped from extraArgs so the CLI sees it once.
441 assert.deepEqual(createInput?.options.extraArgs, { verbose: null });
442 }).pipe(
443 Effect.provideService(Random.Random, makeDeterministicRandomService()),
444 Effect.provide(harness.layer),
445 );
446 });
447
425448 it.effect("loads Claude filesystem settings sources for SDK sessions", () => {
426449 const harness = makeHarness();
427450 return Effect.gen(function* () {
apps/server/src/provider/Layers/ClaudeAdapter.ts+13-2
......@@ -4608,7 +4608,11 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
46084608 ) => runPromise(handleResumeDialog(request, callbackOptions));
46094609
46104610 const claudeBinaryPath = claudeSdkExecutablePath;
4611 const extraArgs = parseCliArgs(claudeSettings.launchArgs).flags;
4611 const {
4612 "permission-mode": launchArgPermissionMode,
4613 "dangerously-skip-permissions": launchArgSkipPermissions,
4614 ...extraArgs
4615 } = parseCliArgs(claudeSettings.launchArgs).flags;
46124616 const selectedModel =
46134617 input.modelSelection?.instanceId === boundInstanceId ? input.modelSelection : undefined;
46144618 const modelSelection = selectedModel
......@@ -4649,7 +4653,14 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
46494653 auto: "auto",
46504654 "full-access": "bypassPermissions",
46514655 };
4652 const permissionMode = runtimeModeToPermission[input.runtimeMode];
4656 // A permission launch arg is folded into the mode T3 sends rather than
4657 // passed through: the CLI resolves both inputs together, so argv order
4658 // never let the user's flag win.
4659 const permissionMode =
4660 (launchArgPermissionMode as PermissionMode | null | undefined) ??
4661 (launchArgSkipPermissions === null || launchArgSkipPermissions === "true"
4662 ? "bypassPermissions"
4663 : runtimeModeToPermission[input.runtimeMode]);
46534664 const settings = {
46544665 ...(typeof thinking === "boolean" ? { alwaysThinkingEnabled: thinking } : {}),
46554666 ...(fastMode ? { fastMode: true } : {}),