Skip to content

Commit 97a87d7

Browse files
shanselmanCopilot
andcommitted
Harden wizard progress polling budget
Allow a single long-running gateway progress step to use the full bounded progress budget instead of failing after the previous shorter per-step cap. Share the progress constants between the setup runner and UI and cover the intended budget in tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8b12acd commit 97a87d7

4 files changed

Lines changed: 31 additions & 18 deletions

File tree

src/OpenClaw.SetupEngine.UI/Pages/WizardPage.xaml.cs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ public sealed partial class WizardPage : Page
1717
private const int MaxSameStepVisits = 3;
1818

1919
// Bound progress polling separately from interactive wizard steps.
20-
private const int MaxProgressPolls = 360;
21-
private const int MaxTotalProgressPolls = 1200;
22-
private static readonly TimeSpan ProgressPollDelay = TimeSpan.FromSeconds(1);
2320

2421
private SetupConfig? _config;
2522
private OpenClawGatewayClient? _client;
@@ -252,19 +249,19 @@ private async Task ApplyPayloadAsync(JsonElement payload)
252249

253250
_progressPolls++;
254251
_totalProgressPolls++;
255-
if (_progressPolls > MaxProgressPolls)
252+
if (_progressPolls > WizardTimeouts.MaxProgressPollsPerStep)
256253
{
257-
ShowError($"Gateway wizard progress step '{_stepId}' did not complete after {MaxProgressPolls} updates.");
254+
ShowError($"Gateway wizard progress step '{_stepId}' did not complete after {WizardTimeouts.MaxProgressPollsPerStep} updates.");
258255
return;
259256
}
260-
if (_totalProgressPolls > MaxTotalProgressPolls)
257+
if (_totalProgressPolls > WizardTimeouts.MaxTotalProgressPolls)
261258
{
262-
ShowError($"Gateway wizard did not finish after {MaxTotalProgressPolls} progress updates.");
259+
ShowError($"Gateway wizard did not finish after {WizardTimeouts.MaxTotalProgressPolls} progress updates.");
263260
return;
264261
}
265262

266263
RenderProgressStep(title, message);
267-
await Task.Delay(ProgressPollDelay);
264+
await Task.Delay(WizardTimeouts.ProgressPollDelay);
268265
if (generation != _operationGeneration || _errorState || _client == null)
269266
return;
270267

src/OpenClaw.SetupEngine/SetupWizardRunner.cs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,8 @@ public sealed class SetupWizardRunner
1111
private const int MaxSameStepVisits = 3;
1212
private static readonly Regex s_normalizeKeyRegex = new("[^a-z0-9]+", RegexOptions.Compiled);
1313

14-
// Progress steps can repeat while background work runs; keep per-step and
15-
// aggregate caps so setup fails with a diagnostic instead of hanging.
16-
private const int MaxProgressPolls = 360;
17-
private const int MaxTotalProgressPolls = 1200;
18-
private static readonly TimeSpan ProgressPollDelay = TimeSpan.FromSeconds(1);
14+
// Progress steps can repeat while background work runs; keep bounded caps
15+
// so setup fails with a diagnostic instead of hanging.
1916

2017
private readonly SetupContext _ctx;
2118

@@ -182,17 +179,17 @@ async Task<JsonElement> SendWizardNextAsync(object parameters, int timeoutMs)
182179

183180
progressPolls++;
184181
totalProgressPolls++;
185-
if (progressPolls > MaxProgressPolls)
186-
return StepResult.Fail($"Gateway wizard progress step '{parsed.StepId}' did not complete after {MaxProgressPolls} polls.");
187-
if (totalProgressPolls > MaxTotalProgressPolls)
188-
return StepResult.Fail($"Gateway wizard did not finish after {MaxTotalProgressPolls} progress updates.");
182+
if (progressPolls > WizardTimeouts.MaxProgressPollsPerStep)
183+
return StepResult.Fail($"Gateway wizard progress step '{parsed.StepId}' did not complete after {WizardTimeouts.MaxProgressPollsPerStep} polls.");
184+
if (totalProgressPolls > WizardTimeouts.MaxTotalProgressPolls)
185+
return StepResult.Fail($"Gateway wizard did not finish after {WizardTimeouts.MaxTotalProgressPolls} progress updates.");
189186

190187
var progressText = $"{parsed.Title} {parsed.Message}".Trim();
191188
_ctx.Logger.Info(string.IsNullOrWhiteSpace(progressText)
192189
? $"Wizard progress step '{parsed.StepId}' — polling for next step"
193190
: $"Wizard progress: {progressText}");
194191

195-
await Task.Delay(ProgressPollDelay, ct);
192+
await Task.Delay(WizardTimeouts.ProgressPollDelay, ct);
196193
payload = await SendWizardNextAsync(WizardNextPayload.Acknowledge(sessionId, parsed.StepId), TimeoutFor(parsed));
197194
continue;
198195
}

src/OpenClaw.SetupEngine/WizardTimeouts.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ public static class WizardTimeouts
99
/// <summary>Extended timeout for steps that wait on external auth.</summary>
1010
public const int AuthTimeoutMs = 300_000;
1111

12+
/// <summary>Polling delay for gateway progress/status wizard steps.</summary>
13+
public static readonly TimeSpan ProgressPollDelay = TimeSpan.FromSeconds(1);
14+
15+
/// <summary>Total progress/status updates before setup fails as stalled.</summary>
16+
public const int MaxTotalProgressPolls = 1200;
17+
18+
/// <summary>Per-step progress/status budget; allow a single long install to consume the total budget.</summary>
19+
public const int MaxProgressPollsPerStep = MaxTotalProgressPolls;
20+
1221
private static readonly string[] s_authHints =
1322
{
1423
"device", "authorize", "login", "sign in", "oauth",

tests/OpenClaw.SetupEngine.Tests/WizardTimeoutsTests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,14 @@ public void OrdinarySteps_GetDefaultTimeout(string text)
2929
{
3030
Assert.Equal(WizardTimeouts.DefaultTimeoutMs, WizardTimeouts.ForStep(text, string.Empty));
3131
}
32+
33+
[Fact]
34+
public void ProgressPollBudget_AllowsSingleLongSetupStepToUseTotalBudget()
35+
{
36+
Assert.Equal(WizardTimeouts.MaxTotalProgressPolls, WizardTimeouts.MaxProgressPollsPerStep);
37+
var totalBudget = TimeSpan.FromTicks(
38+
WizardTimeouts.ProgressPollDelay.Ticks * WizardTimeouts.MaxTotalProgressPolls);
39+
Assert.True(
40+
totalBudget >= TimeSpan.FromMinutes(20));
41+
}
3242
}

0 commit comments

Comments
 (0)