Skip to content
Open
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
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
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 { }
}
}
6 changes: 5 additions & 1 deletion src/OpenClaw.SetupEngine/SetupRunLock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public static bool TryAcquire(string dataDir, out SetupRunLock? runLock, out str
public void Dispose()
{
_stream.Dispose();
try { File.Delete(_path); } catch { }
try { File.Delete(_path); }
catch (Exception ex)
{
SetupDiagnostics.TryWriteStderrWarning($"Failed to delete setup lock '{_path}': {ex.Message}");
}
}
}
14 changes: 11 additions & 3 deletions src/OpenClaw.SetupEngine/SetupSteps.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2645,7 +2645,8 @@ public override Task<StepResult> ExecuteAsync(SetupContext ctx, CancellationToke

if (File.Exists(markerPath))
{
try { File.Delete(markerPath); } catch { }
try { File.Delete(markerPath); }
catch (Exception ex) { ctx.Logger.Warn($"Failed to delete stale keepalive marker '{markerPath}': {ex.Message}"); }
}

// Launch detached keepalive process — keeps the distro alive so port forwarding
Expand Down Expand Up @@ -2752,7 +2753,10 @@ public override async Task RollbackAsync(SetupContext ctx, CancellationToken ct)
ctx.Logger.Info($"[Uninstall] Killed keepalive process tree PID {proc.Id}");
}
}
catch { /* process may have exited */ }
catch (Exception ex)
{
ctx.Logger.Debug($"Skipping keepalive process candidate PID {proc.Id}: {ex.Message}");
}
finally { proc.Dispose(); }
}
}
Expand Down Expand Up @@ -2810,7 +2814,11 @@ internal static bool IsKeepaliveCommandLine(string? commandLine, string distro)
p.WaitForExit(5000);
return output.Trim();
}
catch { return null; }
catch (Exception ex)
{
SetupDiagnostics.TryWriteStderrWarning($"Failed to query command line for process {pid}: {ex.Message}");
return null;
}
}
}

Expand Down
18 changes: 13 additions & 5 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,20 +85,25 @@ private void Append(JournalEntry entry)
var json = JsonSerializer.Serialize(entry, _jsonOptions);
_writer?.WriteLine(json);
}
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 @@ -105,9 +113,9 @@ private void LoadExistingEntries(string filePath)
if (entry != null)
_entries.Add(entry);
}
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
75 changes: 75 additions & 0 deletions src/OpenClaw.Shared/TokenSanitizer.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace OpenClaw.Shared;
Expand All @@ -20,6 +24,38 @@ public static class TokenSanitizer
@"(?<![A-Za-z0-9_-])[A-Za-z0-9_-]{43}(?![A-Za-z0-9_-])",
RegexOptions.Compiled | RegexOptions.CultureInvariant);

private static readonly Regex PathWindowsUserPattern = new(
@"\b[A-Za-z]:\\Users\\[^\\\s]+",
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

private static readonly Regex PathUnixUserPattern = new(
@"/Users/[^/\s]+",
RegexOptions.Compiled | RegexOptions.CultureInvariant);

private static readonly Regex UrlHostPattern = new(
@"\b[a-z][a-z0-9+.-]*://(?:[^@\s/]+@)?(?<host>[^:/\s]+)",
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

private static readonly Regex IpAddressPattern = new(
@"\b(?:\d{1,3}\.){3}\d{1,3}\b",
RegexOptions.Compiled | RegexOptions.CultureInvariant);

private static readonly Regex EmailPattern = new(
@"\b[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\b",
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

private static readonly Regex UserAtHostPattern = new(
@"\b(?<user>[A-Za-z0-9._-]+)@(?<host>[A-Za-z0-9._-]+)(?=[:\s]|$)",
RegexOptions.Compiled | RegexOptions.CultureInvariant);

private static readonly Regex HostAfterToPattern = new(
@"(?<=\bto\s)[A-Za-z0-9._-]+(?=:\d{1,5}\b)",
RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

private static readonly Regex LeadingHostPattern = new(
@"^\s*[A-Za-z0-9._-]+(?=:\d{1,5}\b)",
RegexOptions.Compiled | RegexOptions.CultureInvariant);

public static string Sanitize(string? message)
{
if (string.IsNullOrEmpty(message))
Expand All @@ -32,4 +68,43 @@ public static string Sanitize(string? message)
sanitized = BareGatewayHexTokenPattern.Replace(sanitized, "[REDACTED_TOKEN]");
return LongBase64UrlPattern.Replace(sanitized, "[REDACTED_TOKEN]");
}

public static string SanitizeLogMessage(string? message)
{
var sanitized = Sanitize(message);
if (string.IsNullOrEmpty(sanitized))
return sanitized;

sanitized = RedactLocalPaths(sanitized);
sanitized = UrlHostPattern.Replace(
sanitized,
match => match.Value.Replace(match.Groups["host"].Value, "<host>"));
sanitized = IpAddressPattern.Replace(sanitized, "<ip>");
sanitized = EmailPattern.Replace(sanitized, "<email>");
sanitized = UserAtHostPattern.Replace(sanitized, "<user>@<host>");
sanitized = HostAfterToPattern.Replace(sanitized, "<host>");
return LeadingHostPattern.Replace(sanitized, "<host>");
}

private static string RedactLocalPaths(string message)
{
var redacted = message;
foreach (var (folder, replacement) in KnownLocalFolders()
.Where(pair => !string.IsNullOrWhiteSpace(pair.Folder))
.OrderByDescending(pair => pair.Folder.Length))
{
redacted = redacted.Replace(folder, replacement, StringComparison.OrdinalIgnoreCase);
}

redacted = PathWindowsUserPattern.Replace(redacted, "%USERPROFILE%");
return PathUnixUserPattern.Replace(redacted, "$HOME");
}

private static IEnumerable<(string Folder, string Replacement)> KnownLocalFolders()
{
yield return (Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "%USERPROFILE%");
yield return (Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "%APPDATA%");
yield return (Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "%LOCALAPPDATA%");
yield return (Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), Path.Combine("%USERPROFILE%", "Documents"));
}
}
38 changes: 33 additions & 5 deletions src/OpenClaw.Tray.WinUI/A2UI/DataModel/DataModelStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.ComponentModel;
using System.Text.Json.Nodes;
using Microsoft.UI.Dispatching;
using OpenClawTray.Services;

namespace OpenClawTray.A2UI.DataModel;

Expand Down Expand Up @@ -95,6 +96,7 @@ public void ApplyDataModelUpdate(string surfaceId, string? basePath, IReadOnlyLi
var changed = new List<string>(entries.Count);
var prefix = NormalizePath(basePath ?? "/");
if (prefix == "/") prefix = "";
var droppedEntries = 0;

foreach (var entry in entries)
{
Expand All @@ -113,12 +115,17 @@ public void ApplyDataModelUpdate(string surfaceId, string? basePath, IReadOnlyLi
model.SetByPointer(pointer, entry.ToJsonNode());
changed.Add(NormalizePath(pointer));
}
catch (Exception)
catch (Exception ex)
{
// bad pointer; skip — router logs aggregate.
droppedEntries++;
if (droppedEntries == 1)
Logger.Warn($"Dropped data model entry for surface '{surfaceId}' at key '{entry.Key}': {ex.Message}");
}
}

if (droppedEntries > 1)
Logger.Warn($"Dropped {droppedEntries} data model entries for surface '{surfaceId}'.");

if (changed.Count > 0)
new DataModelObservable(model, _dispatcher).NotifyPaths(changed);
}
Expand Down Expand Up @@ -314,7 +321,10 @@ public void Write(string pointer, JsonNode? value)
_model.SetByPointer(pointer, value);
NotifyPaths(new[] { Normalize(pointer) });
}
catch { /* swallow; bad pointer */ }
catch (Exception ex)
{
Logger.Warn($"Failed to write data model pointer '{pointer}': {ex.Message}");
}
}

/// <summary>
Expand Down Expand Up @@ -377,8 +387,26 @@ internal void NotifyAllPaths()

private void Dispatch(Action callback)
{
if (_dispatcher == null || _dispatcher.HasThreadAccess) { try { callback(); } catch { } return; }
_dispatcher.TryEnqueue(() => { try { callback(); } catch { } });
if (_dispatcher == null || _dispatcher.HasThreadAccess)
{
InvokeSubscriber(callback);
return;
}

if (!_dispatcher.TryEnqueue(() => InvokeSubscriber(callback)))
Logger.Warn("Data model subscriber callback could not be queued because the dispatcher rejected the work item");
}

private static void InvokeSubscriber(Action callback)
{
try
{
callback();
}
catch (Exception ex)
{
Logger.Warn($"Data model subscriber callback failed: {ex.Message}");
}
}

private static string Normalize(string p) =>
Expand Down
31 changes: 30 additions & 1 deletion src/OpenClaw.Tray.WinUI/App.ToastActivation.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Toolkit.Uwp.Notifications;
using OpenClaw.Shared;
using OpenClawTray.Helpers;
using OpenClawTray.Services;
using System.Diagnostics;
Expand All @@ -20,7 +21,10 @@ private void OnToastActivated(ToastNotificationActivatedEventArgsCompat args)
OpenUrl = url =>
{
try { Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }); }
catch { }
catch (Exception ex)
{
Logger.Warn($"Toast activation failed to open URL '{SanitizeToastUrlForLog(url)}': {ex.Message}");
}
},
OpenDashboard = () => OpenDashboard(),
OpenSettings = ShowSettings,
Expand All @@ -43,6 +47,31 @@ private void OnToastActivated(ToastNotificationActivatedEventArgsCompat args)
: null;
}

private static string SanitizeToastUrlForLog(string? url)
{
if (string.IsNullOrWhiteSpace(url))
return string.Empty;

var sanitized = TokenSanitizer.Sanitize(url.Trim());
if (!Uri.TryCreate(sanitized, UriKind.Absolute, out var uri))
return sanitized.Length <= 80 ? sanitized : $"{sanitized[..80]}...";

var builder = new UriBuilder(uri)
{
UserName = string.Empty,
Password = string.Empty,
Query = string.Empty,
Fragment = string.Empty
};

var safe = builder.Uri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.SafeUnescaped);
if (!string.IsNullOrEmpty(uri.Query))
safe += "?[redacted]";
if (!string.IsNullOrEmpty(uri.Fragment))
safe += "#[redacted]";
return safe;
}

public static void CopyTextToClipboard(string text)
{
ClipboardHelper.CopyText(text);
Expand Down
Loading
Loading