-
Notifications
You must be signed in to change notification settings - Fork 797
Add --isolated to run command #14123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 14123Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 14123" |
🎬 CLI E2E Test RecordingsThe following terminal recordings are available for commit
📹 Recordings uploaded automatically from CI run #21395825072 |
9606930 to
6a5c50a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces an --isolated mode to aspire run that randomizes ports and uses per-run isolated user secrets, plus supporting infrastructure to share user-secrets path logic across CLI and hosting. It also wires localized strings and tests around the new behavior.
Changes:
- Introduced shared
UserSecretsPathHelperandIsolatedUserSecretsHelperunderAspire.Shared.UserSecrets, and updated hosting code/tests to use the shared helpers instead of a pipelines-specific implementation. - Extended the CLI
runcommand with a new--isolatedoption that setsDcpPublisher__RandomizePortsand creates/cleans up an isolatedDOTNET_USER_SECRETS_IDfor the current run, including new interaction messages and localized strings. - Added unit tests for the shared user-secrets helpers and for the new
--isolatedbehavior inRunCommand, verifying both randomized ports and user-secrets isolation.
Reviewed changes
Copilot reviewed 28 out of 29 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/Aspire.Hosting.Tests/UserSecretsParameterDefaultTests.cs | Switched tests to use the shared UserSecretsPathHelper namespace, validating user-secrets defaults against the new shared helper. |
| tests/Aspire.Hosting.Tests/SecretsStoreTests.cs | Updated to use Aspire.Shared.UserSecrets.UserSecretsPathHelper for reading/clearing secrets in store tests, aligning with the shared helper. |
| tests/Aspire.Cli.Tests/Utils/IsolatedUserSecretsHelperTests.cs | Added focused tests around IsolatedUserSecretsHelper creation/cleanup paths, including null/empty IDs and non-existent secrets. |
| tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs | Added a new test that exercises run --isolated, asserting randomized ports and a distinct GUID-based DOTNET_USER_SECRETS_ID wired via the new helper. |
| src/Shared/UserSecrets/UserSecretsPathHelper.cs | Retargeted the user-secrets path helper into Aspire.Shared.UserSecrets for reuse by both hosting and CLI. |
| src/Shared/UserSecrets/IsolatedUserSecretsHelper.cs | Introduced a shared helper for cloning and cleaning up isolated user-secret sets by ID. |
| src/Aspire.Hosting/UserSecrets/UserSecretsManagerFactory.cs | Updated to use the shared UserSecretsPathHelper when resolving secrets paths while retaining existing manager behavior. |
| src/Aspire.Hosting/Pipelines/Internal/UserSecretsPathHelper.cs | Removed the hosting-specific UserSecretsPathHelper now superseded by the shared implementation. |
| src/Aspire.Hosting/Aspire.Hosting.csproj | Linked the shared UserSecretsPathHelper into the hosting assembly and ensured internals visibility remains intact. |
| src/Aspire.Cli/Utils/UserSecretsHelper.cs | Added a CLI-local helper that duplicates the shared isolated user-secrets functionality (currently unused and overlapping with IsolatedUserSecretsHelper). |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.zh-Hant.xlf | Added localized entries for the --isolated argument description and “Copying user secrets for isolated mode...” message (pending actual translations). |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.zh-Hans.xlf | Same as above for Simplified Chinese resources. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.tr.xlf | Same as above for Turkish resources. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.ru.xlf | Same as above for Russian resources. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.pt-BR.xlf | Same as above for Brazilian Portuguese resources. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.pl.xlf | Same as above for Polish resources. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.ko.xlf | Same as above for Korean resources. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.ja.xlf | Same as above for Japanese resources. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.it.xlf | Same as above for Italian resources. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.fr.xlf | Same as above for French resources. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.es.xlf | Same as above for Spanish resources. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.de.xlf | Same as above for German resources. |
| src/Aspire.Cli/Resources/xlf/RunCommandStrings.cs.xlf | Same as above for Czech resources. |
| src/Aspire.Cli/Resources/RunCommandStrings.resx | Added base resources for the isolated-mode argument description and “Copying user secrets for isolated mode...” line. |
| src/Aspire.Cli/Resources/RunCommandStrings.Designer.cs | Exposed strongly-typed properties for the new resource strings used by the CLI. |
| src/Aspire.Cli/Projects/DotNetAppHostProject.cs | Implemented isolated-mode behavior: sets DcpPublisher__RandomizePorts, queries UserSecretsId, clones secrets via IsolatedUserSecretsHelper, wires DOTNET_USER_SECRETS_ID, and cleans up at the end of the run. |
| src/Aspire.Cli/Projects/AppHostProjectContext.cs | Extended the run context with an Isolated flag indicating whether to enable isolated-mode behavior. |
| src/Aspire.Cli/Commands/RunCommand.cs | Added the --isolated option, plumbed it into AppHostProjectContext, and ensured it’s forwarded when running detached. |
| src/Aspire.Cli/Aspire.Cli.csproj | Linked the shared user-secrets helpers into the CLI assembly and declared internals visibility for tests. |
Files not reviewed (1)
- src/Aspire.Cli/Resources/RunCommandStrings.Designer.cs: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
🧪 End-to-End Test Report:
|
| Feature | Status |
|---|---|
--isolated flag in help |
✅ Works |
| User secrets isolation ("Copying user secrets for isolated mode...") | ✅ Works |
Port randomization (DcpPublisher__RandomizePorts=true) |
✅ Works |
Single instance with --isolated |
✅ Works |
What Doesn't Work ❌
CRITICAL: Running Multiple Instances Simultaneously DOES NOT WORK
This is the primary use case for the --isolated flag, and it fails.
Expected Behavior:
Running aspire run --isolated twice in the same directory should result in both instances running simultaneously on different ports.
Actual Behavior:
When running aspire run --isolated a second time, the CLI:
- Detects the existing running instance
- Stops the previous instance
- Then starts the new instance
Evidence from logs:
[21:35:43] [dbug] DotNetAppHostProject: Connecting to auxiliary backchannel...
ℹ Stopping previous instance (AppHost PID: 113376, CLI PID: 30940)
[21:35:43] [dbug] DotNetAppHostProject: Requesting AppHost to stop
✔ Running instance stopped successfully.
Root Cause:
The "running instance detection" feature is still enabled even when --isolated is specified. The --isolated flag needs to bypass this check to allow multiple simultaneous instances.
Recommendation
When --isolated is true, skip the "is another instance running?" check so that multiple instances can coexist.
|
Update after reading the Scaling AI Agents with Aspire Isolation blog post: I may have misunderstood the use case. The blog describes running different worktrees (separate git checkouts) with isolated ports, not running the same AppHost multiple times from the same directory. However, re-reading the PR description which fixes:
And the flag description: "Run in isolated mode with randomized ports and isolated user secrets, allowing multiple instances to run simultaneously" It does seem like the intent is to allow multiple instances. Could you clarify the expected behavior?
If it's scenario #2, the current behavior makes sense (stop existing instance in same directory, but isolated ports prevent conflicts with other directories). The test I ran was scenario #1. |
✅ Updated Test Results - Worktree Scenario WORKS!After re-testing with the correct scenario (different directories simulating worktrees), the feature works as intended: Both instances from different directories are running simultaneously! ✅ Clarification on Behavior
Minor Issue: Dashboard Port Not RandomizedBoth instances show Could this be addressed by also randomizing the dashboard/OTLP ports in isolated mode? SummaryThe feature works correctly for the worktree use case! My earlier report was based on testing the wrong scenario (same directory). |
|
3e99c86 to
2b0f78d
Compare
11f1c51 to
30993b4
Compare
Code Review Findings🔴 High: Missing exception handling for File.Copy operationFile: src/Shared/UserSecrets/IsolatedUserSecretsHelper.cs:42 The Suggested fix: Wrap file operations in try-catch, clean up partial state on failure, and return null to allow graceful fallback. 🟡 Medium: Race condition in directory cleanupFile: src/Shared/UserSecrets/IsolatedUserSecretsHelper.cs:70-75 Non-atomic check-then-delete pattern: var remainingFiles = Directory.GetFiles(secretsDir);
if (remainingFiles.Length == 0)
{
Directory.Delete(secretsDir);
}Between checking and deleting, another process could modify the directory contents. 🟡 Medium: Null check for Path.GetDirectoryName comes too lateFile: src/Shared/UserSecrets/IsolatedUserSecretsHelper.cs:61-71
Suggested fix: Check |
Suggestion: Support user secrets isolation for single-file apphostsCurrently, the PR skips user secrets isolation for single-file apphosts because ApproachFor single-file apphosts, read the // For single-file apphosts, read UserSecretsId from the assembly attribute
private static string? GetUserSecretsIdFromAssembly(FileInfo appHostFile)
{
try
{
// Use MetadataLoadContext to avoid loading the assembly into the current domain
var resolver = new PathAssemblyResolver(new[] { appHostFile.FullName, typeof(object).Assembly.Location });
using var mlc = new MetadataLoadContext(resolver);
var assembly = mlc.LoadFromAssemblyPath(appHostFile.FullName);
var attr = assembly.GetCustomAttributesData()
.FirstOrDefault(a => a.AttributeType.FullName ==
"Microsoft.Extensions.Configuration.UserSecrets.UserSecretsIdAttribute");
return attr?.ConstructorArguments.FirstOrDefault().Value as string;
}
catch
{
return null;
}
}VerificationTested locally - the This would allow |
Updated: Use
|
Complete solution for file-based apphost user secrets isolationTesting with The UserSecretsId for file-based apps is deterministically generated from the file path: Suggested approach for file-based apphosts:private static string GetFileBasedAppHostUserSecretsId(FileInfo appHostFile)
{
// Match the logic used by dotnet user-secrets for file-based apps
var fullPath = appHostFile.FullName;
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(fullPath));
var hashString = Convert.ToHexString(hash).ToLowerInvariant();
return $"apphost-{hashString}";
}Then in if (isSingleFileAppHost)
{
var userSecretsId = GetFileBasedAppHostUserSecretsId(appHostFile);
// ... proceed with CreateIsolatedUserSecrets(userSecretsId)
}This would enable full |
|
OR we shell out to user secrets id in all cases? |
The existing |
9541dd4 to
a362413
Compare
PR Testing Report -
|
| Instance | Directory | Dashboard Port |
|---|---|---|
| Worktree 1 | worktree1/SharedApp.AppHost |
17204 |
| Worktree 2 | worktree2/SharedApp.AppHost |
17170 |
PATH PID CLI_PID DASHBOARD
SharedApp.AppHost.csproj 55388 83700 https://localhost:17170/...
SharedApp.AppHost.csproj 35024 62944 https://localhost:17204/...
3. File-Based AppHosts (Python FastAPI) ✅
Multiple file-based apphosts ran simultaneously:
PATH PID CLI_PID DASHBOARD
python-fastapi2/apphost.cs 82752 75492 -
python-fastapi/apphost.cs 68152 53340 -
4. Isolated User Secrets - Standard Projects ✅
Set different secrets in each instance:
- Instance 1:
"TestSecret": "SECRET_FROM_INSTANCE_1" - Instance 2:
"TestSecret": "SECRET_FROM_INSTANCE_2"
Verified isolated copies were created at runtime:
- Instance 1 isolated (
65426f11-...): ContainsSECRET_FROM_INSTANCE_1 - Instance 2 isolated (
105b09ac-...): ContainsSECRET_FROM_INSTANCE_2
Each instance gets its own copy of user secrets, preventing cross-contamination.
5. Isolated User Secrets - File-Based AppHosts ✅
File-based apphosts create isolated secrets with apphost-<hash> naming:
apphost-77b4748515338f4cc965705503d9c099c421c4bd65b7d5ad5011434a25c3ccf7
OtlpApiKey: e20919448e65e129719e66eab85bd44d
McpApiKey: a328155a626432fee1bf304b8d746753
Summary
| Feature | Status |
|---|---|
--isolated flag added |
✅ |
| Randomized ports | ✅ |
| Multiple instances simultaneously | ✅ |
| Git worktree support (same project, different dirs) | ✅ |
| File-based apphost support | ✅ |
| Isolated user secrets (standard projects) | ✅ |
| Isolated user secrets (file-based) | ✅ |
Overall Result
✅ PR VERIFIED - All scenarios passed successfully.
Additional Test: TypeScript AppHost ✅Tested TypeScript apphosts created with
Both TypeScript apphosts ran simultaneously with
|
Additional Test: TypeScript AppHost Folder Copy ✅Tested copying an identical TypeScript apphost folder and running both with Setup
ResultsIsolated User Secrets VerifiedEach instance received its own unique secrets:
✅ User secrets are correctly isolated per TypeScript apphost instance, even when running identical folder copies. |
|
@JamesNK I think we missed the resource server URL. Not I'm wondering if we got lucky? Are we randomizing the DashboardServiceHost ? |
|
Yes, it's randomized. |


Description
Adds
aspire run --isolated. Used by agents to run aspire in the background from multiple app hosts usingaspire run --isolated --detach--isolatedis specified (people might expect it to allow concurrent runs from a single directory, when in reality it's designed for worktrees)Fixes #13607
Fixes #12509
Checklist
<remarks />and<code />elements on your triple slash comments?doc-ideatemplatebreaking-changetemplatediagnostictemplate