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
2 changes: 2 additions & 0 deletions apps/web/src/content/docs/docs/guides/workspace-pool.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,8 @@ Instead of duplicating workspace configuration across eval files, you can refere
workspace: ./path/to/workspace.yaml
```

The external file should contain the workspace config object directly, not a nested `workspace:` key.

The path is resolved relative to the eval file's directory. Relative paths **inside** the workspace file (template, repo source paths) resolve from the workspace file's own directory.

This pattern is especially valuable with pooling: a single `workspace.yaml` guarantees all eval files that reference it produce the same fingerprint and share the same pool.
Expand Down
18 changes: 17 additions & 1 deletion packages/core/src/evaluation/yaml-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,23 @@ async function resolveWorkspaceConfig(
}
// Resolve paths relative to the workspace file's directory
const workspaceFileDir = path.dirname(workspaceFilePath);
return parseWorkspaceConfig(parsed, workspaceFileDir);
const resolvedWorkspace = parseWorkspaceConfig(parsed, workspaceFileDir);
if (resolvedWorkspace) {
return resolvedWorkspace;
}

const parsedObject = parsed as Record<string, unknown>;
if ('workspace' in parsedObject && isJsonObject(parsedObject.workspace)) {
throw new Error(
[
`Invalid workspace file format: ${workspaceFilePath}`,
'External workspace files must contain the workspace config object directly.',
'Remove the top-level "workspace:" wrapper.',
].join(' '),
);
}

return undefined;
}
return parseWorkspaceConfig(raw, evalFileDir);
}
Expand Down
33 changes: 33 additions & 0 deletions packages/core/test/evaluation/workspace-config-parsing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,39 @@ tests:
);
});

it('should throw a clear error when external workspace file wraps config under workspace', async () => {
const wsDir = path.join(testDir, 'wrapped-workspace');
await mkdir(wsDir, { recursive: true });

const workspaceFile = path.join(wsDir, 'workspace.yaml');
await writeFile(
workspaceFile,
`
workspace:
hooks:
after_each:
reset: fast
`,
);

const evalFile = path.join(testDir, 'wrapped-workspace-eval.yaml');
await writeFile(
evalFile,
`
workspace: ./wrapped-workspace/workspace.yaml

tests:
- id: wrapped-workspace
input: "Do something"
criteria: "Should work"
`,
);

await expect(loadTests(evalFile, testDir)).rejects.toThrow(
/External workspace files must contain the workspace config object directly.*Remove the top-level "workspace:" wrapper/,
);
});

it('should allow per-case workspace override with external suite workspace', async () => {
const wsDir = path.join(testDir, 'override-shared');
await mkdir(wsDir, { recursive: true });
Expand Down
Loading