Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
b00def0
Migrate SilentProcessRunner.ExecuteCommand to async
jimmyp May 25, 2026
f168db7
Migrate ISilentProcessRunner and wrappers to async
jimmyp May 25, 2026
373bce4
Migrate CommandLineRunner internals to async ExecuteCommandAsync
jimmyp May 25, 2026
1927326
Migrate RunningScript.RunScript to RunScriptAsync
jimmyp May 25, 2026
085a0fb
Update sync-boundary callers to block on ExecuteCommandAsync
jimmyp May 25, 2026
6db9733
Migrate test scaffolding callers to ExecuteCommandAsync
jimmyp May 25, 2026
72b2a9c
Name the sync-over-async pattern in sync-boundary comments
jimmyp May 25, 2026
26bfaf2
Address PR review on #1236: signature-only SilentProcessRunner change…
jimmyp May 25, 2026
e4c2911
Apply Pattern 1 sync-over-async wrap-private-async to sync↔async boun…
jimmyp May 26, 2026
0b9e11f
Push async sibling up so KubernetesScriptPodCreator awaits directly
jimmyp May 26, 2026
7bb3ef6
Rework sync-over-async justifications + fix wizard telemetry test
jimmyp May 27, 2026
0a5d941
Pull shared post-fetch logic into BuildStorageInformation helper
jimmyp May 27, 2026
e8a7126
Add abandon contracts and TentacleDebugDisableProcessKill env var
jimmyp May 26, 2026
1800584
Add abandon token to SilentProcessRunner and remove process.Close() race
jimmyp May 26, 2026
c45ba7b
Plumb abandon token through ISilentProcessRunner
jimmyp May 26, 2026
ec4ce8f
Pass abandon: CancellationToken.None at sync-boundary callers
jimmyp May 26, 2026
2b6dec1
Plumb abandon token through RunningScript, differentiate abandoned vs…
jimmyp May 26, 2026
86dc538
Implement ScriptServiceV2.AbandonScriptAsync and abandon-gated worksp…
jimmyp May 26, 2026
175cc6e
Advertise AbandonScriptV2 capability
jimmyp May 26, 2026
9a45896
Add abandon-specific tests and helpers
jimmyp May 26, 2026
8475bbc
Add EFT-3295 spec, plan, and restructuring docs
jimmyp May 26, 2026
3cbefc2
Pass abandon: CancellationToken.None at test/setup callers and extrac…
jimmyp May 26, 2026
a811ad4
Fix advertised AbandonScriptV2 capability string
jimmyp May 27, 2026
e37f98b
Rename WaitForGrandchild* to reflect that it waits for an arbitrary PID
jimmyp May 28, 2026
2bcd40c
Apply ScriptServiceV2 and RunningScript review comments
jimmyp May 28, 2026
63c10fb
Use nameof for the advertised AbandonScript capability; restructure t…
jimmyp May 28, 2026
1f0ac09
Address SilentProcessRunner comment + env var review feedback
jimmyp May 28, 2026
bef90e9
Drop explicit CancellationToken.None passes to ExecuteCommandAsync
jimmyp May 28, 2026
46882eb
Hide WaitForExit pragma inside WaitForProcessExitAsync wrapper
jimmyp May 28, 2026
67a8c2b
Expose AbandonScript on TentacleClient
jimmyp May 28, 2026
f92799f
RunningScript: symmetric Create / CreateAbandonable factories
jimmyp May 28, 2026
ff0e284
Drop elapsed-time assertion on abandon test; rely on exit code
jimmyp May 28, 2026
5219972
Fix ScriptServiceV2 CompleteScript tests to use one service instance
jimmyp May 28, 2026
f864101
Address SilentProcessRunner review feedback
jimmyp May 28, 2026
cd8efc7
Remove ClientAndTentacle.CreateScriptServiceV2Client direct-Halibut e…
jimmyp May 28, 2026
e83e00a
Use builder pattern for ScriptServiceV2Fixture SUT construction
jimmyp May 28, 2026
7c966c7
Tidy two follow-up review comments on #1226
jimmyp May 28, 2026
d5f6b83
Drop process.Close() removal comment from source
jimmyp May 28, 2026
830317f
Address review feedback on #1226
jimmyp May 28, 2026
013dfe7
Delete middle-layer abandon test in RunningScriptFixture
jimmyp May 28, 2026
bad0a31
Tighten the sleep-still-running assert comment
jimmyp May 28, 2026
82508ac
Rewrite sleep-still-running assert comment in plain prose
jimmyp May 28, 2026
c4468c5
Rewrite assert comment to include the prod-doesn't-care framing
jimmyp May 28, 2026
f6ee579
Reword assert comment per review
jimmyp May 28, 2026
fe9aa11
Retrigger CI after base branch change to main
jimmyp May 28, 2026
a76914b
Fix two CI test failures: cancel-path hang and capabilities count
jimmyp May 28, 2026
a9f8ddf
Bump grandchild-pipes test bounds to 60s on both Windows and Linux
jimmyp May 28, 2026
f1cfcc1
Restore process.Close() in cancel cleanup to release pipe handles
jimmyp May 29, 2026
3071172
Revert "Restore process.Close() in cancel cleanup to release pipe han…
jimmyp May 29, 2026
5e62dbf
Cancel WaitForExitAsync via linked token instead of process.Close()
jimmyp May 29, 2026
0bc660b
Split cancel/abandon OCE handling into two catches with asymmetric gu…
jimmyp May 29, 2026
f11754d
Make cancel-catch legible: name kill grace period, drop dead try
jimmyp May 29, 2026
a9b6699
Unlink cancel/abandon: only abandon breaks the wait; cancel waits for…
jimmyp Jun 1, 2026
fe64b87
PR1: cancel/abandon via sync WaitForExit + Task.WhenAny
jimmyp Jun 1, 2026
c16fa74
Fix net48 build: use TaskCompletionSource<object?> (non-generic overl…
jimmyp Jun 1, 2026
cf1f3b4
Consolidate Tentacle abandon spec into one canonical design
jimmyp Jun 1, 2026
9b6156d
Abandon best-effort-kills the process (in the runner's abandon branch)
jimmyp Jun 1, 2026
a3de628
ExecuteScript: optional abandonAfterCancellationPendingFor escalates …
jimmyp Jun 1, 2026
45e42f0
spec: abandon's kill lives in the runner's abandon branch (race-free)…
jimmyp Jun 1, 2026
d137217
PR2 (follow-up): async WaitForExitAsync; grandchildren require abandon
jimmyp Jun 1, 2026
f2f64a3
Fix racy both-tokens fixture test: fire abandon first (deterministic …
jimmyp Jun 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

475 changes: 475 additions & 0 deletions docs/superpowers/specs/2026-05-21-tentacle-script-abandon-design.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ public SetupTentacleWizardModelBuilder()
commandLineRunner = Substitute.For<ICommandLineRunner>();
commandLineRunner.Execute(Arg.Any<CommandLineInvocation>(), Arg.Any<ILog>()).Returns(true);
commandLineRunner.Execute(Arg.Any<IEnumerable<CommandLineInvocation>>(), Arg.Any<ILog>()).Returns(true);
commandLineRunner.ExecuteAsync(Arg.Any<CommandLineInvocation>(), Arg.Any<ILog>()).Returns(true);
commandLineRunner.ExecuteAsync(Arg.Any<IEnumerable<CommandLineInvocation>>(), Arg.Any<ILog>()).Returns(true);
}

public SetupTentacleWizardModelBuilder WithTelemetryService(ITelemetryService telemetryService)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Octopus.Tentacle.Diagnostics;
using Octopus.Tentacle.Util;

Expand All @@ -9,31 +11,46 @@ public class PowerShellPrerequisite : IPrerequisite
{
public string StatusMessage => "Checking that Windows PowerShell 2.0 is installed...";

// Why this is sync: IPrerequisite.Check() is part of a sync interface used by
// the WPF installer's prerequisite plumbing. Making it async would mean
// converting the whole IPrerequisite chain, which is a wider refactor than
// this PR.
//
// Why blocking on the async call is safe: PreReqWindow.Start dispatches each
// prerequisite via DispatchHelper.Background, which queues us via
// ThreadPool.QueueUserWorkItem. That's a plain thread-pool worker with no
// SynchronizationContext, so there's nothing for the awaited continuation
// to wait on.
// See https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
public PrerequisiteCheckResult Check()
=> CheckAsync().GetAwaiter().GetResult();

async Task<PrerequisiteCheckResult> CheckAsync()
{
return CheckPowerShellIsInstalled(out var commandLineOutput)
var (installed, commandLineOutput) = await CheckPowerShellIsInstalledAsync();
return installed
? PrerequisiteCheckResult.Successful()
: PrerequisiteCheckResult.Failed("Windows PowerShell 2.0 or above does not appear to be installed and on the System Path on this machine. Please install Windows PowerShell or add it to the System Path then re-run this setup tool.",
helpLink: "http://g.octopushq.com/HowDoIInstallPowerShell",
helpLinkText: "Download and install Windows PowerShell",
commandLineOutput: commandLineOutput);
}

static bool CheckPowerShellIsInstalled(out string commandLineOutput)
static async Task<(bool installed, string commandLineOutput)> CheckPowerShellIsInstalledAsync()
{
var stdOut = new StringWriter();
var stdErr = new StringWriter();

const string powerShellExe = "powershell.exe";
const string arguments = "-NonInteractive -NoProfile -Command \"Write-Output $PSVersionTable.PSVersion\"";
commandLineOutput = $"{powerShellExe} {arguments}";
var commandLineOutput = $"{powerShellExe} {arguments}";

// Despite our old check conforming to Microsoft's recommendations
// for PS version checking around the 1.0/2.0 era, and extending
// to detect 3.0, it failed to detect 4. Going the direct route:
try
{
SilentProcessRunnerExtended.ExecuteCommand(
await SilentProcessRunnerExtended.ExecuteCommandAsync(
powerShellExe,
arguments,
".",
Expand All @@ -45,12 +62,12 @@ static bool CheckPowerShellIsInstalled(out string commandLineOutput)

commandLineOutput = $"{commandLineOutput}{Environment.NewLine}{stdOut}{Environment.NewLine}{stdErr}";

return outputText.Contains("Major");
return (outputText.Contains("Major"), commandLineOutput);
}
catch (Exception ex)
{
commandLineOutput = $"{commandLineOutput}{Environment.NewLine}{ex}";
return false;
return (false, commandLineOutput);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public async Task<bool> GenerateAndExecuteScript()
{
var script = GenerateScript();
ContributeSensitiveValues(logger);
success = commandLineRunner.Execute(script, logger);
success = await commandLineRunner.ExecuteAsync(script, logger);
}
catch (Exception ex)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
using Octopus.Tentacle.Client.Capabilities;
using Octopus.Tentacle.Contracts.Capabilities;

namespace Octopus.Tentacle.Client.Tests
{
[TestFixture]
public class CapabilitiesResponseV2ExtensionMethodsTests
{
[Test]
public void HasAbandonScriptV2_WhenAdvertised_ReturnsTrue()
{
var capabilities = new CapabilitiesResponseV2(new List<string> { "IScriptServiceV2", "AbandonScriptAsync" });
capabilities.HasAbandonScriptV2().Should().BeTrue();
}

[Test]
public void HasAbandonScriptV2_WhenNotAdvertised_ReturnsFalse()
{
var capabilities = new CapabilitiesResponseV2(new List<string> { "IScriptServiceV2" });
capabilities.HasAbandonScriptV2().Should().BeFalse();
}

[Test]
public void HasAbandonScriptV2_WhenEmpty_ReturnsFalse()
{
var capabilities = new CapabilitiesResponseV2(new List<string>());
capabilities.HasAbandonScriptV2().Should().BeFalse();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using NSubstitute;
using NUnit.Framework;
using Octopus.Tentacle.Client.EventDriven;
using Octopus.Tentacle.Client.Scripts;
using Octopus.Tentacle.Contracts;

namespace Octopus.Tentacle.Client.Tests
{
[TestFixture]
public class ObservingScriptOrchestratorAbandonTests
{
static CommandContext Context() => new(new ScriptTicket("T"), 0, ScriptServiceVersion.ScriptServiceVersion2);

static ScriptOperationExecutionResult Running()
=> new(new ScriptStatus(ProcessState.Running, 0, new List<ProcessOutput>()), Context());

static ScriptOperationExecutionResult Complete(int exitCode)
=> new(new ScriptStatus(ProcessState.Complete, exitCode, new List<ProcessOutput>()), Context());

static ObservingScriptOrchestrator CreateOrchestrator(IScriptExecutor executor, TimeSpan? abandonAfter)
=> new(
new ImmediateBackoff(),
_ => { },
_ => Task.CompletedTask,
executor,
abandonAfter);

sealed class ImmediateBackoff : IScriptObserverBackoffStrategy
{
public TimeSpan GetBackoff(int iteration) => TimeSpan.Zero;
}

[Test]
public async Task ParamUnset_CancelsOnly_NeverAbandons()
{
var executor = Substitute.For<IScriptExecutor>();
executor.CancelScript(Arg.Any<CommandContext>())
.Returns(Running(), Complete(ScriptExitCodes.CanceledExitCode));

var orchestrator = CreateOrchestrator(executor, abandonAfter: null);
using var cts = new CancellationTokenSource();
cts.Cancel();

await orchestrator.ObserveUntilComplete(Running(), cts.Token);

await executor.DidNotReceive().AbandonScript(Arg.Any<CommandContext>());
await executor.Received().CancelScript(Arg.Any<CommandContext>());
}

[Test]
public async Task ThresholdCrossed_SwitchesFromCancelToAbandon()
{
var executor = Substitute.For<IScriptExecutor>();
// abandonAfter = Zero means the threshold is crossed on the first cancelled iteration,
// so the orchestrator abandons immediately. AbandonScript returns Complete to end the loop.
executor.AbandonScript(Arg.Any<CommandContext>())
.Returns(Complete(ScriptExitCodes.AbandonedExitCode));

var orchestrator = CreateOrchestrator(executor, abandonAfter: TimeSpan.Zero);
using var cts = new CancellationTokenSource();
cts.Cancel();

var result = await orchestrator.ObserveUntilComplete(Running(), cts.Token);

await executor.Received().AbandonScript(Arg.Any<CommandContext>());
result.ScriptStatus.ExitCode.Should().Be(ScriptExitCodes.AbandonedExitCode);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using FluentAssertions;
using Halibut.ServiceModel;
using NSubstitute;
using NUnit.Framework;
using Octopus.Tentacle.Client;
using Octopus.Tentacle.Client.EventDriven;
using Octopus.Tentacle.Client.Execution;
using Octopus.Tentacle.Client.Observability;
using Octopus.Tentacle.Client.Retries;
using Octopus.Tentacle.Client.Scripts;
using Octopus.Tentacle.Contracts;
using Octopus.Tentacle.Contracts.Capabilities;
using Octopus.Tentacle.Contracts.ClientServices;
using Octopus.Tentacle.Contracts.Logging;
using Octopus.Tentacle.Contracts.Observability;
using Octopus.Tentacle.Contracts.ScriptServiceV2;

namespace Octopus.Tentacle.Client.Tests
{
[TestFixture]
public class ScriptServiceV2ExecutorAbandonTests
{
static ScriptServiceV2Executor CreateExecutor(IAsyncClientScriptServiceV2 scriptService, IAsyncClientCapabilitiesServiceV2 capabilities)
=> new(
scriptService,
capabilities,
RpcCallExecutorFactory.Create(TimeSpan.Zero, Substitute.For<ITentacleClientObserver>()),
ClientOperationMetricsBuilder.Start(),
TimeSpan.Zero,
new TentacleClientOptions(new RpcRetrySettings(RetriesEnabled: false, RetryDuration: TimeSpan.Zero)),
Substitute.For<ITentacleClientTaskLog>());

static CommandContext Context() => new(new ScriptTicket("TestTicket"), 0, ScriptServiceVersion.ScriptServiceVersion2);

[Test]
public async Task AbandonScript_WhenCapabilityAdvertised_CallsAbandonScriptAsync()
{
var scriptService = Substitute.For<IAsyncClientScriptServiceV2>();
scriptService.AbandonScriptAsync(Arg.Any<AbandonScriptCommandV2>(), Arg.Any<HalibutProxyRequestOptions>())
.Returns(x => Task.FromResult(new ScriptStatusResponseV2(
x.Arg<AbandonScriptCommandV2>().Ticket, ProcessState.Complete,
ScriptExitCodes.AbandonedExitCode, new List<ProcessOutput>(), 1)));

var capabilities = Substitute.For<IAsyncClientCapabilitiesServiceV2>();
capabilities.GetCapabilitiesAsync(Arg.Any<HalibutProxyRequestOptions>())
.Returns(Task.FromResult(new CapabilitiesResponseV2(new List<string> { "IScriptServiceV2", "AbandonScriptAsync" })));

var executor = CreateExecutor(scriptService, capabilities);

var result = await executor.AbandonScript(Context());

await scriptService.Received(1).AbandonScriptAsync(Arg.Any<AbandonScriptCommandV2>(), Arg.Any<HalibutProxyRequestOptions>());
await scriptService.DidNotReceive().CancelScriptAsync(Arg.Any<CancelScriptCommandV2>(), Arg.Any<HalibutProxyRequestOptions>());
result.ScriptStatus.ExitCode.Should().Be(ScriptExitCodes.AbandonedExitCode);
}

[Test]
public async Task AbandonScript_WhenCapabilityAbsent_FallsBackToCancelScriptAsync()
{
var scriptService = Substitute.For<IAsyncClientScriptServiceV2>();
scriptService.CancelScriptAsync(Arg.Any<CancelScriptCommandV2>(), Arg.Any<HalibutProxyRequestOptions>())
.Returns(x => Task.FromResult(new ScriptStatusResponseV2(
x.Arg<CancelScriptCommandV2>().Ticket, ProcessState.Running,
0, new List<ProcessOutput>(), 1)));

var capabilities = Substitute.For<IAsyncClientCapabilitiesServiceV2>();
capabilities.GetCapabilitiesAsync(Arg.Any<HalibutProxyRequestOptions>())
.Returns(Task.FromResult(new CapabilitiesResponseV2(new List<string> { "IScriptServiceV2" })));

var executor = CreateExecutor(scriptService, capabilities);

await executor.AbandonScript(Context());

await scriptService.DidNotReceive().AbandonScriptAsync(Arg.Any<AbandonScriptCommandV2>(), Arg.Any<HalibutProxyRequestOptions>());
await scriptService.Received(1).CancelScriptAsync(Arg.Any<CancelScriptCommandV2>(), Arg.Any<HalibutProxyRequestOptions>());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,17 @@ public static bool HasScriptServiceV2(this CapabilitiesResponseV2 capabilities)

return capabilities.SupportedCapabilities.Contains(nameof(IScriptServiceV2));
}

public static bool HasAbandonScriptV2(this CapabilitiesResponseV2 capabilities)
{
if (capabilities?.SupportedCapabilities?.Any() != true)
{
return false;
}

// Tentacle advertises this as nameof(ScriptServiceV2.AbandonScriptAsync). Keep the
// literal in sync with CapabilitiesServiceV2.GetCapabilitiesAsync on the Tentacle side.
return capabilities.SupportedCapabilities.Contains("AbandonScriptAsync");
}
}
}
15 changes: 14 additions & 1 deletion source/Octopus.Tentacle.Client/ITentacleClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Octopus.Tentacle.Client.Scripts.Models;
using Octopus.Tentacle.Contracts;
using Octopus.Tentacle.Contracts.Logging;
using Octopus.Tentacle.Contracts.ScriptServiceV2;

namespace Octopus.Tentacle.Client
{
Expand All @@ -31,7 +32,8 @@ Task<ScriptExecutionResult> ExecuteScript(
OnScriptStatusResponseReceived onScriptStatusResponseReceived,
OnScriptCompleted onScriptCompleted,
ITentacleClientTaskLog logger,
CancellationToken scriptExecutionCancellationToken);
CancellationToken scriptExecutionCancellationToken,
TimeSpan? abandonAfterCancellationPendingFor = null);

/// <summary>
/// Start the script.
Expand Down Expand Up @@ -59,6 +61,17 @@ Task<ScriptOperationExecutionResult> StartScript(ExecuteScriptCommand command,
/// <returns>The result, which includes the CommandContext for the next command</returns>
Task<ScriptOperationExecutionResult> CancelScript(CommandContext commandContext, ITentacleClientTaskLog logger);

/// <summary>
/// Abandon a running script. Signals Tentacle to release the script's isolation mutex
/// and clean up its workspace without waiting for the underlying process to exit.
/// Used as an escape hatch when CancelScript cannot terminate a stuck process.
/// </summary>
/// <param name="scriptTicket">The ticket of the script to abandon</param>
/// <param name="logger">Used to output user orientated log messages</param>
/// <param name="cancellationToken">Cancels the RPC call</param>
/// <returns>The current status snapshot of the script at the time abandon was processed</returns>
Task<ScriptStatusResponseV2> AbandonScript(ScriptTicket scriptTicket, ITentacleClientTaskLog logger, CancellationToken cancellationToken);

/// <summary>
/// Complete the script.
/// </summary>
Expand Down
10 changes: 9 additions & 1 deletion source/Octopus.Tentacle.Client/ScriptExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,15 @@ public async Task<ScriptOperationExecutionResult> CancelScript(CommandContext co

return await scriptExecutor.CancelScript(commandContext);
}


public async Task<ScriptOperationExecutionResult> AbandonScript(CommandContext commandContext)
{
var scriptExecutorFactory = CreateScriptExecutorFactory();
var scriptExecutor = scriptExecutorFactory.CreateScriptExecutor(commandContext.ScripServiceVersionUsed);

return await scriptExecutor.AbandonScript(commandContext);
}

public async Task<ScriptStatus?> CompleteScript(CommandContext commandContext, CancellationToken cancellationToken)
{
var scriptExecutorFactory = CreateScriptExecutorFactory();
Expand Down
8 changes: 8 additions & 0 deletions source/Octopus.Tentacle.Client/Scripts/IScriptExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ Task<ScriptOperationExecutionResult> StartScript(ExecuteScriptCommand command,
/// <returns>The result, which includes the CommandContext for the next command</returns>
Task<ScriptOperationExecutionResult> CancelScript(CommandContext commandContext);

/// <summary>
/// Abandon the script: signal Tentacle to stop waiting and release the isolation mutex.
/// Returns the abandoned status when the Tentacle advertises the abandon capability;
/// otherwise falls back to cancelling and returns that result.
/// </summary>
/// <param name="commandContext">The CommandContext from the previous command</param>
Task<ScriptOperationExecutionResult> AbandonScript(CommandContext commandContext);

/// <summary>
/// Complete the script.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ async Task<KubernetesScriptStatusResponseV1> CancelScriptAction(CancellationToke
return Map(kubernetesScriptStatusResponseV1);
}

public Task<ScriptOperationExecutionResult> AbandonScript(CommandContext commandContext)
{
// KubernetesScriptServiceV1 has no abandon verb; degrade to cancel.
return CancelScript(commandContext);
}

public async Task<ScriptStatus?> CompleteScript(CommandContext lastStatusResponse, CancellationToken scriptExecutionCancellationToken)
{
using var activity = TentacleClient.ActivitySource.StartActivity($"{nameof(KubernetesScriptServiceV1Executor)}.{nameof(CompleteScript)}");
Expand Down
Loading