Skip to content

Commit f7822bc

Browse files
committed
fix(core): reject authored use_target targets
Entire-Checkpoint: 99ac33df0820
1 parent 0d9c676 commit f7822bc

3 files changed

Lines changed: 30 additions & 34 deletions

File tree

packages/core/src/evaluation/providers/targets.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -908,7 +908,6 @@ export type ResolvedTarget =
908908
* here automatically makes it valid in targets.yaml without a separate update.
909909
*/
910910
export const COMMON_TARGET_SETTINGS = [
911-
'use_target',
912911
'runtime',
913912
'batch_requests',
914913
'subagent_mode_allowed',

packages/core/src/evaluation/validation/targets-validator.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@ function validateUnknownSettings(
347347
'env',
348348
'grader_target',
349349
'judge_target',
350+
'use_target',
350351
'workers',
351352
'$schema',
352353
'targets',
@@ -695,17 +696,23 @@ export async function validateTargetsFile(filePath: string): Promise<ValidationR
695696
const provider = effectiveTarget.provider;
696697
const rawTarget = rawTargets[i];
697698
const rawUseTarget = isObject(rawTarget) ? rawTarget.use_target : undefined;
698-
const hasUseTarget =
699-
isNonEmptyString(effectiveTarget.use_target) || isNonEmptyString(rawUseTarget);
699+
if (effectiveTarget.use_target !== undefined || rawUseTarget !== undefined) {
700+
errors.push({
701+
severity: 'error',
702+
filePath: absolutePath,
703+
location: `${location}.use_target`,
704+
message:
705+
"The 'use_target' field has been removed from authored targets.yaml target definitions. Define a concrete target with 'provider' instead.",
706+
});
707+
}
700708
const providerValue = typeof provider === 'string' ? provider.trim().toLowerCase() : undefined;
701709
const isTemplated = isEnvTemplated(provider);
702-
if (!hasUseTarget && (typeof provider !== 'string' || provider.trim().length === 0)) {
710+
if (typeof provider !== 'string' || provider.trim().length === 0) {
703711
errors.push({
704712
severity: 'error',
705713
filePath: absolutePath,
706714
location: `${location}.provider`,
707-
message:
708-
"Missing or invalid 'provider' field (must be a non-empty string, or use use_target for delegation)",
715+
message: "Missing or invalid 'provider' field (must be a non-empty string)",
709716
});
710717
} else if (!isTemplated && (providerValue === 'claude' || providerValue === 'copilot')) {
711718
errors.push({

packages/core/test/evaluation/validation/targets-validator.test.ts

Lines changed: 18 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -408,12 +408,13 @@ targets:
408408
).toBe(true);
409409
});
410410

411-
it('accepts env-templated use_target values without resolving the env during validation', async () => {
411+
it('rejects use_target on authored target definitions', async () => {
412412
const filePath = path.join(tempDir, 'templated-use-target.yaml');
413413
await writeFile(
414414
filePath,
415415
`targets:
416416
- id: default
417+
provider: mock
417418
use_target: "{{ env.AGENT_TARGET }}"
418419
- id: grader
419420
use_target: "{{ env.GRADER_TARGET }}"
@@ -424,34 +425,23 @@ targets:
424425
`,
425426
);
426427

427-
const originalAgentTarget = process.env.AGENT_TARGET;
428-
const originalGraderTarget = process.env.GRADER_TARGET;
429-
Reflect.deleteProperty(process.env, 'AGENT_TARGET');
430-
Reflect.deleteProperty(process.env, 'GRADER_TARGET');
431-
432-
try {
433-
const result = await validateTargetsFile(filePath);
428+
const result = await validateTargetsFile(filePath);
434429

435-
expect(result.valid).toBe(true);
436-
expect(
437-
result.errors.some(
438-
(error) =>
439-
error.severity === 'error' &&
440-
error.message.includes("Missing or invalid 'provider' field"),
441-
),
442-
).toBe(false);
443-
} finally {
444-
if (originalAgentTarget === undefined) {
445-
Reflect.deleteProperty(process.env, 'AGENT_TARGET');
446-
} else {
447-
process.env.AGENT_TARGET = originalAgentTarget;
448-
}
449-
if (originalGraderTarget === undefined) {
450-
Reflect.deleteProperty(process.env, 'GRADER_TARGET');
451-
} else {
452-
process.env.GRADER_TARGET = originalGraderTarget;
453-
}
454-
}
430+
expect(result.valid).toBe(false);
431+
expect(result.errors).toContainEqual(
432+
expect.objectContaining({
433+
severity: 'error',
434+
location: 'targets[0].use_target',
435+
message: expect.stringContaining("'use_target' field has been removed"),
436+
}),
437+
);
438+
expect(result.errors).toContainEqual(
439+
expect.objectContaining({
440+
severity: 'error',
441+
location: 'targets[1].use_target',
442+
message: expect.stringContaining("'use_target' field has been removed"),
443+
}),
444+
);
455445
});
456446

457447
it('rejects legacy env interpolation in target YAML', async () => {

0 commit comments

Comments
 (0)