mirror of
https://github.com/actions/runner.git
synced 2025-12-10 12:36:23 +00:00
Compare commits
3 Commits
fhammerl/s
...
v2.280.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d17f77f5e | ||
|
|
3e46f55ac9 | ||
|
|
ed15c5389b |
@@ -1,17 +1,14 @@
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Adds support for composite actions if the server supports it (#1222)
|
|
||||||
- Adds `generateIdTokenUri` to env variables for actions (#1234)
|
|
||||||
|
|
||||||
## Bugs
|
## Bugs
|
||||||
|
|
||||||
- Prefer higher `libicu` versions in `installDependencies.sh` (#1228)
|
- Fixed a bug where composite actions did not respect `continue-on-error` (#1238)
|
||||||
|
- Fixed a bug where composite actions post steps did not have the correct step context (#1243)
|
||||||
|
|
||||||
|
|
||||||
## Misc
|
## Misc
|
||||||
|
|
||||||
- Send step telemetry to server on JobCompletion (#1229)
|
- Correctly finish Job when worker crashes with IO Exceptions (#1239)
|
||||||
- Print out the resolved SHA for each downloaded action (#1233)
|
|
||||||
|
|
||||||
## Windows x64
|
## Windows x64
|
||||||
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
We recommend configuring the runner in a root folder of the Windows drive (e.g. "C:\actions-runner"). This will help avoid issues related to service identity folder permissions and long file path restrictions on Windows.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
<Update to ./src/runnerversion when creating release>
|
2.280.1
|
||||||
|
|||||||
@@ -507,7 +507,20 @@ namespace GitHub.Runner.Listener
|
|||||||
{
|
{
|
||||||
detailInfo = string.Join(Environment.NewLine, workerOutput);
|
detailInfo = string.Join(Environment.NewLine, workerOutput);
|
||||||
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
|
Trace.Info($"Return code {returnCode} indicate worker encounter an unhandled exception or app crash, attach worker stdout/stderr to JobRequest result.");
|
||||||
await LogWorkerProcessUnhandledException(message, detailInfo);
|
|
||||||
|
var jobServer = HostContext.GetService<IJobServer>();
|
||||||
|
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
||||||
|
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
|
||||||
|
await jobServer.ConnectAsync(jobConnection);
|
||||||
|
|
||||||
|
await LogWorkerProcessUnhandledException(jobServer, message, detailInfo);
|
||||||
|
|
||||||
|
// Go ahead to finish the job with result 'Failed' if the STDERR from worker is System.IO.IOException, since it typically means we are running out of disk space.
|
||||||
|
if (detailInfo.Contains(typeof(System.IO.IOException).ToString(), StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
Trace.Info($"Finish job with result 'Failed' due to IOException.");
|
||||||
|
await ForceFailJob(jobServer, message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskResult result = TaskResultUtil.TranslateFromReturnCode(returnCode);
|
TaskResult result = TaskResultUtil.TranslateFromReturnCode(returnCode);
|
||||||
@@ -915,53 +928,16 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
|
|
||||||
// log an error issue to job level timeline record
|
// log an error issue to job level timeline record
|
||||||
private async Task LogWorkerProcessUnhandledException(Pipelines.AgentJobRequestMessage message, string errorMessage)
|
private async Task LogWorkerProcessUnhandledException(IJobServer jobServer, Pipelines.AgentJobRequestMessage message, string errorMessage)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var systemConnection = message.Resources.Endpoints.SingleOrDefault(x => string.Equals(x.Name, WellKnownServiceEndpointNames.SystemVssConnection));
|
|
||||||
ArgUtil.NotNull(systemConnection, nameof(systemConnection));
|
|
||||||
|
|
||||||
var jobServer = HostContext.GetService<IJobServer>();
|
|
||||||
VssCredentials jobServerCredential = VssUtil.GetVssCredential(systemConnection);
|
|
||||||
VssConnection jobConnection = VssUtil.CreateConnection(systemConnection.Url, jobServerCredential);
|
|
||||||
|
|
||||||
/* Below is the legacy 'OnPremises' code that is currently unused by the runner
|
|
||||||
ToDo: re-implement code as appropriate once GHES support is added.
|
|
||||||
// Make sure SystemConnection Url match Config Url base for OnPremises server
|
|
||||||
if (!message.Variables.ContainsKey(Constants.Variables.System.ServerType) ||
|
|
||||||
string.Equals(message.Variables[Constants.Variables.System.ServerType]?.Value, "OnPremises", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Uri result = null;
|
|
||||||
Uri configUri = new Uri(_runnerSetting.ServerUrl);
|
|
||||||
if (Uri.TryCreate(new Uri(configUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped)), jobServerUrl.PathAndQuery, out result))
|
|
||||||
{
|
|
||||||
//replace the schema and host portion of messageUri with the host from the
|
|
||||||
//server URI (which was set at config time)
|
|
||||||
jobServerUrl = result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException ex)
|
|
||||||
{
|
|
||||||
//cannot parse the Uri - not a fatal error
|
|
||||||
Trace.Error(ex);
|
|
||||||
}
|
|
||||||
catch (UriFormatException ex)
|
|
||||||
{
|
|
||||||
//cannot parse the Uri - not a fatal error
|
|
||||||
Trace.Error(ex);
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
await jobServer.ConnectAsync(jobConnection);
|
|
||||||
|
|
||||||
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
var timeline = await jobServer.GetTimelineAsync(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, message.Timeline.Id, CancellationToken.None);
|
||||||
|
|
||||||
ArgUtil.NotNull(timeline, nameof(timeline));
|
ArgUtil.NotNull(timeline, nameof(timeline));
|
||||||
|
|
||||||
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
TimelineRecord jobRecord = timeline.Records.FirstOrDefault(x => x.Id == message.JobId && x.RecordType == "Job");
|
||||||
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
ArgUtil.NotNull(jobRecord, nameof(jobRecord));
|
||||||
|
|
||||||
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = errorMessage };
|
var unhandledExceptionIssue = new Issue() { Type = IssueType.Error, Message = errorMessage };
|
||||||
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
unhandledExceptionIssue.Data[Constants.Runner.InternalTelemetryIssueDataKey] = Constants.Runner.WorkerCrash;
|
||||||
jobRecord.ErrorCount++;
|
jobRecord.ErrorCount++;
|
||||||
@@ -975,6 +951,21 @@ namespace GitHub.Runner.Listener
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// raise job completed event to fail the job.
|
||||||
|
private async Task ForceFailJob(IJobServer jobServer, Pipelines.AgentJobRequestMessage message)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var jobCompletedEvent = new JobCompletedEvent(message.RequestId, message.JobId, TaskResult.Failed);
|
||||||
|
await jobServer.RaisePlanEventAsync<JobCompletedEvent>(message.Plan.ScopeIdentifier, message.Plan.PlanType, message.Plan.PlanId, jobCompletedEvent, CancellationToken.None);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Trace.Error("Fail to raise JobCompletedEvent back to service.");
|
||||||
|
Trace.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class WorkerDispatcher : IDisposable
|
private class WorkerDispatcher : IDisposable
|
||||||
{
|
{
|
||||||
public long RequestId { get; }
|
public long RequestId { get; }
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ namespace GitHub.Runner.Worker
|
|||||||
Guid Id { get; }
|
Guid Id { get; }
|
||||||
Guid EmbeddedId { get; }
|
Guid EmbeddedId { get; }
|
||||||
string ScopeName { get; }
|
string ScopeName { get; }
|
||||||
|
string SiblingScopeName { get; }
|
||||||
string ContextName { get; }
|
string ContextName { get; }
|
||||||
Task ForceCompleted { get; }
|
Task ForceCompleted { get; }
|
||||||
TaskResult? Result { get; set; }
|
TaskResult? Result { get; set; }
|
||||||
@@ -74,8 +75,8 @@ namespace GitHub.Runner.Worker
|
|||||||
// Initialize
|
// Initialize
|
||||||
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
|
void InitializeJob(Pipelines.AgentJobRequestMessage message, CancellationToken token);
|
||||||
void CancelToken();
|
void CancelToken();
|
||||||
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid));
|
IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null);
|
||||||
IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, Dictionary<string, string> intraActionState = null);
|
IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, Dictionary<string, string> intraActionState = null, string siblingScopeName = null);
|
||||||
|
|
||||||
// logging
|
// logging
|
||||||
long Write(string tag, string message);
|
long Write(string tag, string message);
|
||||||
@@ -140,6 +141,7 @@ namespace GitHub.Runner.Worker
|
|||||||
public Guid Id => _record.Id;
|
public Guid Id => _record.Id;
|
||||||
public Guid EmbeddedId { get; private set; }
|
public Guid EmbeddedId { get; private set; }
|
||||||
public string ScopeName { get; private set; }
|
public string ScopeName { get; private set; }
|
||||||
|
public string SiblingScopeName { get; private set; }
|
||||||
public string ContextName { get; private set; }
|
public string ContextName { get; private set; }
|
||||||
public Task ForceCompleted => _forceCompleted.Task;
|
public Task ForceCompleted => _forceCompleted.Task;
|
||||||
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
public CancellationToken CancellationToken => _cancellationTokenSource.Token;
|
||||||
@@ -258,6 +260,7 @@ namespace GitHub.Runner.Worker
|
|||||||
|
|
||||||
public void RegisterPostJobStep(IStep step)
|
public void RegisterPostJobStep(IStep step)
|
||||||
{
|
{
|
||||||
|
string siblingScopeName = null;
|
||||||
if (this.IsEmbedded)
|
if (this.IsEmbedded)
|
||||||
{
|
{
|
||||||
if (step is IActionRunner actionRunner && !Root.EmbeddedStepsWithPostRegistered.Add(actionRunner.Action.Id))
|
if (step is IActionRunner actionRunner && !Root.EmbeddedStepsWithPostRegistered.Add(actionRunner.Action.Id))
|
||||||
@@ -271,12 +274,16 @@ namespace GitHub.Runner.Worker
|
|||||||
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to post step stack.");
|
Trace.Info($"'post' of '{actionRunner.DisplayName}' already push to post step stack.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (step is IActionRunner runner)
|
||||||
|
{
|
||||||
|
siblingScopeName = runner.Action.ContextName;
|
||||||
|
}
|
||||||
|
|
||||||
step.ExecutionContext = Root.CreatePostChild(step.DisplayName, IntraActionState);
|
step.ExecutionContext = Root.CreatePostChild(step.DisplayName, IntraActionState, siblingScopeName);
|
||||||
Root.PostJobSteps.Push(step);
|
Root.PostJobSteps.Push(step);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid))
|
public IExecutionContext CreateChild(Guid recordId, string displayName, string refName, string scopeName, string contextName, Dictionary<string, string> intraActionState = null, int? recordOrder = null, IPagingLogger logger = null, bool isEmbedded = false, CancellationTokenSource cancellationTokenSource = null, Guid embeddedId = default(Guid), string siblingScopeName = null)
|
||||||
{
|
{
|
||||||
Trace.Entering();
|
Trace.Entering();
|
||||||
|
|
||||||
@@ -286,6 +293,7 @@ namespace GitHub.Runner.Worker
|
|||||||
child.ScopeName = scopeName;
|
child.ScopeName = scopeName;
|
||||||
child.ContextName = contextName;
|
child.ContextName = contextName;
|
||||||
child.EmbeddedId = embeddedId;
|
child.EmbeddedId = embeddedId;
|
||||||
|
child.SiblingScopeName = siblingScopeName;
|
||||||
if (intraActionState == null)
|
if (intraActionState == null)
|
||||||
{
|
{
|
||||||
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
child.IntraActionState = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
@@ -333,9 +341,9 @@ namespace GitHub.Runner.Worker
|
|||||||
/// An embedded execution context shares the same record ID, record name, logger,
|
/// An embedded execution context shares the same record ID, record name, logger,
|
||||||
/// and a linked cancellation token.
|
/// and a linked cancellation token.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, Dictionary<string, string> intraActionState = null)
|
public IExecutionContext CreateEmbeddedChild(string scopeName, string contextName, Guid embeddedId, Dictionary<string, string> intraActionState = null, string siblingScopeName = null)
|
||||||
{
|
{
|
||||||
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, logger: _logger, isEmbedded: true, cancellationTokenSource: CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token), intraActionState: intraActionState, embeddedId: embeddedId);
|
return Root.CreateChild(_record.Id, _record.Name, _record.Id.ToString("N"), scopeName, contextName, logger: _logger, isEmbedded: true, cancellationTokenSource: CancellationTokenSource.CreateLinkedTokenSource(_cancellationTokenSource.Token), intraActionState: intraActionState, embeddedId: embeddedId, siblingScopeName: siblingScopeName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Start(string currentOperation = null)
|
public void Start(string currentOperation = null)
|
||||||
@@ -914,7 +922,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IExecutionContext CreatePostChild(string displayName, Dictionary<string, string> intraActionState)
|
private IExecutionContext CreatePostChild(string displayName, Dictionary<string, string> intraActionState, string siblingScopeName = null)
|
||||||
{
|
{
|
||||||
if (!_expandedForPostJob)
|
if (!_expandedForPostJob)
|
||||||
{
|
{
|
||||||
@@ -924,7 +932,7 @@ namespace GitHub.Runner.Worker
|
|||||||
}
|
}
|
||||||
|
|
||||||
var newGuid = Guid.NewGuid();
|
var newGuid = Guid.NewGuid();
|
||||||
return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count);
|
return CreateChild(newGuid, displayName, newGuid.ToString("N"), null, null, intraActionState, _childTimelineRecordOrder - Root.PostJobSteps.Count, siblingScopeName: siblingScopeName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
// only relevant for local composite actions that need to JIT download/setup containers
|
// only relevant for local composite actions that need to JIT download/setup containers
|
||||||
if (LocalActionContainerSetupSteps != null && LocalActionContainerSetupSteps.Count > 0)
|
if (LocalActionContainerSetupSteps != null && LocalActionContainerSetupSteps.Count > 0)
|
||||||
{
|
{
|
||||||
foreach(var step in LocalActionContainerSetupSteps)
|
foreach (var step in LocalActionContainerSetupSteps)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(step, step.DisplayName);
|
ArgUtil.NotNull(step, step.DisplayName);
|
||||||
var stepId = $"__{Guid.NewGuid()}";
|
var stepId = $"__{Guid.NewGuid()}";
|
||||||
@@ -128,17 +128,31 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
embeddedSteps.Add(step);
|
embeddedSteps.Add(step);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Pipelines.ActionStep stepData in steps)
|
foreach (Pipelines.ActionStep stepData in steps)
|
||||||
{
|
{
|
||||||
|
// Compute child sibling scope names for post steps
|
||||||
|
// We need to use the main's scope to keep step context correct, makes inputs flow correctly
|
||||||
|
string siblingScopeName = null;
|
||||||
|
if (!String.IsNullOrEmpty(ExecutionContext.SiblingScopeName) && stage == ActionRunStage.Post)
|
||||||
|
{
|
||||||
|
siblingScopeName = $"{ExecutionContext.SiblingScopeName}.{stepData.ContextName}";
|
||||||
|
}
|
||||||
|
|
||||||
var step = HostContext.CreateService<IActionRunner>();
|
var step = HostContext.CreateService<IActionRunner>();
|
||||||
step.Action = stepData;
|
step.Action = stepData;
|
||||||
step.Stage = stage;
|
step.Stage = stage;
|
||||||
step.Condition = stepData.Condition;
|
step.Condition = stepData.Condition;
|
||||||
ExecutionContext.Root.EmbeddedIntraActionState.TryGetValue(step.Action.Id, out var intraActionState);
|
ExecutionContext.Root.EmbeddedIntraActionState.TryGetValue(step.Action.Id, out var intraActionState);
|
||||||
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepData.ContextName, step.Action.Id, intraActionState: intraActionState);
|
step.ExecutionContext = ExecutionContext.CreateEmbeddedChild(childScopeName, stepData.ContextName, step.Action.Id, intraActionState: intraActionState, siblingScopeName: siblingScopeName);
|
||||||
step.ExecutionContext.ExpressionValues["inputs"] = inputsData;
|
step.ExecutionContext.ExpressionValues["inputs"] = inputsData;
|
||||||
|
if (!String.IsNullOrEmpty(ExecutionContext.SiblingScopeName))
|
||||||
|
{
|
||||||
|
step.ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(ExecutionContext.SiblingScopeName);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
step.ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(childScopeName);
|
step.ExecutionContext.ExpressionValues["steps"] = ExecutionContext.Global.StepsContext.GetScope(childScopeName);
|
||||||
|
}
|
||||||
|
|
||||||
// Shallow copy github context
|
// Shallow copy github context
|
||||||
var gitHubContext = step.ExecutionContext.ExpressionValues["github"] as GitHubContext;
|
var gitHubContext = step.ExecutionContext.ExpressionValues["github"] as GitHubContext;
|
||||||
@@ -153,7 +167,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run embedded steps
|
// Run embedded steps
|
||||||
await RunStepsAsync(embeddedSteps);
|
await RunStepsAsync(embeddedSteps, stage);
|
||||||
|
|
||||||
// Set outputs
|
// Set outputs
|
||||||
ExecutionContext.ExpressionValues["inputs"] = inputsData;
|
ExecutionContext.ExpressionValues["inputs"] = inputsData;
|
||||||
@@ -212,7 +226,7 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RunStepsAsync(List<IStep> embeddedSteps)
|
private async Task RunStepsAsync(List<IStep> embeddedSteps, ActionRunStage stage)
|
||||||
{
|
{
|
||||||
ArgUtil.NotNull(embeddedSteps, nameof(embeddedSteps));
|
ArgUtil.NotNull(embeddedSteps, nameof(embeddedSteps));
|
||||||
|
|
||||||
@@ -388,9 +402,13 @@ namespace GitHub.Runner.Worker.Handlers
|
|||||||
if (step.ExecutionContext.Result == TaskResult.Failed || step.ExecutionContext.Result == TaskResult.Canceled)
|
if (step.ExecutionContext.Result == TaskResult.Failed || step.ExecutionContext.Result == TaskResult.Canceled)
|
||||||
{
|
{
|
||||||
Trace.Info($"Update job result with current composite step result '{step.ExecutionContext.Result}'.");
|
Trace.Info($"Update job result with current composite step result '{step.ExecutionContext.Result}'.");
|
||||||
ExecutionContext.Result = step.ExecutionContext.Result;
|
ExecutionContext.Result = TaskResultUtil.MergeTaskResults(ExecutionContext.Result, step.ExecutionContext.Result.Value);
|
||||||
ExecutionContext.Root.Result = TaskResultUtil.MergeTaskResults(ExecutionContext.Root.Result, step.ExecutionContext.Result.Value);
|
|
||||||
ExecutionContext.Root.JobContext.Status = ExecutionContext.Root.Result?.ToActionResult();
|
// We should run cleanup even if one of the cleanup step fails
|
||||||
|
if (stage != ActionRunStage.Post)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
2.280.0
|
2.280.1
|
||||||
|
|||||||
Reference in New Issue
Block a user