diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index a75a3c993ac5..88fae178ccf1 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -1233,8 +1233,11 @@ export const layer = Layer.effect( permissions.push({ permission: t, action: enabled ? "allow" : "deny", pattern: "*" }) } if (permissions.length > 0) { - session.permission = permissions - yield* sessions.setPermission({ sessionID: session.id, permission: permissions }) + // Merge so per-call tool rules don't clobber inherited session rules + // (e.g. external_directory allows from the parent session). + const merged = Permission.merge(session.permission ?? [], permissions) + session.permission = merged + yield* sessions.setPermission({ sessionID: session.id, permission: merged }) } if (input.noReply === true) return message diff --git a/packages/opencode/test/session/prompt.test.ts b/packages/opencode/test/session/prompt.test.ts index 7c2f6be0f071..6c21d5d6f86c 100644 --- a/packages/opencode/test/session/prompt.test.ts +++ b/packages/opencode/test/session/prompt.test.ts @@ -795,6 +795,33 @@ it.instance("failed subtask preserves metadata on error tool state", () => }), ) +it.instance("subtask child inherits parent session external_directory allow", () => + Effect.gen(function* () { + const { llm } = yield* useServerConfig(providerCfg) + const prompt = yield* SessionPrompt.Service + const sessions = yield* Session.Service + const chat = yield* sessions.create({ + title: "Parent", + permission: [{ permission: "external_directory", pattern: "/tmp/allowed/*", action: "allow" }], + }) + yield* llm.text("done") + const msg = yield* user(chat.id, "hello") + yield* addSubtask(chat.id, msg.id) + + yield* prompt.loop({ sessionID: chat.id }) + + const kids = yield* sessions.children(chat.id) + expect(kids).toHaveLength(1) + const child = kids[0]! + const rules = child.permission ?? [] + expect(rules).toEqual( + expect.arrayContaining([{ permission: "external_directory", pattern: "/tmp/allowed/*", action: "allow" }]), + ) + expect(Permission.evaluate("external_directory", "/tmp/allowed/file", rules).action).toBe("allow") + expect(Permission.evaluate("task", "anything", rules).action).toBe("deny") + }), +) + it.instance( "running subtask preserves metadata after tool-call transition", () =>