fix(agents): surface worktree fallback when sync agents background

Share async_launched payload construction so the sync-to-background path
includes worktreeIsolationFallback when worktree isolation soft-falls back.
This commit is contained in:
jatmn
2026-07-29 07:18:09 -07:00
parent 4633529b49
commit ecea66b6b6
2 changed files with 62 additions and 16 deletions
@@ -5,6 +5,7 @@ import { join } from 'path'
import {
AgentTool,
assertAgentToolCwdAllowed,
buildAsyncLaunchedToolData,
buildWorktreeIsolationFallbackNotice,
formatWorktreeIsolationFallbackResultText,
fullInputSchema,
@@ -205,6 +206,36 @@ describe('AgentTool input schema isolation contract', () => {
)
})
test('buildAsyncLaunchedToolData carries worktree isolation fallback for backgrounded sync agents', () => {
const data = buildAsyncLaunchedToolData({
agentId: 'agent-bg-1',
description: baseInput.description,
prompt: baseInput.prompt,
canReadOutputFile: true,
worktreeIsolationFallback: true,
})
expect(data.worktreeIsolationFallback).toBe(true)
expect(outputSchema().safeParse(data).success).toBe(true)
const block = AgentTool.mapToolResultToToolResultBlockParam(data, 'toolu_bg')
const text = block.content[0]?.type === 'text' ? block.content[0].text : ''
expect(text).toContain('worktreeIsolationFallback: true')
expect(text).toContain('ran without an isolated worktree')
})
test('buildAsyncLaunchedToolData omits fallback when isolation succeeded', () => {
const data = buildAsyncLaunchedToolData({
agentId: 'agent-bg-2',
description: baseInput.description,
prompt: baseInput.prompt,
canReadOutputFile: false,
worktreeIsolationFallback: false,
})
expect(data.worktreeIsolationFallback).toBeUndefined()
})
test('prefers worktree cwd over explicit cwd when both are present defensively', () => {
const worktreePath = join(tmpdir(), 'openclaude-worktree')
expect(
+31 -16
View File
@@ -216,6 +216,26 @@ export function formatWorktreeIsolationFallbackResultText(): string {
return 'worktreeIsolationFallback: true\nnote: Worktree isolation was unavailable; this agent ran without an isolated worktree (edits are not sandboxed in a worktree).';
}
/** Shared async_launched payload for direct-async and sync-to-background paths. */
export function buildAsyncLaunchedToolData(args: {
agentId: string;
description: string;
prompt: string;
canReadOutputFile: boolean;
worktreeIsolationFallback?: boolean;
}) {
return {
isAsync: true as const,
status: 'async_launched' as const,
agentId: args.agentId,
description: args.description,
prompt: args.prompt,
outputFile: getTaskOutputPath(args.agentId),
canReadOutputFile: args.canReadOutputFile,
...(args.worktreeIsolationFallback && { worktreeIsolationFallback: true as const }),
};
}
// Output schema - multi-agent spawned schema added dynamically at runtime when enabled
export const outputSchema = lazySchema(() => {
const syncOutputSchema = agentToolResultSchema().extend({
@@ -910,16 +930,13 @@ export const AgentTool = buildTool({
})));
const canReadOutputFile = toolUseContext.options.tools.some(t => toolMatchesName(t, FILE_READ_TOOL_NAME) || toolMatchesName(t, BASH_TOOL_NAME));
return {
data: {
isAsync: true as const,
status: 'async_launched' as const,
data: buildAsyncLaunchedToolData({
agentId: agentBackgroundTask.agentId,
description: description,
prompt: prompt,
outputFile: getTaskOutputPath(agentBackgroundTask.agentId),
description,
prompt,
canReadOutputFile,
...(worktreeIsolationFallback && { worktreeIsolationFallback: true as const }),
}
worktreeIsolationFallback,
}),
};
} else {
// Create an explicit agentId for sync agents
@@ -1202,15 +1219,13 @@ export const AgentTool = buildTool({
// Return async_launched result immediately
const canReadOutputFile = toolUseContext.options.tools.some(t => toolMatchesName(t, FILE_READ_TOOL_NAME) || toolMatchesName(t, BASH_TOOL_NAME));
return {
data: {
isAsync: true as const,
status: 'async_launched' as const,
data: buildAsyncLaunchedToolData({
agentId: backgroundedTaskId,
description: description,
prompt: prompt,
outputFile: getTaskOutputPath(backgroundedTaskId),
canReadOutputFile
}
description,
prompt,
canReadOutputFile,
worktreeIsolationFallback,
}),
};
}
}