Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/reports/e2e-smoke-2026-10-25.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,11 @@ The spawned `claude` inherits the user's global configuration. In the rerun, the
### Still untested

Tier-2 auto-resolution on a real conflict, three or more conflicting tasks, an agent that fails or times out, `gemini`/`codex`.

## Third run: agents isolated from the operator's Claude Code setup (2026-10-26)

Change: `claude` is started with `--setting-sources project,local` (opt back in with `FSC_CLAUDE_USER_SETTINGS=1`) and with stdin closed.

- Trivial prompt, default vs isolated: 22 s vs 9 s. Default: the agent could recite the operator's tracker rules and the Stop hook; isolated: it knew neither.
- Same two-task repo as the first run, real `claude` agents: exec phase **20 s** (first run: 77 s). Both tasks merged at tier 1; `main` has the two implementations; agent logs contain no tracker or hook talk.
- **Correction:** this run took 15 min 28 s wall clock, not 20 s: analysis and execution finished within about a minute (file timestamps: 20 s + 27 s) and the process then idled for ~14 minutes. So the slow runs are not explained by a user-level hook (this run had them isolated). The cause is a different defect, found right after this report and fixed in a follow-up PR: the agent timeout timer was never cleared, so a successful run kept the process alive until the 15-minute timeout fired. Failing runs exited through `process.exit(1)` and never showed it. That also explains why only successful runs were slow.
3 changes: 3 additions & 0 deletions packages/core/src/analyzerNative.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { analyzerPrompt } from './prompts/analyzerPrompt';
import { getCodingStyle } from './codingStyle';
import { getWorkStyleDescription, WorkStyle } from './workStyle';
import { trimJSONObjectArray } from './utils/trimJSON';
import { claudeIsolationArgs } from './execution/types';

const ALLOWED_TOOLS = 'Bash,ReadEdit,Glob,Grep,WebFetch,WebSearch,Write,TodoWrite,SlashCommand';
const DEFAULT_TIMEOUT_SECONDS = 1200;
Expand Down Expand Up @@ -91,9 +92,11 @@ export async function analyzeCodebaseNative(config: Config, repoPath: string): P
ALLOWED_TOOLS,
'--permission-mode',
'bypassPermissions',
...claudeIsolationArgs(),
],
cwd: scratch,
env: { ...agentEnv(config), IS_SANDBOX: '1' },
stdin: 'ignore',
stdout: 'pipe',
stderr: 'pipe',
});
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/execution/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ export class LocalExecution implements ExecutionStrategy {
FSC_WORKTREE: worktreePath,
...(unit.context?.memorixUri ? { MEMORIX_URL: unit.context.memorixUri } : {}),
},
// No stdin: an inherited, silent stdin makes `claude -p` wait ~3 s before it starts.
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
});
Expand Down
19 changes: 17 additions & 2 deletions packages/core/src/execution/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,24 @@ export interface WorktreeInfo {
}

// Pre-built agent arg builders
/**
* Agents must not inherit the operator's personal Claude Code setup: user-level hooks (a Stop hook
* can block or slow a headless run) and the user's global CLAUDE.md both apply by default. Measured
* on a trivial prompt: default 22 s and the agent recited the operator's tracker rules; with
* `--setting-sources project,local` 9 s and none of it. Project and local settings still apply.
* Set FSC_CLAUDE_USER_SETTINGS=1 to opt back in.
*/
export function claudeIsolationArgs(): string[] {
return process.env.FSC_CLAUDE_USER_SETTINGS === "1" ? [] : ["--setting-sources", "project,local"];
}

export const AGENT_BUILDERS: Record<string, AgentConfig["buildArgs"]> = {
claude: (prompt: string, cwd: string) =>
["-p", prompt, "--allowedTools", "Edit,Write,Bash,Read,Glob,Grep", "--permission-mode", "bypassPermissions"],
claude: (prompt: string, _cwd: string) => [
"-p", prompt,
"--allowedTools", "Edit,Write,Bash,Read,Glob,Grep",
"--permission-mode", "bypassPermissions",
...claudeIsolationArgs(),
],
codex: (prompt: string, cwd: string) =>
["exec", "--approval-mode", "full-auto", "-w", cwd, prompt],
gemini: (prompt: string, cwd: string) =>
Expand Down
30 changes: 30 additions & 0 deletions packages/core/test/execution/mapping.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,33 @@ describe("normalizeAgentType", () => {
expect(args).toContain("--permission-mode");
});
});

describe("claude isolation", () => {
const { toWorkUnitForAgent } = require("../../src/execution/mapping");
const task = { ID: "t", title: "x", description: "y", priority: 1 };

test("agents ignore the operator's user-level settings by default", () => {
const prev = process.env.FSC_CLAUDE_USER_SETTINGS;
delete process.env.FSC_CLAUDE_USER_SETTINGS;
try {
const args = toWorkUnitForAgent(task, "claude-code", ".").agentConfig.buildArgs("p", "/wt");
const i = args.indexOf("--setting-sources");
expect(i).toBeGreaterThan(-1);
expect(args[i + 1]).toBe("project,local");
} finally {
if (prev !== undefined) process.env.FSC_CLAUDE_USER_SETTINGS = prev;
}
});

test("FSC_CLAUDE_USER_SETTINGS=1 opts back in", () => {
const prev = process.env.FSC_CLAUDE_USER_SETTINGS;
process.env.FSC_CLAUDE_USER_SETTINGS = "1";
try {
const args = toWorkUnitForAgent(task, "claude-code", ".").agentConfig.buildArgs("p", "/wt");
expect(args).not.toContain("--setting-sources");
} finally {
if (prev === undefined) delete process.env.FSC_CLAUDE_USER_SETTINGS;
else process.env.FSC_CLAUDE_USER_SETTINGS = prev;
}
});
});
Loading