Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 8d062fb

Browse files
committed
run setup discovery agent in plan mode
1 parent 9e370a3 commit 8d062fb

5 files changed

Lines changed: 69 additions & 2 deletions

File tree

packages/agent/src/adapters/claude/session/options.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -449,7 +449,11 @@ export function buildSessionOptions(params: BuildOptionsParams): Options {
449449
...params.userProvidedOptions,
450450
betas: ["context-1m-2025-08-07"],
451451
systemPrompt: params.systemPrompt ?? buildSystemPrompt(),
452-
settingSources: ["user", "project", "local"],
452+
settingSources: params.userProvidedOptions?.settingSources ?? [
453+
"user",
454+
"project",
455+
"local",
456+
],
453457
stderr: (err) => params.logger.error(err),
454458
cwd: params.cwd,
455459
includePartialMessages: true,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { beforeEach, describe, expect, it, vi } from "vitest";
2+
3+
const startMutate = vi.fn(async (_input: Record<string, unknown>) => {});
4+
5+
vi.mock("@posthog/di/container", () => ({
6+
resolveService: () => ({ agent: { start: { mutate: startMutate } } }),
7+
}));
8+
9+
vi.mock("../../shell/analytics", () => ({
10+
captureException: vi.fn(),
11+
track: vi.fn(),
12+
}));
13+
14+
import { SetupRunServiceImpl } from "./setupRunServiceImpl";
15+
16+
describe("SetupRunServiceImpl.startAgent", () => {
17+
beforeEach(() => {
18+
startMutate.mockClear();
19+
});
20+
21+
it("starts the discovery agent in plan mode, never bypassPermissions", async () => {
22+
const service = new SetupRunServiceImpl();
23+
24+
await service.startAgent({
25+
taskId: "task-1",
26+
taskRunId: "run-1",
27+
repoPath: "/repo",
28+
apiHost: "https://us.posthog.com",
29+
projectId: 1,
30+
jsonSchema: {},
31+
});
32+
33+
expect(startMutate).toHaveBeenCalledTimes(1);
34+
expect(startMutate.mock.calls[0][0]).toMatchObject({
35+
permissionMode: "plan",
36+
disallowedTools: ["EnterPlanMode", "ExitPlanMode", "AskUserQuestion"],
37+
settingSources: ["user"],
38+
});
39+
});
40+
});

packages/ui/src/features/setup/setupRunServiceImpl.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,14 @@ export class SetupRunServiceImpl implements ISetupRunService {
111111
repoPath: input.repoPath,
112112
apiHost: input.apiHost,
113113
projectId: input.projectId,
114-
permissionMode: "bypassPermissions",
114+
// Read-only scan: plan mode denies write/exec tools (VERIA-356).
115+
permissionMode: "plan",
116+
// Deny tools that would open a permission prompt no one answers on an
117+
// auto-launched run, which an injected repo could exploit to hang it.
118+
disallowedTools: ["EnterPlanMode", "ExitPlanMode", "AskUserQuestion"],
119+
// Skip the repo's project/local .claude settings so a committed hook
120+
// can't execute when discovery auto-starts against an untrusted repo.
121+
settingSources: ["user"],
115122
jsonSchema: input.jsonSchema,
116123
});
117124
}

packages/workspace-server/src/services/agent/agent.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ function buildClaudeCodeOptions(args: {
248248
effort?: EffortLevel;
249249
plugins: { type: "local"; path: string }[];
250250
disallowedTools?: string[];
251+
settingSources?: ("user" | "project" | "local")[];
251252
}) {
252253
return {
253254
...(args.additionalDirectories?.length && {
@@ -257,6 +258,9 @@ function buildClaudeCodeOptions(args: {
257258
...(args.disallowedTools?.length && {
258259
disallowedTools: args.disallowedTools,
259260
}),
261+
...(args.settingSources?.length && {
262+
settingSources: args.settingSources,
263+
}),
260264
plugins: args.plugins,
261265
};
262266
}
@@ -278,6 +282,8 @@ interface SessionConfig {
278282
systemPromptOverride?: string;
279283
/** Tool names denied for this session (passed to the Claude SDK). */
280284
disallowedTools?: string[];
285+
/** SDK setting sources to load; omit for the default user+project+local. */
286+
settingSources?: ("user" | "project" | "local")[];
281287
/** Effort level for Claude sessions */
282288
effort?: EffortLevel;
283289
/** Model to use for the session (e.g. "claude-sonnet-4-6") */
@@ -779,6 +785,7 @@ If a repository IS genuinely required, attach one in this priority order:
779785
customInstructions,
780786
systemPromptOverride,
781787
disallowedTools,
788+
settingSources,
782789
effort,
783790
model,
784791
jsonSchema,
@@ -1010,6 +1017,7 @@ If a repository IS genuinely required, attach one in this priority order:
10101017
effort,
10111018
plugins,
10121019
disallowedTools,
1020+
settingSources,
10131021
});
10141022

10151023
let configOptions: SessionConfigOption[] | undefined;
@@ -2131,6 +2139,8 @@ For git operations while detached:
21312139
: undefined,
21322140
disallowedTools:
21332141
"disallowedTools" in params ? params.disallowedTools : undefined,
2142+
settingSources:
2143+
"settingSources" in params ? params.settingSources : undefined,
21342144
effort: "effort" in params ? params.effort : undefined,
21352145
model: "model" in params ? params.model : undefined,
21362146
jsonSchema: "jsonSchema" in params ? params.jsonSchema : undefined,

packages/workspace-server/src/services/agent/schemas.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,12 @@ export const startSessionInput = z.object({
7777
* Lets a sandboxed surface deny file/shell/network tools.
7878
*/
7979
disallowedTools: z.array(z.string()).optional(),
80+
/**
81+
* SDK setting sources to load. Discovery passes ["user"] to skip the repo's
82+
* project/local .claude settings, so a committed hook can't execute on an
83+
* auto-launched scan (VERIA-356). Omit for the default user+project+local.
84+
*/
85+
settingSources: z.array(z.enum(["user", "project", "local"])).optional(),
8086
effort: effortLevelSchema.optional(),
8187
model: z.string().optional(),
8288
jsonSchema: z.record(z.string(), z.unknown()).nullish(),

0 commit comments

Comments
 (0)