Skip to content

Commit badd642

Browse files
jariy17jariy17
andauthored
fix: resolve the gateway target for A/B test invocation URLs (#1854) (#1874)
* fix: stop putting the runtime name in A/B test invocation URLs (#1854) `getInvocationUrl` built config-bundle URLs from `record.agent` — the RUNTIME name — but a gateway invocation path segment must be a gateway TARGET name. Whenever the two differed, every request to the printed URL failed: {"success":false,"error":"No Target found for Target name: CustomerSupportAB"} A config-bundle test has no target of its own to substitute. Its variants are configuration bundles, it attaches to the whole gateway, and the service splits traffic with a gateway rule — so the path segment is whichever gateway target the caller invokes, which the CLI cannot know. (Confirmed against the service: config-bundle tests store only `configurationBundle` per variant, and the config-bundle e2e creates one on a gateway with no targets at all.) So report the gateway base URL and name what to append, rather than guessing a path that 404s. Target-based tests are unchanged — their variants *are* targets, so `variants[0].targetName` is a real path. Gateway URL: https://<gatewayId>.gateway.bedrock-agentcore.<region>.amazonaws.com → append /<gateway-target>/invocations (see `agentcore status --json`) `view ab-test --json` reports `gatewayUrl` + `invocationUrlHint` for config-bundle tests instead of `invocationUrl`, so scripts reading `.invocationUrl` get nothing rather than a URL that fails. Target-based keeps `invocationUrl` unchanged. Also corrects two comments that documented the bug as intended behaviour: `docs/ab-tests.md` ("config-bundle uses the agent name") and a claim in RunABTestFlow that targets deploy as `${project}-${target}` — the L3 CDK deploys each target under its spec name verbatim. * fix: resolve the gateway target for A/B test invocation URLs (#1854) Config-bundle tests printed an invocation URL built from the runtime name, but a gateway path segment must be a gateway TARGET name, so requests 404'd with "No Target found for Target name: <runtime>". Resolve the gateway target(s) fronting the runtime at create time — the link lives in `agentCoreGateways[].targets[].httpRuntime.runtime`, which create() already loads — and persist the result on the record: - exactly one match -> `targetName` -> a complete `invocationUrl` - several matches -> `targetCandidates` -> `invocationUrlCandidates` (pick one) - none -> neither -> `gatewayUrl` + `invocationUrlHint` This fixes both same-name and different-name configurations (the previous gateway-base-only output regressed the same-name case and left the workflow manual), and keeps `.invocationUrl` populated whenever a single target is known. The runtime name is never used as the path. Target-based tests are unchanged. `getInvocationUrl` stays synchronous — resolution happens once at create and is read from the record — so `view --json`, `printABTestDetail`, and the TUI detail view need no plumbing changes. Also corrects two comments that documented the bug as intended behaviour. --------- Co-authored-by: jariy17 <tjariy+jariy17@users.noreply.github.com>
1 parent 369c404 commit badd642

11 files changed

Lines changed: 333 additions & 27 deletions

File tree

docs/ab-tests.md

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,14 +115,25 @@ Promote does not deploy — review the change and run `agentcore deploy` to roll
115115

116116
## Invocation URL
117117

118-
`view ab-test <id>` shows an **Invocation URL** derived from the test's gateway. Send traffic there and the gateway
119-
splits it between the variants per the configured weights:
118+
`view ab-test <id>` shows a URL derived from the test's gateway. Send traffic there and the gateway splits it between
119+
the variants per the configured weights:
120120

121121
```
122-
https://<gatewayId>.gateway.bedrock-agentcore.<region>.amazonaws.com/<target-or-agent>/invocations
122+
https://<gatewayId>.gateway.bedrock-agentcore.<region>.amazonaws.com/<gateway-target>/invocations
123123
```
124124

125-
(target-based uses the control target's path; config-bundle uses the agent name.)
125+
The path segment is always a **gateway target** name — never a runtime name. Config-bundle tests carry no target of
126+
their own (their variants are configuration bundles), so the CLI resolves the gateway target(s) fronting the `--runtime`
127+
under test when the test is created:
128+
129+
- **one matching target** (the common case, including target-based tests) — a complete **Invocation URL**.
130+
- **several matching targets** (e.g. a canary beside prod) — one **Invocation URL** per target; pick the one to send
131+
traffic to.
132+
- **no matching target** — the **Gateway URL** only; append `/<gateway-target>/invocations` yourself, using
133+
`agentcore status --json` to list the gateway's targets.
134+
135+
With `--json` the field mirrors these cases: `invocationUrl` (single), `invocationUrlCandidates` (several), or
136+
`gatewayUrl` + `invocationUrlHint` (none).
126137

127138
## Results
128139

docs/commands.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1267,7 +1267,9 @@ agentcore archive ab-test -i <ab-test-id>
12671267
View job history and details. Works for all four job types — `recommendation`, `batch-evaluation`, `ab-test`, and
12681268
`insights`. With no `[id]` it lists every job of that type; with an `[id]` it shows that job's detail (status, inputs,
12691269
and results). Without `--json` the command launches the interactive TUI; with `--json` it prints a machine-readable
1270-
record (the `ab-test` detail also includes `invocationUrl`).
1270+
record (the `ab-test` detail also includes an invocation URL — `invocationUrl`, `invocationUrlCandidates`, or
1271+
`gatewayUrl` + `invocationUrlHint` depending on how many gateway targets front the runtime; see
1272+
[A/B tests](ab-tests.md#invocation-url)).
12711273

12721274
```bash
12731275
# List all jobs of a type
@@ -1279,7 +1281,7 @@ agentcore view insights
12791281
# Detail for one job (JSON is non-interactive)
12801282
agentcore view recommendation <id> --json
12811283
agentcore view batch-evaluation <id> --json
1282-
agentcore view ab-test <id> --json # JSON includes invocationUrl + results
1284+
agentcore view ab-test <id> --json # JSON includes gateway URL fields + results
12831285
```
12841286

12851287
Each `view <type>` subcommand accepts the same argument and flags:

src/cli/commands/view/command.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import { ConfigIO, JobNotFoundError, serializeResult } from '../../../lib';
22
import { createJobEngine } from '../../operations/jobs';
33
import type { ABTestJobRecord, JobType } from '../../operations/jobs';
4-
import { getInvocationUrl, printABTestDetail, printABTestHistory } from '../../operations/jobs/ab-test/format';
4+
import {
5+
INVOCATION_PATH_HINT,
6+
getGatewayBaseUrl,
7+
getInvocationUrl,
8+
getInvocationUrlCandidates,
9+
printABTestDetail,
10+
printABTestHistory,
11+
} from '../../operations/jobs/ab-test/format';
512
import { printBatchEvaluationDetail, printBatchEvaluationHistory } from '../../operations/jobs/batch-evaluation/format';
613
import { printInsightsDetail, printInsightsHistory } from '../../operations/jobs/insights/format';
714
import { printRecommendationDetail, printRecommendationHistory } from '../../operations/jobs/recommendation/format';
@@ -44,6 +51,23 @@ const TYPE_META: Record<
4451
},
4552
};
4653

54+
/**
55+
* URL fields for `view ab-test --json`.
56+
*
57+
* When exactly one gateway target is known (target-based control, or a config-bundle runtime that
58+
* resolved to a single target), emit the complete `invocationUrl`. When several targets front the
59+
* runtime, emit `invocationUrlCandidates` so the consumer can choose. When none is known, emit
60+
* `gatewayUrl` + `invocationUrlHint` so the path can be built by hand. The runtime name is never used
61+
* as the path — that was the #1854 bug.
62+
*/
63+
function abTestUrlFields(record: ABTestJobRecord): Record<string, string | string[] | undefined> {
64+
const url = getInvocationUrl(record);
65+
if (url) return { invocationUrl: url };
66+
const candidates = getInvocationUrlCandidates(record);
67+
if (candidates.length) return { invocationUrlCandidates: candidates };
68+
return { gatewayUrl: getGatewayBaseUrl(record), invocationUrlHint: INVOCATION_PATH_HINT };
69+
}
70+
4771
function registerViewSubcommand(viewCmd: Command, type: JobType) {
4872
const meta = TYPE_META[type];
4973

@@ -65,8 +89,7 @@ function registerViewSubcommand(viewCmd: Command, type: JobType) {
6589
if (!record) {
6690
throw new JobNotFoundError(`${meta.label} "${id}" not found.`);
6791
}
68-
const extra =
69-
type === 'ab-test' ? { invocationUrl: getInvocationUrl(record as unknown as ABTestJobRecord) } : {};
92+
const extra = type === 'ab-test' ? abTestUrlFields(record as unknown as ABTestJobRecord) : {};
7093
console.log(JSON.stringify(serializeResult({ success: true, ...record, ...extra })));
7194
return { job_type: type };
7295
});

src/cli/operations/jobs/ab-test/__tests__/format.test.ts

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { ABTestJobRecord } from '../../shared/types';
2-
import { printABTestDetail } from '../format';
2+
import { getInvocationUrl, getInvocationUrlCandidates, printABTestDetail } from '../format';
33
import { afterEach, describe, expect, it, vi } from 'vitest';
44

55
function baseRecord(overrides: Partial<ABTestJobRecord> = {}): ABTestJobRecord {
@@ -45,3 +45,83 @@ describe('printABTestDetail — gateway filter', () => {
4545
expect(output).toContain('Gateway filter: none');
4646
});
4747
});
48+
49+
const GW_BASE = 'https://gw-abc.gateway.bedrock-agentcore.us-east-1.amazonaws.com';
50+
51+
const cfgBundle = (overrides: Partial<ABTestJobRecord> = {}) =>
52+
baseRecord({ mode: 'config-bundle', agent: 'CustomerSupportAB', variants: [], ...overrides });
53+
54+
describe('getInvocationUrl', () => {
55+
it('target-based: builds a full invocation URL from the control target name', () => {
56+
expect(getInvocationUrl(baseRecord())).toBe(`${GW_BASE}/ctrl/invocations`);
57+
});
58+
59+
it('target-based: returns undefined when the control target name is missing', () => {
60+
expect(getInvocationUrl(baseRecord({ variants: [] }))).toBeUndefined();
61+
});
62+
63+
it('config-bundle: builds a full URL from the target resolved at create time', () => {
64+
expect(getInvocationUrl(cfgBundle({ targetName: 'customer-support-ab' }))).toBe(
65+
`${GW_BASE}/customer-support-ab/invocations`
66+
);
67+
});
68+
69+
// Regression for #1854: `agent` holds the RUNTIME name, which is not a valid gateway path segment.
70+
// With no resolved target, no complete URL is emitted (candidates / base URL cover those cases).
71+
it('config-bundle: returns undefined when no single target resolved (never the runtime name)', () => {
72+
expect(getInvocationUrl(cfgBundle())).toBeUndefined();
73+
expect(getInvocationUrl(cfgBundle({ targetCandidates: ['a', 'b'] }))).toBeUndefined();
74+
});
75+
76+
it('returns undefined for a gateway ARN it cannot parse', () => {
77+
expect(getInvocationUrl(baseRecord({ gatewayArn: 'not-an-arn' }))).toBeUndefined();
78+
});
79+
});
80+
81+
describe('getInvocationUrlCandidates', () => {
82+
it('builds one URL per candidate target', () => {
83+
expect(getInvocationUrlCandidates(cfgBundle({ targetCandidates: ['prod', 'canary'] }))).toEqual([
84+
`${GW_BASE}/prod/invocations`,
85+
`${GW_BASE}/canary/invocations`,
86+
]);
87+
});
88+
89+
it('is empty when a single target resolved or none did', () => {
90+
expect(getInvocationUrlCandidates(cfgBundle({ targetName: 'only' }))).toEqual([]);
91+
expect(getInvocationUrlCandidates(cfgBundle())).toEqual([]);
92+
});
93+
});
94+
95+
describe('printABTestDetail — invocation URL', () => {
96+
afterEach(() => {
97+
vi.restoreAllMocks();
98+
});
99+
100+
function capture(record: ABTestJobRecord): string {
101+
const spy = vi.spyOn(console, 'log').mockImplementation(vi.fn());
102+
printABTestDetail(record);
103+
return spy.mock.calls.map(c => c.join(' ')).join('\n');
104+
}
105+
106+
it('prints a complete invocation URL for target-based tests', () => {
107+
expect(capture(baseRecord())).toContain(`Invocation URL: ${GW_BASE}/ctrl/invocations`);
108+
});
109+
110+
it('prints a complete invocation URL when a config-bundle target uniquely resolved', () => {
111+
const output = capture(cfgBundle({ targetName: 'customer-support-ab' }));
112+
expect(output).toContain(`Invocation URL: ${GW_BASE}/customer-support-ab/invocations`);
113+
});
114+
115+
it('lists candidate URLs when several targets front the runtime', () => {
116+
const output = capture(cfgBundle({ targetCandidates: ['prod', 'canary'] }));
117+
expect(output).toContain(`${GW_BASE}/prod/invocations`);
118+
expect(output).toContain(`${GW_BASE}/canary/invocations`);
119+
});
120+
121+
it('falls back to the gateway URL and hint when no target could be resolved', () => {
122+
const output = capture(cfgBundle());
123+
expect(output).toContain(`Gateway URL: ${GW_BASE}`);
124+
expect(output).toContain('append /<gateway-target>/invocations');
125+
expect(output).not.toContain('CustomerSupportAB');
126+
});
127+
});
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import type { AgentCoreProjectSpec } from '../../../../../schema';
2+
import { resolveRuntimeTargetNames } from '../resolve';
3+
import { describe, expect, it } from 'vitest';
4+
5+
type GatewaysOnly = Pick<AgentCoreProjectSpec, 'agentCoreGateways'>;
6+
7+
/** Project spec carrying only the gateway targets the resolver reads. */
8+
function specWithTargets(targets: unknown[]): GatewaysOnly {
9+
return { agentCoreGateways: [{ name: 'my-gw', targets }] } as unknown as GatewaysOnly;
10+
}
11+
12+
const httpTarget = (name: string, runtime: string) => ({
13+
name,
14+
targetType: 'httpRuntime',
15+
httpRuntime: { runtime },
16+
});
17+
18+
describe('resolveRuntimeTargetNames', () => {
19+
it('returns the single httpRuntime target routing to the runtime', () => {
20+
const spec = specWithTargets([httpTarget('customer-support-ab', 'CustomerSupportAB')]);
21+
expect(resolveRuntimeTargetNames('my-gw', 'CustomerSupportAB', spec)).toEqual(['customer-support-ab']);
22+
});
23+
24+
it('picks only the matching target when the gateway serves several runtimes', () => {
25+
const spec = specWithTargets([
26+
httpTarget('orders', 'OrdersAgent'),
27+
httpTarget('customer-support-ab', 'CustomerSupportAB'),
28+
]);
29+
expect(resolveRuntimeTargetNames('my-gw', 'CustomerSupportAB', spec)).toEqual(['customer-support-ab']);
30+
});
31+
32+
it('returns every matching target, in spec order, when several front one runtime', () => {
33+
const spec = specWithTargets([
34+
httpTarget('customer-support-ab', 'CustomerSupportAB'),
35+
httpTarget('customer-support-canary', 'CustomerSupportAB'),
36+
]);
37+
expect(resolveRuntimeTargetNames('my-gw', 'CustomerSupportAB', spec)).toEqual([
38+
'customer-support-ab',
39+
'customer-support-canary',
40+
]);
41+
});
42+
43+
it('returns [] when no target routes to the runtime', () => {
44+
const spec = specWithTargets([httpTarget('orders', 'OrdersAgent')]);
45+
expect(resolveRuntimeTargetNames('my-gw', 'CustomerSupportAB', spec)).toEqual([]);
46+
});
47+
48+
it('returns [] for a gateway with no targets', () => {
49+
expect(resolveRuntimeTargetNames('my-gw', 'CustomerSupportAB', specWithTargets([]))).toEqual([]);
50+
});
51+
52+
// Only httpRuntime targets front a runtime; a same-named lambda/mcpServer target is not a route to it.
53+
it('ignores targets that are not httpRuntime', () => {
54+
const spec = specWithTargets([
55+
{ name: 'customer-support-ab', targetType: 'lambda', httpRuntime: { runtime: 'CustomerSupportAB' } },
56+
]);
57+
expect(resolveRuntimeTargetNames('my-gw', 'CustomerSupportAB', spec)).toEqual([]);
58+
});
59+
60+
it('returns [] for an unknown gateway name', () => {
61+
const spec = specWithTargets([httpTarget('customer-support-ab', 'CustomerSupportAB')]);
62+
expect(resolveRuntimeTargetNames('other-gw', 'CustomerSupportAB', spec)).toEqual([]);
63+
});
64+
65+
it('returns [] when the gateway or runtime is unset', () => {
66+
const spec = specWithTargets([httpTarget('customer-support-ab', 'CustomerSupportAB')]);
67+
expect(resolveRuntimeTargetNames(undefined, 'CustomerSupportAB', spec)).toEqual([]);
68+
expect(resolveRuntimeTargetNames('my-gw', undefined, spec)).toEqual([]);
69+
});
70+
71+
it('returns [] when the project declares no gateways', () => {
72+
expect(resolveRuntimeTargetNames('my-gw', 'CustomerSupportAB', {} as GatewaysOnly)).toEqual([]);
73+
});
74+
});

src/cli/operations/jobs/ab-test/format.ts

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,56 @@ import { dnsSuffix } from '../../../aws/partition';
33
import { formatJobDate } from '../shared/format';
44
import type { ABTestJobRecord } from '../shared/types';
55

6-
/**
7-
* Derive the gateway invocation URL from the stored gateway ARN.
8-
* Target-based: `https://{gateway}/{control-target-name}/invocations`.
9-
* Config-bundle: `https://{gateway}/{agent-name}/invocations`.
10-
*/
11-
export function getInvocationUrl(record: ABTestJobRecord): string | undefined {
6+
/** Gateway base URL (no path) from the stored gateway ARN, or undefined if the ARN can't be parsed. */
7+
function gatewayBaseUrl(record: ABTestJobRecord): string | undefined {
128
const parts = record.gatewayArn.split(':');
139
const region = parts[3];
1410
const gatewayId = parts[5]?.split('/')[1];
1511
if (!region || !gatewayId) return undefined;
16-
const baseUrl = `https://${gatewayId}.gateway.bedrock-agentcore.${region}.${dnsSuffix(region)}`;
17-
if (record.mode === 'target-based') {
18-
const targetName = record.variants[0]?.targetName;
19-
return targetName ? `${baseUrl}/${targetName}/invocations` : undefined;
20-
}
21-
return record.agent ? `${baseUrl}/${record.agent}/invocations` : undefined;
12+
return `https://${gatewayId}.gateway.bedrock-agentcore.${region}.${dnsSuffix(region)}`;
13+
}
14+
15+
/** The gateway target name that uniquely identifies this test's invocation path, if there is exactly one. */
16+
function uniqueTargetName(record: ABTestJobRecord): string | undefined {
17+
// Target-based: the control variant's target. Config-bundle: the target resolved at create time,
18+
// set only when exactly one gateway target routed to the runtime.
19+
return record.mode === 'target-based' ? record.variants[0]?.targetName : record.targetName;
20+
}
21+
22+
/**
23+
* Derive the complete invocation URL: `https://{gateway}/{target}/invocations`.
24+
*
25+
* The path segment must be a gateway TARGET name. Config-bundle records store the target resolved from
26+
* the runtime at create time (`targetName`); target-based records carry it on the control variant.
27+
* Returns undefined when no single target is known — either the gateway has none fronting the runtime,
28+
* or several do (see getInvocationUrlCandidates). Substituting the runtime name here produced URLs that
29+
* 404'd with "No Target found for Target name: <runtime>" (issue #1854), so it is deliberately not done.
30+
*/
31+
export function getInvocationUrl(record: ABTestJobRecord): string | undefined {
32+
const baseUrl = gatewayBaseUrl(record);
33+
const targetName = uniqueTargetName(record);
34+
return baseUrl && targetName ? `${baseUrl}/${targetName}/invocations` : undefined;
2235
}
2336

37+
/**
38+
* Candidate invocation URLs when several gateway targets route to the runtime (config-bundle only).
39+
* Each is a valid path; only the user can say which should receive traffic. Empty when a single URL
40+
* was resolvable (use getInvocationUrl) or when no target matched.
41+
*/
42+
export function getInvocationUrlCandidates(record: ABTestJobRecord): string[] {
43+
const baseUrl = gatewayBaseUrl(record);
44+
if (!baseUrl || !record.targetCandidates?.length) return [];
45+
return record.targetCandidates.map(t => `${baseUrl}/${t}/invocations`);
46+
}
47+
48+
/** Gateway base URL to show when no invocation path could be determined, so the user can build one. */
49+
export function getGatewayBaseUrl(record: ABTestJobRecord): string | undefined {
50+
return gatewayBaseUrl(record);
51+
}
52+
53+
/** Names what the caller must append to a gateway base URL to reach a variant. */
54+
export const INVOCATION_PATH_HINT = 'append /<gateway-target>/invocations (see `agentcore status --json`)';
55+
2456
export function printABTestHistory(records: ABTestJobRecord[]): void {
2557
if (records.length === 0) {
2658
console.log('No A/B test jobs found. Run `agentcore run ab-test` to create one.');
@@ -47,7 +79,19 @@ export function printABTestDetail(record: ABTestJobRecord): void {
4779
console.log(`Gateway: ${record.gatewayArn}`);
4880
console.log(`Gateway filter: ${record.gatewayFilter?.targetPaths?.[0] ?? 'none'}`);
4981
const invocationUrl = getInvocationUrl(record);
50-
if (invocationUrl) console.log(`Invocation URL: ${invocationUrl}`);
82+
const candidates = getInvocationUrlCandidates(record);
83+
if (invocationUrl) {
84+
console.log(`Invocation URL: ${invocationUrl}`);
85+
} else if (candidates.length) {
86+
console.log('Invocation URLs (one per matching gateway target — pick the one to send traffic to):');
87+
for (const url of candidates) console.log(` ${url}`);
88+
} else {
89+
const baseUrl = getGatewayBaseUrl(record);
90+
if (baseUrl) {
91+
console.log(`Gateway URL: ${baseUrl}`);
92+
console.log(` → ${INVOCATION_PATH_HINT}`);
93+
}
94+
}
5195
console.log(`Started: ${formatJobDate(record.createdAt)}`);
5296
if (record.completedAt) console.log(`Stopped: ${formatJobDate(record.completedAt)}`);
5397
if (record.maxDurationExpiresAt) console.log(`Max duration expires: ${formatJobDate(record.maxDurationExpiresAt)}`);

src/cli/operations/jobs/ab-test/handler.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { regionFromArn, resolveJobRegion } from '../shared/region';
2525
import type { ABTestHandler, ABTestJobRecord, DebugCheckResult, StartABTestJobOptions } from '../shared/types';
2626
import { buildABTestRequest } from './build-options';
2727
import { promoteABTestConfig } from './promote';
28-
import { deleteABTestRole, getOrCreateABTestRole, resolveGatewayArn } from './resolve';
28+
import { deleteABTestRole, getOrCreateABTestRole, resolveGatewayArn, resolveRuntimeTargetNames } from './resolve';
2929
import { CloudWatchLogsClient, FilterLogEventsCommand } from '@aws-sdk/client-cloudwatch-logs';
3030

3131
/** AB-test create retries while the freshly-created IAM role propagates (gateway/eval AccessDenied). */
@@ -190,6 +190,14 @@ export const abTestHandler: ABTestHandler = {
190190
opts.onProgress?.('started', `A/B test created: ${createResult.abTestId} (${createResult.executionStatus})`);
191191
logger?.finalize(true);
192192

193+
// Config-bundle tests carry no target in their variants; resolve the gateway target(s) routing to
194+
// the runtime so `view` can print a complete invocation URL (a single match) or list candidates
195+
// (several). Target-based tests already carry the target in variantSummaries.
196+
const targetNames =
197+
opts.mode === 'target-based'
198+
? []
199+
: resolveRuntimeTargetNames(opts.gateway, opts.runtime ?? opts.agent, projectSpec);
200+
193201
const record: ABTestJobRecord = {
194202
type: 'ab-test',
195203
id: createResult.abTestId,
@@ -203,6 +211,8 @@ export const abTestHandler: ABTestHandler = {
203211
mode: opts.mode,
204212
gatewayArn,
205213
gatewayName: opts.gateway,
214+
targetName: targetNames.length === 1 ? targetNames[0] : undefined,
215+
targetCandidates: targetNames.length > 1 ? targetNames : undefined,
206216
roleArn,
207217
roleCreatedByCli,
208218
variants: built.variantSummaries,

0 commit comments

Comments
 (0)