Skip to content

Commit 428c4d8

Browse files
remorsessimonklee
andauthored
fix(session): merge per-call tool rules into session permission (anomalyco#30529)
Co-authored-by: Simon Klee <hello@simonklee.dk>
1 parent ed8c169 commit 428c4d8

2 files changed

Lines changed: 32 additions & 2 deletions

File tree

packages/opencode/src/session/prompt.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,8 +1198,11 @@ export const layer = Layer.effect(
11981198
permissions.push({ permission: t, action: enabled ? "allow" : "deny", pattern: "*" })
11991199
}
12001200
if (permissions.length > 0) {
1201-
session.permission = permissions
1202-
yield* sessions.setPermission({ sessionID: session.id, permission: permissions })
1201+
// Merge so per-call tool rules don't clobber inherited session rules
1202+
// (e.g. external_directory allows from the parent session).
1203+
const merged = Permission.merge(session.permission ?? [], permissions)
1204+
session.permission = merged
1205+
yield* sessions.setPermission({ sessionID: session.id, permission: merged })
12031206
}
12041207

12051208
if (input.noReply === true) return message

packages/opencode/test/session/prompt.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,33 @@ it.instance("failed subtask preserves metadata on error tool state", () =>
797797
}),
798798
)
799799

800+
it.instance("subtask child inherits parent session external_directory allow", () =>
801+
Effect.gen(function* () {
802+
const { llm } = yield* useServerConfig(providerCfg)
803+
const prompt = yield* SessionPrompt.Service
804+
const sessions = yield* Session.Service
805+
const chat = yield* sessions.create({
806+
title: "Parent",
807+
permission: [{ permission: "external_directory", pattern: "/tmp/allowed/*", action: "allow" }],
808+
})
809+
yield* llm.text("done")
810+
const msg = yield* user(chat.id, "hello")
811+
yield* addSubtask(chat.id, msg.id)
812+
813+
yield* prompt.loop({ sessionID: chat.id })
814+
815+
const kids = yield* sessions.children(chat.id)
816+
expect(kids).toHaveLength(1)
817+
const child = kids[0]!
818+
const rules = child.permission ?? []
819+
expect(rules).toEqual(
820+
expect.arrayContaining([{ permission: "external_directory", pattern: "/tmp/allowed/*", action: "allow" }]),
821+
)
822+
expect(Permission.evaluate("external_directory", "/tmp/allowed/file", rules).action).toBe("allow")
823+
expect(Permission.evaluate("task", "anything", rules).action).toBe("deny")
824+
}),
825+
)
826+
800827
it.instance(
801828
"running subtask preserves metadata after tool-call transition",
802829
() =>

0 commit comments

Comments
 (0)