From f532755c459260147a5426fcfd311e865170f35d Mon Sep 17 00:00:00 2001 From: Christopher Tso Date: Mon, 13 Apr 2026 13:19:45 +0000 Subject: [PATCH] fix(core): allow mode=static with no repos and no path When workspace.mode=static is set without workspace.path and there are no repos, fall back to temp mode instead of erroring. The static mode's purpose is to skip repo cloning, which is already a no-op when there are no repos. Closes #1082 Co-Authored-By: Claude Opus 4.6 --- packages/core/src/evaluation/orchestrator.ts | 17 ++++++++--- .../core/test/evaluation/orchestrator.test.ts | 30 +++++++++++++++++-- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/core/src/evaluation/orchestrator.ts b/packages/core/src/evaluation/orchestrator.ts index 667cc1ba..7f910cf3 100644 --- a/packages/core/src/evaluation/orchestrator.ts +++ b/packages/core/src/evaluation/orchestrator.ts @@ -731,10 +731,22 @@ export async function runEvaluation( if (cliWorkspacePath && workspaceMode && workspaceMode !== 'static') { throw new Error('--workspace-path requires --workspace-mode static when both are provided'); } - const configuredMode = cliWorkspacePath + let configuredMode = cliWorkspacePath ? 'static' : (workspaceMode ?? suiteWorkspace?.mode ?? (yamlWorkspacePath ? 'static' : 'pooled')); const configuredStaticPath = cliWorkspacePath ?? yamlWorkspacePath; + + // When mode=static with no path and no repos, the static mode is a no-op (nothing to + // skip cloning). Fall back to temp mode so agentv creates a temp directory automatically. + if (configuredMode === 'static' && !configuredStaticPath) { + if (!suiteWorkspace?.repos?.length) { + setupLog('workspace.mode=static with no path and no repos — falling back to temp mode'); + configuredMode = 'temp'; + } else { + throw new Error('workspace.mode=static requires workspace.path or --workspace-path'); + } + } + const useStaticWorkspace = configuredMode === 'static'; // static workspace is incompatible with per_test isolation @@ -743,9 +755,6 @@ export async function runEvaluation( 'static workspace mode is incompatible with isolation: per_test. Use isolation: shared (default).', ); } - if (configuredMode === 'static' && !configuredStaticPath) { - throw new Error('workspace.mode=static requires workspace.path or --workspace-path'); - } if (configuredMode !== 'static' && configuredStaticPath) { throw new Error('workspace.path requires workspace.mode=static'); } diff --git a/packages/core/test/evaluation/orchestrator.test.ts b/packages/core/test/evaluation/orchestrator.test.ts index 2f25f927..21e4d986 100644 --- a/packages/core/test/evaluation/orchestrator.test.ts +++ b/packages/core/test/evaluation/orchestrator.test.ts @@ -3126,11 +3126,37 @@ fs.writeFileSync(path.join(payload.workspace_path, 'hook.txt'), payload.test_id expect(results[0].error).toBeUndefined(); }); - it('errors when workspaceMode is static without workspace path', async () => { + it('falls back to temp mode when workspaceMode is static with no path and no repos', async () => { const provider = new SequenceProvider('mock', { responses: [{ output: [{ role: 'assistant', content: [{ type: 'text', text: 'answer' }] }] }], }); + const results = await runEvaluation({ + testFilePath: 'in-memory.yaml', + repoRoot: 'in-memory', + target: baseTarget, + providerFactory: () => provider, + evaluators: evaluatorRegistry, + evalCases: [baseTestCase], + workspaceMode: 'static', + }); + + expect(results).toHaveLength(1); + expect(results[0].error).toBeUndefined(); + }); + + it('errors when workspaceMode is static without workspace path but with repos', async () => { + const provider = new SequenceProvider('mock', { + responses: [{ output: [{ role: 'assistant', content: [{ type: 'text', text: 'answer' }] }] }], + }); + + const evalCase = { + ...baseTestCase, + workspace: { + repos: [{ source: { type: 'git' as const, url: 'https://example.com/repo.git' } }], + }, + }; + await expect( runEvaluation({ testFilePath: 'in-memory.yaml', @@ -3138,7 +3164,7 @@ fs.writeFileSync(path.join(payload.workspace_path, 'hook.txt'), payload.test_id target: baseTarget, providerFactory: () => provider, evaluators: evaluatorRegistry, - evalCases: [baseTestCase], + evalCases: [evalCase], workspaceMode: 'static', }), ).rejects.toThrow('workspace.mode=static requires workspace.path or --workspace-path');