Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 4 additions & 6 deletions src/OpenClaw.Connection/GatewayConnectionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -289,8 +289,7 @@ tunnel.SshPort is < 1 or > 65535 ||
{
await lifecycle.ConnectAsync(ct);
}
// slopwatch-ignore: SW003 Shutdown cancellation or disposal is expected and the caller already preserves the safe state.
catch (OperationCanceledException) { }
catch (OperationCanceledException) { /* Expected: connect was cancelled. */ }
catch (Exception ex)
{
_logger.Error($"[ConnMgr] Connect failed: {ex.Message}");
Expand Down Expand Up @@ -655,8 +654,7 @@ private bool TryScheduleOperatorTokenRecovery(string message, long gen)
if (Interlocked.Read(ref _generation) != gen || _disposed) return;
await ReconnectAsync();
}
// slopwatch-ignore: SW003 Shutdown cancellation or disposal is expected and the caller already preserves the safe state.
catch (ObjectDisposedException) { }
catch (ObjectDisposedException) { /* Expected: connection manager disposed during reconnect. */ }
catch (Exception ex)
{
_logger.Warn($"[ConnMgr] Operator token recovery reconnect failed: {ex.Message}");
Expand Down Expand Up @@ -1241,8 +1239,8 @@ private async Task DisposeCoreAsync()
{
if (semaphoreEntered)
{
// slopwatch-ignore: SW003 Cleanup is best-effort; failure cannot improve caller state and the original outcome is preserved.
try { _transitionSemaphore.Release(); } catch { }
try { _transitionSemaphore.Release(); }
catch (Exception ex) { _logger.Debug($"[ConnMgr] Dispose: transition semaphore release failed: {ex.Message}"); }
_transitionSemaphore.Dispose();
}

Expand Down
8 changes: 4 additions & 4 deletions src/OpenClaw.Connection/SshTunnelService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ public void Stop()
}
finally
{
// slopwatch-ignore: SW003 Cleanup is best-effort; failure cannot improve caller state and the original outcome is preserved.
try { _process.Dispose(); } catch { }
try { _process.Dispose(); }
catch (Exception disposeEx) { _logger.Debug($"SshTunnelService.Stop: process dispose failed: {disposeEx.Message}"); }
_process = null;
_lastSpec = null;
CurrentBrowserProxyLocalPort = 0;
Expand Down Expand Up @@ -173,8 +173,8 @@ private void StartProcess(string user, string host, int remotePort, int localPor
LastError = $"SSH tunnel exited unexpectedly with code {exitCode}.";
StartedAtUtc = null;
Status = TunnelStatus.Failed;
// slopwatch-ignore: SW003 Cleanup is best-effort; failure cannot improve caller state and the original outcome is preserved.
try { process.Dispose(); } catch { }
try { process.Dispose(); }
catch (Exception disposeEx) { _logger.Debug($"SshTunnelService: process dispose after unexpected exit failed: {disposeEx.Message}"); }
_process = null;
_lastSpec = null;
CurrentBrowserProxyLocalPort = 0;
Expand Down
7 changes: 4 additions & 3 deletions src/OpenClaw.SetupEngine.UI/LogFileLauncher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,11 @@ public static void RevealInExplorer(string? logPath)
});
}
}
// slopwatch-ignore: SW003 Audited non-critical fallback is intentional and the caller preserves safe behavior without this work.
catch
catch (Exception ex)
{
// best effort — the link is informational
// best effort — the link is informational; if shell open fails
// the user can navigate to the log path manually.
System.Diagnostics.Trace.WriteLine($"LogFileLauncher.OpenContainingFolder: {ex.GetType().Name}: {ex.Message}");
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/OpenClaw.SetupEngine/AtomicFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ private static void TryDeleteTemp(string tempPath)
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"Failed to delete temporary file '{tempPath}': {ex.Message}");
// Best-effort temp cleanup; no logger available in this static helper.
System.Diagnostics.Trace.WriteLine($"AtomicFile.TryDeleteTemp('{tempPath}'): {ex.GetType().Name}: {ex.Message}");
}
}
}
8 changes: 6 additions & 2 deletions src/OpenClaw.SetupEngine/CommandRunner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,12 @@ public Task<CommandResult> RunInWslAsync(

private static void TryKill(Process process)
{
// slopwatch-ignore: SW003 Cleanup is best-effort; failure cannot improve caller state and the original outcome is preserved.
try { process.Kill(entireProcessTree: true); } catch { /* best effort */ }
try { process.Kill(entireProcessTree: true); }
catch (Exception ex)
{
// Best effort — process may already be exiting; no logger in this static helper.
Trace.WriteLine($"CommandRunner.TryKill: {ex.GetType().Name}: {ex.Message}");
}
}

private sealed class BoundedOutputBuffer(int maxChars)
Expand Down
2 changes: 1 addition & 1 deletion src/OpenClaw.SetupEngine/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ public static async Task<int> Main(string[] args)
// cannot truncate the active run's log or journal files.
using var logger = new SetupLogger(config.LogPath, Enum.TryParse<LogLevel>(config.LogLevel, true, out var lvl) ? lvl : LogLevel.Trace);
var journalPath = Path.ChangeExtension(config.LogPath, ".journal.jsonl");
using var journal = new TransactionJournal(journalPath);
using var journal = new TransactionJournal(journalPath, logger);
var commands = new CommandRunner(logger);
using var cts = new CancellationTokenSource();

Expand Down
5 changes: 4 additions & 1 deletion src/OpenClaw.SetupEngine/RetryExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ public static async Task<StepResult> ExecuteWithRetry(
}
catch (Exception ex)
{
logger.Error($"Step '{stepId}' threw exception (attempt {attempt}/{policy.MaxAttempts}): {ex.Message}");
// Logged at Warn here because we may still retry; if we exhaust retries,
// SetupLogger.StepCompleted will emit the authoritative Error entry with
// the full exception once the failing StepResult is returned.
logger.Warn($"Step '{stepId}' threw exception (attempt {attempt}/{policy.MaxAttempts}): {ex.Message}");
lastResult = StepResult.Fail($"Unhandled exception: {ex.Message}", ex);
}

Expand Down
10 changes: 10 additions & 0 deletions src/OpenClaw.SetupEngine/SetupDiagnostics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace OpenClaw.SetupEngine;

internal static class SetupDiagnostics
{
public static void TryWriteStderrWarning(string message)
{
try { Console.Error.WriteLine($"WARN: {message}"); }
catch { }
}
}
11 changes: 10 additions & 1 deletion src/OpenClaw.SetupEngine/SetupLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,16 @@ public void StepStarted(string stepId, string displayName)
=> Write(LogLevel.Info, $"step.started: {displayName}", new { step_id = stepId });

public void StepCompleted(string stepId, StepResult result, TimeSpan elapsed)
=> Write(LogLevel.Info, $"step.completed: {stepId} → {result.Outcome}", new { step_id = stepId, outcome = result.Outcome.ToString(), message = result.Message, elapsed_ms = elapsed.TotalMilliseconds });
{
Write(LogLevel.Info, $"step.completed: {stepId} → {result.Outcome}", new { step_id = stepId, outcome = result.Outcome.ToString(), message = result.Message, elapsed_ms = elapsed.TotalMilliseconds });
if (result.Error is not null)
{
// StepResult.Fail(message, ex) preserves the original exception so
// callers don't have to log it inline at every catch site. Surface
// it here at Error level so the cause is never silently dropped.
Write(LogLevel.Error, $"step.exception: {stepId}: {result.Error.GetType().Name}: {result.Error.Message}", new { step_id = stepId, exception_type = result.Error.GetType().FullName, exception = result.Error.ToString() });
}
}

public void CommandStarted(string exe, string[] args, TimeSpan timeout)
=> Write(LogLevel.Debug, $"cmd.start: {exe} {Sanitize(string.Join(' ', args))}", new { exe, args = args.Select(Sanitize).ToArray(), timeout_ms = timeout.TotalMilliseconds });
Expand Down
12 changes: 10 additions & 2 deletions src/OpenClaw.SetupEngine/SetupPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,16 @@ public async Task<PipelineResult> RunAsync(SetupContext ctx)
continue;
}

// Step failed — handle rollback if configured
ctx.Logger.Error($"Step '{step.Id}' failed: {result.Message}");
// Step failed — handle rollback if configured.
// When result.Error is set, StepCompleted already emitted an Error log for the
// exception, so we downgrade the summary to Warn to avoid duplicate Errors.
// When there is no exception (StepResult.Fail(message)), StepCompleted does NOT
// emit Error, so we keep this summary at Error so failed steps remain visible on
// Error-filtered dashboards.
if (result.Error is null)
ctx.Logger.Error($"SetupPipeline: Step '{step.Id}' failed: {result.Message}");
else
ctx.Logger.Warn($"SetupPipeline: Step '{step.Id}' failed: {result.Message}");

if (ctx.Config.RollbackOnFailure)
{
Expand Down
11 changes: 9 additions & 2 deletions src/OpenClaw.SetupEngine/SetupRunLock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ public static bool TryAcquire(string dataDir, out SetupRunLock? runLock, out str
public void Dispose()
{
_stream.Dispose();
// slopwatch-ignore: SW003 Cleanup is best-effort; failure cannot improve caller state and the original outcome is preserved.
try { File.Delete(_path); } catch { }
try { File.Delete(_path); }
catch (Exception ex)
{
// Best-effort cleanup of the run-lock file. If the delete fails, the
// next setup attempt will see an orphan lock and surface a confusing
// "another setup active" error — surface the cause via the diagnostic
// stderr channel so it is visible in test/CI logs.
SetupDiagnostics.TryWriteStderrWarning($"SetupRunLock.Dispose: failed to delete lock file '{_path}': {ex.GetType().Name}: {ex.Message}");
}
}
}
22 changes: 8 additions & 14 deletions src/OpenClaw.SetupEngine/SetupSteps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2724,14 +2724,8 @@ public override Task<StepResult> ExecuteAsync(SetupContext ctx, CancellationToke

if (File.Exists(markerPath))
{
try
{
File.Delete(markerPath);
}
catch (Exception ex)
{
ctx.Logger.Debug($"Could not delete stale keepalive marker '{markerPath}': {ex.Message}");
}
try { File.Delete(markerPath); }
catch (Exception ex) { ctx.Logger.Debug($"[Keepalive] Stale marker delete failed: {ex.Message}"); }
}

// Launch detached keepalive process — keeps the distro alive so port forwarding
Expand Down Expand Up @@ -2804,7 +2798,10 @@ internal static bool TryGetExistingKeepalive(string markerPath, string distro, o
}
catch (Exception ex)
{
logger?.Debug($"Could not validate existing keepalive marker '{markerPath}': {ex.Message}");
// TryGetExistingKeepalive returns false on any failure (file/process
// missing or unreadable). Static method — no ctx.Logger available.
// Debug-level via Trace so the failure is visible in dev diagnostics.
System.Diagnostics.Trace.WriteLine($"[Keepalive] TryGetExistingKeepalive failed: {ex.Message}");
pid = 0;
return false;
}
Expand Down Expand Up @@ -2839,10 +2836,7 @@ public override async Task RollbackAsync(SetupContext ctx, CancellationToken ct)
ctx.Logger.Info($"[Uninstall] Killed keepalive process tree PID {proc.Id}");
}
}
catch (Exception ex)
{
ctx.Logger.Debug($"[Uninstall] Skipping keepalive process PID {proc.Id}: {ex.Message}");
}
catch (Exception ex) { ctx.Logger.Debug($"[Uninstall] Keepalive proc {proc.Id} cleanup skipped (may have exited): {ex.Message}"); }
finally { proc.Dispose(); }
}
}
Expand Down Expand Up @@ -2902,7 +2896,7 @@ internal static bool IsKeepaliveCommandLine(string? commandLine, string distro)
}
catch (Exception ex)
{
logger?.Debug($"Could not read command line for process PID {pid}: {ex.Message}");
SetupDiagnostics.TryWriteStderrWarning($"Failed to query command line for process {pid}: {ex.Message}");
return null;
}
}
Expand Down
20 changes: 13 additions & 7 deletions src/OpenClaw.SetupEngine/TransactionJournal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public sealed class TransactionJournal : IDisposable
private readonly StreamWriter? _writer;
private readonly List<JournalEntry> _entries = new();
private readonly object _lock = new();
private readonly SetupLogger? _logger;

public IReadOnlyList<JournalEntry> Entries
{
Expand All @@ -20,9 +21,10 @@ public IReadOnlyList<JournalEntry> Entries
}
public string? FilePath { get; }

public TransactionJournal(string? filePath)
public TransactionJournal(string? filePath, SetupLogger? logger = null)
{
FilePath = filePath;
_logger = logger;
if (filePath != null)
{
var dir = Path.GetDirectoryName(filePath);
Expand Down Expand Up @@ -74,6 +76,7 @@ public void RecordPipelineEvent(string eventName, string? detail = null)

private void Append(JournalEntry entry)
{
IOException? writeFailure = null;
lock (_lock)
{
_entries.Add(entry);
Expand All @@ -82,21 +85,25 @@ private void Append(JournalEntry entry)
var json = JsonSerializer.Serialize(entry, _jsonOptions);
_writer?.WriteLine(json);
}
// slopwatch-ignore: SW003 Optional persisted state fallback is intentional; caller continues with defaults or prior state.
catch (IOException)
catch (IOException ex)
{
// Journal write failure is non-fatal — entries are still in memory
writeFailure = ex;
}
}

if (writeFailure != null)
_logger?.Warn("transaction journal write failed; entries remain in memory", new { file_path = FilePath, error = writeFailure.Message });
}

private void LoadExistingEntries(string filePath)
{
if (!File.Exists(filePath))
return;

var lineNumber = 0;
foreach (var line in File.ReadLines(filePath))
{
lineNumber++;
if (string.IsNullOrWhiteSpace(line))
continue;

Expand All @@ -106,10 +113,9 @@ private void LoadExistingEntries(string filePath)
if (entry != null)
_entries.Add(entry);
}
// slopwatch-ignore: SW003 Optional persisted state fallback is intentional; caller continues with defaults or prior state.
catch (JsonException)
catch (JsonException ex)
{
// Preserve the journal file as crash evidence even if one line is corrupt.
_logger?.Warn("transaction journal line is corrupt and was skipped", new { file_path = filePath, line = lineNumber, error = ex.Message });
}
}
}
Expand Down
34 changes: 20 additions & 14 deletions src/OpenClaw.Shared/Audio/PiperVoiceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,10 @@ public bool IsVoiceDownloaded(string voiceId)
&& File.Exists(GetTokensPath(voiceId))
&& Directory.Exists(GetEspeakDataDir(voiceId));
}
catch
catch (Exception ex)
{
// FindVoice throws on unknown voiceId — treat as not-downloaded.
_logger.Debug($"PiperVoiceManager.IsVoiceDownloaded('{voiceId}'): {ex.Message}");
return false;
}
}
Expand Down Expand Up @@ -221,16 +222,16 @@ private async Task DownloadVoiceCoreAsync(
{
// Best-effort cleanup — leaves the user able to retry without
// leftover partial files.
// slopwatch-ignore: SW003 Cleanup is best-effort; failure cannot improve caller state and the original outcome is preserved.
try { if (File.Exists(tarballPath)) File.Delete(tarballPath); } catch { /* swallow */ }
// slopwatch-ignore: SW003 Cleanup is best-effort; failure cannot improve caller state and the original outcome is preserved.
try { if (Directory.Exists(voiceDir) && !IsVoiceDownloaded(info.VoiceId)) Directory.Delete(voiceDir, recursive: true); } catch { /* swallow */ }
try { if (File.Exists(tarballPath)) File.Delete(tarballPath); }
catch (Exception cleanupEx) { _logger.Debug($"PiperVoiceManager: post-failure tarball cleanup failed: {cleanupEx.Message}"); }
try { if (Directory.Exists(voiceDir) && !IsVoiceDownloaded(info.VoiceId)) Directory.Delete(voiceDir, recursive: true); }
catch (Exception cleanupEx) { _logger.Debug($"PiperVoiceManager: post-failure voiceDir cleanup failed: {cleanupEx.Message}"); }
throw;
}
finally
{
// slopwatch-ignore: SW003 Cleanup is best-effort; failure cannot improve caller state and the original outcome is preserved.
try { if (File.Exists(tarballPath)) File.Delete(tarballPath); } catch { /* swallow */ }
try { if (File.Exists(tarballPath)) File.Delete(tarballPath); }
catch (Exception cleanupEx) { _logger.Debug($"PiperVoiceManager: finally tarball cleanup failed: {cleanupEx.Message}"); }
}
}

Expand Down Expand Up @@ -273,8 +274,8 @@ public long GetVoiceSize(string voiceId)
long total = 0;
foreach (var f in Directory.EnumerateFiles(dir, "*", SearchOption.AllDirectories))
{
// slopwatch-ignore: SW003 Diagnostic logging fallback is best-effort and logging failure must not cascade.
try { total += new FileInfo(f).Length; } catch { /* skip */ }
try { total += new FileInfo(f).Length; }
catch (Exception ex) { _logger.Debug($"PiperVoiceManager.GetVoiceSize: skip file '{f}': {ex.Message}"); }
}
return total;
}
Expand Down Expand Up @@ -305,8 +306,13 @@ private static void EnsureExtractorAvailable()
proc.WaitForExit(2000);
if (!proc.HasExited)
{
// slopwatch-ignore: SW003 Shutdown cancellation or disposal is expected and the caller already preserves the safe state.
try { proc.Kill(entireProcessTree: true); } catch { /* swallow */ }
try { proc.Kill(entireProcessTree: true); }
catch (Exception killEx)
{
// Static context (no instance logger). Best-effort kill of unresponsive
// tar probe; the InvalidOperationException thrown below carries the user signal.
System.Diagnostics.Trace.WriteLine($"PiperVoiceManager: tar probe kill failed: {killEx.GetType().Name}: {killEx.Message}");
}
throw new InvalidOperationException("tar.exe didn't respond to --version.");
}
if (proc.ExitCode != 0)
Expand Down Expand Up @@ -350,9 +356,9 @@ private static void ExtractTarBz2(string archivePath, string destinationDir, Can
using var proc = System.Diagnostics.Process.Start(psi)
?? throw new InvalidOperationException("Could not start tar to extract Piper voice");

// Cancellation: kill the tar process if requested.
// slopwatch-ignore: SW003 Shutdown cancellation or disposal is expected and the caller already preserves the safe state.
using var reg = cancellationToken.Register(() => { try { proc.Kill(entireProcessTree: true); } catch { /* swallow */ } });
// Cancellation: kill the tar process if requested. Static context — no logger;
// best-effort kill, the cancellation surfaces via the awaited WaitForExit.
using var reg = cancellationToken.Register(() => { try { proc.Kill(entireProcessTree: true); } catch (Exception ex) { System.Diagnostics.Trace.WriteLine($"PiperVoiceManager: tar cancellation kill failed: {ex.GetType().Name}: {ex.Message}"); } });

proc.WaitForExit();
if (proc.ExitCode != 0)
Expand Down
Loading
Loading