From f317d2df67caf4e5a8044246788709abba318c46 Mon Sep 17 00:00:00 2001 From: KazuOnuki Date: Thu, 4 Jun 2026 17:05:59 +0900 Subject: [PATCH] fix(15-private-network-standard-agent-setup): remove addAccountCapabilityHost call Network-injected accounts (properties.networkInjections[].scenario='agent') cause the Cognitive Services resource provider (Microsoft.CognitiveServices) to auto-create an account-level capabilityHost named {accountName}@aml_aiagentservice ~5s after the account PUT. This template is network-secured-only and always passes that property, so any subsequent PUT of a second account-level capabilityHost (caphostacct in #261) fails with 409 'for the same ClientId'. Remove the module call, related vars, and dependsOn entry from main.bicep, and update README.md to describe the auto-create flow. The .bicep module file is retained for non-network-secured sibling templates. Verified twice 2026-06-04 with fresh deploys in a clean subscription (japaneast): both variants Succeeded end-to-end, caphostacct never PUT, project capabilityHost bound to all three connections. Closes #312 --- .../README.md | 6 +-- .../main.bicep | 52 +++++++++++-------- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/infrastructure/infrastructure-setup-bicep/15-private-network-standard-agent-setup/README.md b/infrastructure/infrastructure-setup-bicep/15-private-network-standard-agent-setup/README.md index 9613c6b0e..fb984147e 100644 --- a/infrastructure/infrastructure-setup-bicep/15-private-network-standard-agent-setup/README.md +++ b/infrastructure/infrastructure-setup-bicep/15-private-network-standard-agent-setup/README.md @@ -102,7 +102,7 @@ Use the table below to choose the right infrastructure template for your scenari > **Notes:** - If you do not provide an existing virtual network, the template will create a new virtual network with the default address spaces and subnets described above. If you use an existing virtual network, make sure it already contains two subnets (Agent and Private Endpoint) before deploying the template. - - The account-level capability host is now provisioned declaratively by `modules-network-secured/add-account-capability-host.bicep` as part of `main.bicep`. The standalone `createCapHost.sh` script is no longer required for first-time deployments; it remains in the folder only to support the cleanup-then-recreate flow described in the [Account Deletion Prerequisites and Cleanup Guidance](#account-deletion-prerequisites-and-cleanup-guidance). + - For network-secured deployments, the account-level capability host (`{accountName}@aml_aiagentservice`) is auto-created by the Cognitive Services resource provider (`Microsoft.CognitiveServices`) when the account is created with `properties.networkInjections[].scenario = 'agent'` (set in `modules-network-secured/ai-account-identity.bicep`). Neither `createCapHost.sh` nor the `add-account-capability-host.bicep` module is invoked for first-time deployments; both remain in the folder only to support the cleanup-then-recreate flow described in the [Account Deletion Prerequisites and Cleanup Guidance](#account-deletion-prerequisites-and-cleanup-guidance). - You must ensure the subnet is exclusively delegated to __Microsoft.App/environments__ and cannot be used by any other Azure resources. @@ -128,7 +128,7 @@ Before deleting an **Account** resource, it is essential to first delete the ass **2. Retain Account, Remove Capability Host**: If you intend to retain the account but remove the capability host, execute the script `deleteCaphost.sh` located in this folder. After deletion, allow approximately max of 20 minutes for all resources to be fully unlinked from the account. To recreate the capability host for the account, use the script `createCaphost.sh` located in the same folder. -> **Note**: The account-level capability host is created declaratively by `main.bicep` (via `modules-network-secured/add-account-capability-host.bicep`) on first deployment. The `createCapHost.sh` script is intended for this cleanup-then-recreate scenario only; it is not required for an initial deployment. +> **Note**: For network-secured deployments, the account-level capability host is auto-created by the Cognitive Services resource provider on first deployment (triggered by the `networkInjections.scenario = 'agent'` property set in `modules-network-secured/ai-account-identity.bicep`). The `createCapHost.sh` script and the `add-account-capability-host.bicep` module are intended for this cleanup-then-recreate scenario only; neither is required for an initial deployment. > **Important**: Before deleting the account capability host, ensure that the **project capability host** is deleted. @@ -465,7 +465,7 @@ Private endpoints ensure secure, internal-only connectivity. Private endpoints a ```text modules-network-secured/ -├── add-account-capability-host.bicep # Declarative account-level capability host (replaces createCapHost.sh for first-time deployments) +├── add-account-capability-host.bicep # (Not invoked by main.bicep in network-secured deployments — the account-level capability host is auto-created by the Cognitive Services resource provider via networkInjections.scenario='agent'. Retained for the cleanup-then-recreate flow.) ├── add-project-capability-host.bicep # Configuring the project's capability host ├── ai-account-identity.bicep # Microsoft Foundry deployment and configuration (supports BYO existing account) ├── ai-project-identity.bicep # Foundry project deployment and connection configuration diff --git a/infrastructure/infrastructure-setup-bicep/15-private-network-standard-agent-setup/main.bicep b/infrastructure/infrastructure-setup-bicep/15-private-network-standard-agent-setup/main.bicep index 111dd2578..b765f16c8 100644 --- a/infrastructure/infrastructure-setup-bicep/15-private-network-standard-agent-setup/main.bicep +++ b/infrastructure/infrastructure-setup-bicep/15-private-network-standard-agent-setup/main.bicep @@ -106,13 +106,6 @@ param existingAiFoundryAccountResourceId string = '' @description('Optional. When true, skip the model deployment. Recommended when reusing an existing account that already has the required model deployments.') param skipModelDeployment bool = false -// Re-derive BYO account context at main.bicep level so we can scope the -// account-level capabilityHost module to the right RG/subscription. -var useExistingAccount = !empty(existingAiFoundryAccountResourceId) -var existingAccountIdParts = split(existingAiFoundryAccountResourceId, '/') -var existingAccountSubscriptionId = useExistingAccount ? existingAccountIdParts[2] : subscription().subscriptionId -var existingAccountResourceGroupName = useExistingAccount ? existingAccountIdParts[4] : resourceGroup().name - @description('The AI Search Service full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') param aiSearchResourceId string = '' @description('The AI Storage Account full ARM Resource ID. This is an optional field, and if not provided, the resource will be created.') @@ -431,20 +424,36 @@ module aiSearchRoleAssignments 'modules-network-secured/ai-search-role-assignmen ] } -// Account-level capabilityHost (bootstraps before project caphost). -// The current sample relies on createCapHost.sh being run manually; making it -// declarative keeps the flow idempotent and works for both new and BYO accounts. -module addAccountCapabilityHost 'modules-network-secured/add-account-capability-host.bicep' = { - name: 'account-caphost-${uniqueSuffix}-deployment' - scope: resourceGroup(existingAccountSubscriptionId, existingAccountResourceGroupName) - params: { - accountName: aiAccount.outputs.accountName - agentSubnetResourceId: vnet.outputs.agentSubnetId - } - dependsOn: [ - privateEndpointAndDNS - ] -} +// Account-level capabilityHost. +// +// PR #261 added this module to bootstrap the account-level capabilityHost so +// the flow becomes fully declarative (no createCapHost.sh) and works for BYO +// Foundry accounts that never had the script run. +// +// HOWEVER, this network-secured Standard Setup template creates the AI Foundry +// account with `properties.networkInjections=[{scenario:'agent', subnetArmId:.., useMicrosoftManagedNetwork:false}]` +// (see modules-network-secured/ai-account-identity.bicep). The Cognitive Services +// resource provider (`Microsoft.CognitiveServices`) reacts to that property by +// auto-creating an account-level capabilityHost named `{accountName}@aml_aiagentservice` +// ~5 seconds after the account PUT. Any subsequent PUT of a second account-level +// capabilityHost with a different name (here `caphostacct`) for the same account +// then fails with HTTP 409: +// +// "There is an existing Capability Host with name: {account}@aml_aiagentservice, +// provisioning state: Succeeded for workspace: ... +// cannot create a new Capability Host with name: caphostacct for the same ClientId." +// +// Account-level capability hosts are 1-per-account, keyed on ClientId, and +// cannot be updated. The auto-created `@aml_aiagentservice` is what the +// project-level capabilityHost (addProjectCapabilityHost) binds to, so we must +// NOT create our own here. +// +// Because this template is network-secured-only (every deploy passes +// networkInjections), the auto-create path is always taken. We therefore do +// NOT call modules-network-secured/add-account-capability-host.bicep here. +// The module file is intentionally kept in the directory because it may still +// be useful for other (non-network-secured) BYO bootstrap scenarios. +// See foundry-samples issues #312 / #254 / #255 / #265. // This module creates the capability host for the project and account module addProjectCapabilityHost 'modules-network-secured/add-project-capability-host.bicep' = { @@ -458,7 +467,6 @@ module addProjectCapabilityHost 'modules-network-secured/add-project-capability- projectCapHost: projectCapHost } dependsOn: [ - addAccountCapabilityHost // account caphost must exist first aiSearch // Ensure AI Search exists storage // Ensure Storage exists cosmosDB