{"record":{"id":"8174b52bdb928350","repo":"microsoft/aspire","slug":"failed-to-delete-port-portnumber-on-dev-tunnel-tunnelid-exit","errorCode":null,"errorMessage":"Failed to delete port '{portNumber}' on dev tunnel '{tunnelId}'. Exit code {exitCode}: {error}","messagePattern":"Failed to delete port '(.+?)' on dev tunnel '(.+?)'\\. Exit code (.+?): (.+?)","errorType":"exception","errorClass":"DistributedApplicationException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.DevTunnels/DevTunnelCliClient.cs","lineNumber":250,"sourceCode":"\n            logger?.LogError(\"Failed to create port '{PortNumber}' for dev tunnel '{TunnelId}' (attempt {Attempt} of {MaxAttempts}). Exit code {ExitCode}: {Error}\", portNumber, tunnelId, attempts, _maxCliAttempts, exitCode, error);\n            if (attempts < _maxCliAttempts)\n            {\n                logger?.LogTrace(\"Waiting {WaitSeconds} seconds before retrying to create port '{PortNumber}' on dev tunnel '{TunnelId}'\", _cliRetryOnErrorDelay.TotalSeconds, portNumber, tunnelId);\n                await Task.Delay(_cliRetryOnErrorDelay, cancellationToken).ConfigureAwait(false);\n            }\n        }\n\n        throw new DistributedApplicationException($\"Failed to create port '{portNumber}' for tunnel '{tunnelId}' after {attempts} attempts. Exit code {exitCode}: {error}\");\n    }\n\n    public async Task<DevTunnelPortDeleteResult> DeletePortAsync(string tunnelId, int portNumber, ILogger? logger = default, CancellationToken cancellationToken = default)\n    {\n        logger?.LogTrace(\"Deleting port '{PortNumber}' on dev tunnel '{TunnelId}'.\", portNumber, tunnelId);\n        var (result, exitCode, error) = await CallCliAsJsonAsync<DevTunnelPortDeleteResult>(\n            (stdout, stderr, log, ct) => _cli.DeletePortAsync(tunnelId, portNumber, stdout, stderr, log, ct),\n            logger, cancellationToken).ConfigureAwait(false);\n        return result ?? throw new DistributedApplicationException($\"Failed to delete port '{portNumber}' on dev tunnel '{tunnelId}'. Exit code {exitCode}: {error}\");\n    }\n\n    public async Task<DevTunnelAccessStatus> GetAccessAsync(string tunnelId, int? portNumber = null, ILogger? logger = default, CancellationToken cancellationToken = default)\n    {\n        logger?.LogTrace(\"Getting access details for {PortInfo}dev tunnel '{TunnelId}'.\", portNumber.HasValue ? $\"port '{portNumber}' on \" : string.Empty, tunnelId);\n        var (access, exitCode, error) = await CallCliAsJsonAsync<DevTunnelAccessStatus>(\n            (stdout, stderr, log, ct) => _cli.ListAccessAsync(tunnelId, portNumber, stdout, stderr, log, ct),\n            logger, cancellationToken).ConfigureAwait(false);\n        return access ?? throw new DistributedApplicationException($\"Failed to get access details for '{tunnelId}'{(portNumber.HasValue ? $\" port {portNumber}\" : \"\")}. Exit code {exitCode}: {error}\");\n    }\n\n    public async Task<UserLoginStatus> GetUserLoginStatusAsync(ILogger? logger = default, CancellationToken cancellationToken = default)\n    {\n        logger?.LogTrace(\"Getting dev tunnel user login status.\");\n        var (login, exitCode, error) = await CallCliAsJsonAsync<UserLoginStatus>(\n            _cli.UserStatusAsync,\n            logger, cancellationToken).ConfigureAwait(false);\n        return login ?? throw new DistributedApplicationException($\"Failed to get user login status. Exit code {exitCode}: {error}\");","sourceCodeStart":232,"sourceCodeEnd":268,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.DevTunnels/DevTunnelCliClient.cs#L232-L268","documentation":"Aspire's DevTunnels integration wraps the `devtunnel` CLI. DeletePortAsync runs `devtunnel delete port` and parses JSON output; if the CLI exits non-zero (so no result object could be deserialized), the client throws this DistributedApplicationException carrying the exit code and CLI error text.","triggerScenarios":"Calling DeletePortAsync(tunnelId, portNumber) when the tunnel does not exist, the port number was never created on the tunnel, the CLI is not authenticated, or the devtunnel CLI binary fails/misbehaves (non-zero exit with no parseable JSON).","commonSituations":"Deleting a port after the tunnel was already deleted or expired (max 30 days); a typo'd tunnelId or port; expired devtunnel login; network/service errors returning CLI errors instead of JSON.","solutions":["Verify the tunnel exists and the port number matches one you created with CreatePortAsync/SetAccessAsync before deleting","Run `devtunnel status` or re-login with `devtunnel user login` if exit code indicates auth failure","Log/inspect the trailing error text in the message; it is the CLI's stderr explaining the real cause","Wrap DeletePortAsync in try-catch for DistributedApplicationException if deletion is best-effort cleanup"],"exampleFix":"// before\nclient.DeletePortAsync(\"my-tunnel\", 8080); // throws if tunnel/port gone\n// after\nvar access = await client.GetAccessAsync(\"my-tunnel\");\nif (access.Ports.Any(p => p.PortNumber == 8080))\n{\n    await client.DeletePortAsync(\"my-tunnel\", 8080);\n}","handlingStrategy":"try-catch","validationCode":"var tunnels = await client.ListTunnelsAsync(logger, ct);\nbool portExists = tunnels.Any(t => t.TunnelId == tunnelId); // plus port check via GetAccessAsync\nif (!portExists) return;","typeGuard":"static bool IsValidTunnelId(string? id) => !string.IsNullOrEmpty(id) && id.All(c => char.IsAsciiLetterOrDigit(c) || c == '-');","tryCatchPattern":"try { await client.DeletePortAsync(tunnelId, port, logger, ct); }\ncatch (DistributedApplicationException ex) { logger.LogWarning(ex, \"Port {Port} on {Tunnel} already gone\", port, tunnelId); }","preventionTips":["Check tunnel/port existence before deleting","Treat deletion as idempotent cleanup and swallow DistributedApplicationException","Keep devtunnel CLI updated and logged in"],"tags":["devtunnels","cli","port"],"backgroundTag":"cli-command-failed","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}