diff --git a/packages/agent/src/adapters/claude/session/options.ts b/packages/agent/src/adapters/claude/session/options.ts index 5b8808e807..7c1def1eec 100644 --- a/packages/agent/src/adapters/claude/session/options.ts +++ b/packages/agent/src/adapters/claude/session/options.ts @@ -449,7 +449,11 @@ export function buildSessionOptions(params: BuildOptionsParams): Options { ...params.userProvidedOptions, betas: ["context-1m-2025-08-07"], systemPrompt: params.systemPrompt ?? buildSystemPrompt(), - settingSources: ["user", "project", "local"], + settingSources: params.userProvidedOptions?.settingSources ?? [ + "user", + "project", + "local", + ], stderr: (err) => params.logger.error(err), cwd: params.cwd, includePartialMessages: true, diff --git a/packages/ui/src/features/setup/setupRunServiceImpl.test.ts b/packages/ui/src/features/setup/setupRunServiceImpl.test.ts new file mode 100644 index 0000000000..6c69a9c5e6 --- /dev/null +++ b/packages/ui/src/features/setup/setupRunServiceImpl.test.ts @@ -0,0 +1,40 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const startMutate = vi.fn(async (_input: Record) => {}); + +vi.mock("@posthog/di/container", () => ({ + resolveService: () => ({ agent: { start: { mutate: startMutate } } }), +})); + +vi.mock("../../shell/analytics", () => ({ + captureException: vi.fn(), + track: vi.fn(), +})); + +import { SetupRunServiceImpl } from "./setupRunServiceImpl"; + +describe("SetupRunServiceImpl.startAgent", () => { + beforeEach(() => { + startMutate.mockClear(); + }); + + it("starts the discovery agent in plan mode, never bypassPermissions", async () => { + const service = new SetupRunServiceImpl(); + + await service.startAgent({ + taskId: "task-1", + taskRunId: "run-1", + repoPath: "/repo", + apiHost: "https://us.posthog.com", + projectId: 1, + jsonSchema: {}, + }); + + expect(startMutate).toHaveBeenCalledTimes(1); + expect(startMutate.mock.calls[0][0]).toMatchObject({ + permissionMode: "plan", + disallowedTools: ["EnterPlanMode", "ExitPlanMode", "AskUserQuestion"], + settingSources: ["user"], + }); + }); +}); diff --git a/packages/ui/src/features/setup/setupRunServiceImpl.ts b/packages/ui/src/features/setup/setupRunServiceImpl.ts index b45537c5fc..e99bb8c544 100644 --- a/packages/ui/src/features/setup/setupRunServiceImpl.ts +++ b/packages/ui/src/features/setup/setupRunServiceImpl.ts @@ -111,7 +111,11 @@ export class SetupRunServiceImpl implements ISetupRunService { repoPath: input.repoPath, apiHost: input.apiHost, projectId: input.projectId, - permissionMode: "bypassPermissions", + permissionMode: "plan", + // Nothing answers a permission prompt on an auto-launched run. + disallowedTools: ["EnterPlanMode", "ExitPlanMode", "AskUserQuestion"], + // Never the repo's own .claude settings: they can carry hooks. + settingSources: ["user"], jsonSchema: input.jsonSchema, }); } diff --git a/packages/workspace-server/src/services/agent/agent.ts b/packages/workspace-server/src/services/agent/agent.ts index e9fc690702..9654cb30b7 100644 --- a/packages/workspace-server/src/services/agent/agent.ts +++ b/packages/workspace-server/src/services/agent/agent.ts @@ -248,6 +248,7 @@ function buildClaudeCodeOptions(args: { effort?: EffortLevel; plugins: { type: "local"; path: string }[]; disallowedTools?: string[]; + settingSources?: ("user" | "project" | "local")[]; }) { return { ...(args.additionalDirectories?.length && { @@ -257,6 +258,9 @@ function buildClaudeCodeOptions(args: { ...(args.disallowedTools?.length && { disallowedTools: args.disallowedTools, }), + ...(args.settingSources?.length && { + settingSources: args.settingSources, + }), plugins: args.plugins, }; } @@ -278,6 +282,7 @@ interface SessionConfig { systemPromptOverride?: string; /** Tool names denied for this session (passed to the Claude SDK). */ disallowedTools?: string[]; + settingSources?: ("user" | "project" | "local")[]; /** Effort level for Claude sessions */ effort?: EffortLevel; /** Model to use for the session (e.g. "claude-sonnet-4-6") */ @@ -779,6 +784,7 @@ If a repository IS genuinely required, attach one in this priority order: customInstructions, systemPromptOverride, disallowedTools, + settingSources, effort, model, jsonSchema, @@ -1010,6 +1016,7 @@ If a repository IS genuinely required, attach one in this priority order: effort, plugins, disallowedTools, + settingSources, }); let configOptions: SessionConfigOption[] | undefined; @@ -2131,6 +2138,8 @@ For git operations while detached: : undefined, disallowedTools: "disallowedTools" in params ? params.disallowedTools : undefined, + settingSources: + "settingSources" in params ? params.settingSources : undefined, effort: "effort" in params ? params.effort : undefined, model: "model" in params ? params.model : undefined, jsonSchema: "jsonSchema" in params ? params.jsonSchema : undefined, diff --git a/packages/workspace-server/src/services/agent/schemas.ts b/packages/workspace-server/src/services/agent/schemas.ts index 5d8eeab508..548865f337 100644 --- a/packages/workspace-server/src/services/agent/schemas.ts +++ b/packages/workspace-server/src/services/agent/schemas.ts @@ -77,6 +77,7 @@ export const startSessionInput = z.object({ * Lets a sandboxed surface deny file/shell/network tools. */ disallowedTools: z.array(z.string()).optional(), + settingSources: z.array(z.enum(["user", "project", "local"])).optional(), effort: effortLevelSchema.optional(), model: z.string().optional(), jsonSchema: z.record(z.string(), z.unknown()).nullish(),