Skip to content

Commit efc8b3c

Browse files
authored
Restore default permission mode for chat turns (#416)
- Default approval-required sessions back to `default` after plan turns - Keep permission mode state consistent when switching from plan to chat
1 parent 3b131e8 commit efc8b3c

2 files changed

Lines changed: 55 additions & 4 deletions

File tree

apps/server/src/provider/Layers/ClaudeAdapter.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2473,6 +2473,57 @@ describe("ClaudeAdapterLive", () => {
24732473
);
24742474
});
24752475

2476+
it.effect(
2477+
"restores default permission mode on sendTurn when interactionMode is chat for approval-required sessions",
2478+
() => {
2479+
const harness = makeHarness();
2480+
return Effect.gen(function* () {
2481+
const adapter = yield* ClaudeAdapter;
2482+
2483+
const session = yield* adapter.startSession({
2484+
threadId: THREAD_ID,
2485+
provider: "claudeAgent",
2486+
runtimeMode: "approval-required",
2487+
});
2488+
2489+
yield* adapter.sendTurn({
2490+
threadId: session.threadId,
2491+
input: "plan this",
2492+
interactionMode: "plan",
2493+
attachments: [],
2494+
});
2495+
2496+
const turnCompletedFiber = yield* Stream.filter(
2497+
adapter.streamEvents,
2498+
(event) => event.type === "turn.completed",
2499+
).pipe(Stream.runHead, Effect.forkChild);
2500+
2501+
harness.query.emit({
2502+
type: "result",
2503+
subtype: "success",
2504+
is_error: false,
2505+
errors: [],
2506+
session_id: "sdk-session-approval-plan-restore",
2507+
uuid: "result-approval-plan",
2508+
} as unknown as SDKMessage);
2509+
2510+
yield* Fiber.join(turnCompletedFiber);
2511+
2512+
yield* adapter.sendTurn({
2513+
threadId: session.threadId,
2514+
input: "now answer directly",
2515+
interactionMode: "chat",
2516+
attachments: [],
2517+
});
2518+
2519+
assert.deepEqual(harness.query.setPermissionModeCalls, ["plan", "default"]);
2520+
}).pipe(
2521+
Effect.provideService(Random.Random, makeDeterministicRandomService()),
2522+
Effect.provide(harness.layer),
2523+
);
2524+
},
2525+
);
2526+
24762527
it.effect("does not call setPermissionMode when interactionMode is absent", () => {
24772528
const harness = makeHarness();
24782529
return Effect.gen(function* () {

apps/server/src/provider/Layers/ClaudeAdapter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ interface ClaudeSessionContext {
157157
readonly query: ClaudeQueryRuntime;
158158
streamFiber: Fiber.Fiber<void, Error> | undefined;
159159
readonly startedAt: string;
160-
readonly basePermissionMode: PermissionMode | undefined;
160+
readonly basePermissionMode: PermissionMode;
161161
resumeSessionId: string | undefined;
162162
readonly pendingApprovals: Map<ApprovalRequestId, PendingApproval>;
163163
readonly pendingUserInputs: Map<ApprovalRequestId, PendingUserInput>;
@@ -2790,6 +2790,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
27902790
const permissionMode =
27912791
toPermissionMode(providerOptions?.permissionMode) ??
27922792
(input.runtimeMode === "full-access" ? "bypassPermissions" : undefined);
2793+
const basePermissionMode = permissionMode ?? "default";
27932794
const settings = {
27942795
...(typeof thinking === "boolean" ? { alwaysThinkingEnabled: thinking } : {}),
27952796
...(fastMode ? { fastMode: true } : {}),
@@ -2859,7 +2860,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
28592860
query: queryRuntime,
28602861
streamFiber: undefined,
28612862
startedAt,
2862-
basePermissionMode: permissionMode,
2863+
basePermissionMode,
28632864
resumeSessionId: sessionId,
28642865
pendingApprovals,
28652866
pendingUserInputs,
@@ -2965,8 +2966,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
29652966
});
29662967
} else if (input.interactionMode === "chat" || input.interactionMode === "code") {
29672968
yield* Effect.tryPromise({
2968-
try: () =>
2969-
context.query.setPermissionMode(context.basePermissionMode ?? "bypassPermissions"),
2969+
try: () => context.query.setPermissionMode(context.basePermissionMode),
29702970
catch: (cause) => toRequestError(input.threadId, "turn/setPermissionMode", cause),
29712971
});
29722972
}

0 commit comments

Comments
 (0)