From ce042316cca5fbbfca03fa7787d135f8e9c92553 Mon Sep 17 00:00:00 2001 From: BRUNER Patrick Date: Thu, 9 Jul 2026 09:50:21 +0200 Subject: [PATCH 1/6] Harden clipboard writes against "clipboard in use" failures Windows clipboard access throws ExternalException when another application (clipboard manager, RDP, Office, ...) holds the clipboard open - even after the WinForms-internal retries. Copying in edit mode, copying marked lines, copying the tab path, and the copy buttons in ExceptionWindow/PluginHashDialog all crashed into the exception dialog (previously reported in #195). All clipboard writes now go through ClipboardHelper.TrySetText / TrySetDataObject, which report failure instead of throwing. LogWindow shows the failure in the status line, LogTabWindow/PluginHashDialog show a message box, and ExceptionWindow fails silently so the error dialog can never cascade. Also fixes the literal \n\n (verbatim string) in ExceptionWindow and uses copy: true for SetDataObject so copied lines survive app exit, matching SetText. --- src/LogExpert.Resources/Resources.Designer.cs | 9 ++ src/LogExpert.Resources/Resources.de.resx | 3 + src/LogExpert.Resources/Resources.resx | 3 + src/LogExpert.Resources/Resources.zh-CN.resx | 3 + .../Extensions/ClipboardHelperTests.cs | 125 ++++++++++++++++++ .../Controls/LogWindow/LogWindow.cs | 14 +- src/LogExpert.UI/Dialogs/ExceptionWindow.cs | 7 +- .../Dialogs/LogTabWindow/LogTabWindow.cs | 5 +- src/LogExpert.UI/Dialogs/PluginHashDialog.cs | 12 +- .../Extensions/ClipboardHelper.cs | 47 +++++++ 10 files changed, 214 insertions(+), 14 deletions(-) create mode 100644 src/LogExpert.Tests/Extensions/ClipboardHelperTests.cs create mode 100644 src/LogExpert.UI/Extensions/ClipboardHelper.cs diff --git a/src/LogExpert.Resources/Resources.Designer.cs b/src/LogExpert.Resources/Resources.Designer.cs index 15e879d7..c73c3534 100644 --- a/src/LogExpert.Resources/Resources.Designer.cs +++ b/src/LogExpert.Resources/Resources.Designer.cs @@ -1649,6 +1649,15 @@ public static string LogExpert_Common_UI_Button_Save { } } + /// + /// Looks up a localized string similar to Could not copy to the clipboard. It is in use by another application. Please try again.. + /// + public static string LogExpert_Common_UI_Message_ClipboardInUse { + get { + return ResourceManager.GetString("LogExpert_Common_UI_Message_ClipboardInUse", resourceCulture); + } + } + /// /// Looks up a localized string similar to Deserialize. /// diff --git a/src/LogExpert.Resources/Resources.de.resx b/src/LogExpert.Resources/Resources.de.resx index 37a08534..3af85639 100644 --- a/src/LogExpert.Resources/Resources.de.resx +++ b/src/LogExpert.Resources/Resources.de.resx @@ -188,6 +188,9 @@ images\png\48\Folder_open.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + Kopieren in die Zwischenablage nicht möglich. Sie wird von einer anderen Anwendung verwendet. Bitte versuchen Sie es erneut. + LogExpert diff --git a/src/LogExpert.Resources/Resources.resx b/src/LogExpert.Resources/Resources.resx index 0ccd9002..9e7e5919 100644 --- a/src/LogExpert.Resources/Resources.resx +++ b/src/LogExpert.Resources/Resources.resx @@ -188,6 +188,9 @@ images\png\48\Folder_open.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + Could not copy to the clipboard. It is in use by another application. Please try again. + LogExpert LogExpert diff --git a/src/LogExpert.Resources/Resources.zh-CN.resx b/src/LogExpert.Resources/Resources.zh-CN.resx index b9031f65..732b85fb 100644 --- a/src/LogExpert.Resources/Resources.zh-CN.resx +++ b/src/LogExpert.Resources/Resources.zh-CN.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 无法复制到剪贴板。剪贴板正被其他应用程序使用,请重试。 + LogExpert LogExpert diff --git a/src/LogExpert.Tests/Extensions/ClipboardHelperTests.cs b/src/LogExpert.Tests/Extensions/ClipboardHelperTests.cs new file mode 100644 index 00000000..3410b36e --- /dev/null +++ b/src/LogExpert.Tests/Extensions/ClipboardHelperTests.cs @@ -0,0 +1,125 @@ +using System.Runtime.InteropServices; +using System.Windows.Forms; + +using LogExpert.UI.Extensions; + +using NUnit.Framework; + +namespace LogExpert.Tests.Extensions; + +/// +/// Tests for the hardened clipboard writes (see issue with "Requested Clipboard operation +/// did not succeed" / ExternalException, previously reported in #195). When another +/// application holds the clipboard open, LogExpert must report failure instead of crashing. +/// +[TestFixture] +[Apartment(ApartmentState.STA)] +[NonParallelizable] +public class ClipboardHelperTests +{ + [Test] + public void TrySetText_ClipboardAvailable_PlacesTextAndReturnsTrue () + { + var ok = ClipboardHelper.TrySetText("LogExpert clipboard test"); + + Assert.That(ok, Is.True); + Assert.That(GetClipboardTextWithRetry(), Is.EqualTo("LogExpert clipboard test")); + } + + /// + /// Reads the clipboard text, retrying briefly. Clipboard managers and similar tools + /// open the clipboard to inspect new content right after it changes, which can make an + /// immediate read-back fail or come up empty even though the write succeeded. + /// + private static string GetClipboardTextWithRetry () + { + for (var i = 0; i < 20; i++) + { + var text = Clipboard.GetText(); + if (!string.IsNullOrEmpty(text)) + { + return text; + } + + Thread.Sleep(100); + } + + return string.Empty; + } + + [Test] + public void TrySetText_ClipboardHeldByAnotherWindow_ReturnsFalseInsteadOfThrowing () + { + using ClipboardLock clipboardLock = new(); + + var ok = ClipboardHelper.TrySetText("some text"); + + Assert.That(ok, Is.False); + } + + [Test] + public void TrySetDataObject_ClipboardAvailable_PlacesDataAndReturnsTrue () + { + var ok = ClipboardHelper.TrySetDataObject("LogExpert data object test"); + + Assert.That(ok, Is.True); + Assert.That(GetClipboardTextWithRetry(), Is.EqualTo("LogExpert data object test")); + } + + [Test] + public void TrySetDataObject_ClipboardHeldByAnotherWindow_ReturnsFalseInsteadOfThrowing () + { + using ClipboardLock clipboardLock = new(); + + var ok = ClipboardHelper.TrySetDataObject("some data"); + + Assert.That(ok, Is.False); + } + + /// + /// Holds the Win32 clipboard open from a background thread (without closing it), + /// which makes every clipboard access in other threads/processes fail — the same + /// situation an external clipboard-monitoring tool causes. + /// + private sealed class ClipboardLock : IDisposable + { + private readonly Thread _thread; + private readonly ManualResetEventSlim _acquired = new(false); + private readonly ManualResetEventSlim _release = new(false); + + public ClipboardLock () + { + _thread = new Thread(() => + { + if (!OpenClipboard(IntPtr.Zero)) + { + throw new InvalidOperationException("Test setup failed: could not open the clipboard"); + } + + _acquired.Set(); + _release.Wait(); + _ = CloseClipboard(); + }) + { + IsBackground = true + }; + + _thread.Start(); + _acquired.Wait(TimeSpan.FromSeconds(5)); + } + + public void Dispose () + { + _release.Set(); + _thread.Join(TimeSpan.FromSeconds(5)); + } + + [DllImport("user32.dll", SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static extern bool OpenClipboard (IntPtr hWndNewOwner); + + [DllImport("user32.dll", SetLastError = true)] + [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] + private static extern bool CloseClipboard (); + } +} diff --git a/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs b/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs index c433cb3d..206ce17b 100644 --- a/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs +++ b/src/LogExpert.UI/Controls/LogWindow/LogWindow.cs @@ -1967,9 +1967,9 @@ private void OnEditModeCopyToolStripMenuItemClick (object sender, EventArgs e) { if (dataGridView.EditingControl is DataGridViewTextBoxEditingControl ctl) { - if (!string.IsNullOrEmpty(ctl.SelectedText)) + if (!string.IsNullOrEmpty(ctl.SelectedText) && !ClipboardHelper.TrySetText(ctl.SelectedText)) { - Clipboard.SetText(ctl.SelectedText); + StatusLineError(Resources.LogExpert_Common_UI_Message_ClipboardInUse); } } } @@ -5370,7 +5370,10 @@ private void CopyMarkedLinesToClipboard () data = new DataObject(DataFormats.UnicodeText, transformed); } - Clipboard.SetDataObject(data); + if (!ClipboardHelper.TrySetDataObject(data)) + { + StatusLineError(Resources.LogExpert_Common_UI_Message_ClipboardInUse); + } } else { @@ -5412,7 +5415,10 @@ private void CopyMarkedLinesToClipboard () } } - Clipboard.SetDataObject(clipText.ToString()); + if (!ClipboardHelper.TrySetDataObject(clipText.ToString())) + { + StatusLineError(Resources.LogExpert_Common_UI_Message_ClipboardInUse); + } } } diff --git a/src/LogExpert.UI/Dialogs/ExceptionWindow.cs b/src/LogExpert.UI/Dialogs/ExceptionWindow.cs index 7e283d33..b8bd96ef 100644 --- a/src/LogExpert.UI/Dialogs/ExceptionWindow.cs +++ b/src/LogExpert.UI/Dialogs/ExceptionWindow.cs @@ -1,5 +1,7 @@ using System.Runtime.Versioning; +using LogExpert.UI.Extensions; + namespace LogExpert.UI.Dialogs; [SupportedOSPlatform("windows")] @@ -28,7 +30,7 @@ public ExceptionWindow (string errorText, string stackTrace) _errorText = errorText; _stackTrace = stackTrace; - stackTraceTextBox.Text = _errorText + @"\n\n" + _stackTrace; + stackTraceTextBox.Text = _errorText + Environment.NewLine + Environment.NewLine + _stackTrace; stackTraceTextBox.Select(0, 0); ResumeLayout(); @@ -51,7 +53,8 @@ private void ApplyResources () private void CopyToClipboard () { - Clipboard.SetText(_errorText + @"\n\n" + _stackTrace); + // Never let a failed clipboard access escalate out of the error dialog itself + _ = ClipboardHelper.TrySetText(_errorText + Environment.NewLine + Environment.NewLine + _stackTrace); } #endregion diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs index 7fa3260d..9acfcdf5 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs @@ -2418,7 +2418,10 @@ private void OnToolStripButtonBubblesClick (object sender, EventArgs e) private void OnCopyPathToClipboardToolStripMenuItemClick (object sender, EventArgs e) { var logWindow = dockPanel.ActiveContent as LogWindow.LogWindow; - Clipboard.SetText(logWindow.Title); + if (!ClipboardHelper.TrySetText(logWindow.Title)) + { + _ = MessageBox.Show(this, Resources.LogExpert_Common_UI_Message_ClipboardInUse, Resources.LogExpert_Common_UI_Title_LogExpert, MessageBoxButtons.OK, MessageBoxIcon.Warning); + } } private void OnFindInExplorerToolStripMenuItemClick (object sender, EventArgs e) diff --git a/src/LogExpert.UI/Dialogs/PluginHashDialog.cs b/src/LogExpert.UI/Dialogs/PluginHashDialog.cs index 6d6f5c02..1b12e4de 100644 --- a/src/LogExpert.UI/Dialogs/PluginHashDialog.cs +++ b/src/LogExpert.UI/Dialogs/PluginHashDialog.cs @@ -1,7 +1,8 @@ using System.Globalization; -using System.Runtime.InteropServices; using System.Runtime.Versioning; +using LogExpert.UI.Extensions; + namespace LogExpert.UI.Dialogs; [SupportedOSPlatform("windows")] @@ -55,21 +56,18 @@ private void ApplyResources (string pluginName) private void OnButtonCopyClick (object sender, EventArgs e) { - try + if (ClipboardHelper.TrySetText(_hash)) { - Clipboard.SetText(_hash); _ = MessageBox.Show( Resources.PluginHashDialog_UI_Message_CopySuccess, Resources.PluginHashDialog_UI_Message_SuccessTitle, MessageBoxButtons.OK, MessageBoxIcon.Information); } - catch (Exception ex) when (ex is ExternalException or - ThreadStateException or - ThreadStateException) + else { _ = MessageBox.Show( - string.Format(CultureInfo.InvariantCulture, Resources.PluginHashDialog_UI_Message_CopyError, ex.Message), + string.Format(CultureInfo.InvariantCulture, Resources.PluginHashDialog_UI_Message_CopyError, Resources.LogExpert_Common_UI_Message_ClipboardInUse), Resources.PluginHashDialog_UI_Message_ErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); diff --git a/src/LogExpert.UI/Extensions/ClipboardHelper.cs b/src/LogExpert.UI/Extensions/ClipboardHelper.cs new file mode 100644 index 00000000..b42cab43 --- /dev/null +++ b/src/LogExpert.UI/Extensions/ClipboardHelper.cs @@ -0,0 +1,47 @@ +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +using NLog; + +namespace LogExpert.UI.Extensions; + +/// +/// Hardened clipboard writes. Windows clipboard access fails with an +/// when another application holds the clipboard open +/// (clipboard managers, RDP, Office, ...) — even after the WinForms-internal retries. +/// These helpers report failure instead of letting the exception crash the UI. +/// +[SupportedOSPlatform("windows")] +internal static class ClipboardHelper +{ + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); + + public static bool TrySetText (string text) + { + try + { + Clipboard.SetText(text); + return true; + } + catch (ExternalException e) + { + _logger.Warn(e, "Clipboard is in use by another application, could not copy text"); + return false; + } + } + + public static bool TrySetDataObject (object data) + { + try + { + // copy: true keeps the data available after LogExpert exits (same as SetText) + Clipboard.SetDataObject(data, copy: true); + return true; + } + catch (ExternalException e) + { + _logger.Warn(e, "Clipboard is in use by another application, could not copy data"); + return false; + } + } +} From 609ccb6e8ac46bdc6073f5cc471b1030998681f0 Mon Sep 17 00:00:00 2001 From: BRUNER Patrick Date: Thu, 9 Jul 2026 10:29:15 +0200 Subject: [PATCH 2/6] Clean up clipboard hardening after review Use the Vanara.PInvoke.User32 package instead of manual DllImports in the clipboard lock test fixture, drop the redundant error logging in ClipboardHelper (every call site already surfaces the failure), and simplify the PluginHashDialog copy handler to a ternary. --- .../Extensions/ClipboardHelperTests.cs | 22 ++++++------------- src/LogExpert.UI/Dialogs/PluginHashDialog.cs | 13 ++++------- .../Extensions/ClipboardHelper.cs | 10 ++------- 3 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/LogExpert.Tests/Extensions/ClipboardHelperTests.cs b/src/LogExpert.Tests/Extensions/ClipboardHelperTests.cs index 3410b36e..310d793e 100644 --- a/src/LogExpert.Tests/Extensions/ClipboardHelperTests.cs +++ b/src/LogExpert.Tests/Extensions/ClipboardHelperTests.cs @@ -1,10 +1,9 @@ -using System.Runtime.InteropServices; -using System.Windows.Forms; - using LogExpert.UI.Extensions; using NUnit.Framework; +using Vanara.PInvoke; + namespace LogExpert.Tests.Extensions; /// @@ -15,6 +14,7 @@ namespace LogExpert.Tests.Extensions; [TestFixture] [Apartment(ApartmentState.STA)] [NonParallelizable] +[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1303:Do not pass literals as localized parameters", Justification = "Unit Tests")] public class ClipboardHelperTests { [Test] @@ -91,35 +91,27 @@ public ClipboardLock () { _thread = new Thread(() => { - if (!OpenClipboard(IntPtr.Zero)) + if (!User32.OpenClipboard(HWND.NULL)) { throw new InvalidOperationException("Test setup failed: could not open the clipboard"); } _acquired.Set(); _release.Wait(); - _ = CloseClipboard(); + _ = User32.CloseClipboard(); }) { IsBackground = true }; _thread.Start(); - _acquired.Wait(TimeSpan.FromSeconds(5)); + _ = _acquired.Wait(TimeSpan.FromSeconds(5)); } public void Dispose () { _release.Set(); - _thread.Join(TimeSpan.FromSeconds(5)); + _ = _thread.Join(TimeSpan.FromSeconds(5)); } - - [DllImport("user32.dll", SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - private static extern bool OpenClipboard (IntPtr hWndNewOwner); - - [DllImport("user32.dll", SetLastError = true)] - [DefaultDllImportSearchPaths(DllImportSearchPath.System32)] - private static extern bool CloseClipboard (); } } diff --git a/src/LogExpert.UI/Dialogs/PluginHashDialog.cs b/src/LogExpert.UI/Dialogs/PluginHashDialog.cs index 1b12e4de..35fd1112 100644 --- a/src/LogExpert.UI/Dialogs/PluginHashDialog.cs +++ b/src/LogExpert.UI/Dialogs/PluginHashDialog.cs @@ -56,22 +56,17 @@ private void ApplyResources (string pluginName) private void OnButtonCopyClick (object sender, EventArgs e) { - if (ClipboardHelper.TrySetText(_hash)) - { - _ = MessageBox.Show( + _ = ClipboardHelper.TrySetText(_hash) + ? MessageBox.Show( Resources.PluginHashDialog_UI_Message_CopySuccess, Resources.PluginHashDialog_UI_Message_SuccessTitle, MessageBoxButtons.OK, - MessageBoxIcon.Information); - } - else - { - _ = MessageBox.Show( + MessageBoxIcon.Information) + : MessageBox.Show( string.Format(CultureInfo.InvariantCulture, Resources.PluginHashDialog_UI_Message_CopyError, Resources.LogExpert_Common_UI_Message_ClipboardInUse), Resources.PluginHashDialog_UI_Message_ErrorTitle, MessageBoxButtons.OK, MessageBoxIcon.Error); - } } private void OnButtonCloseClick (object sender, EventArgs e) diff --git a/src/LogExpert.UI/Extensions/ClipboardHelper.cs b/src/LogExpert.UI/Extensions/ClipboardHelper.cs index b42cab43..e0a281a7 100644 --- a/src/LogExpert.UI/Extensions/ClipboardHelper.cs +++ b/src/LogExpert.UI/Extensions/ClipboardHelper.cs @@ -1,8 +1,6 @@ using System.Runtime.InteropServices; using System.Runtime.Versioning; -using NLog; - namespace LogExpert.UI.Extensions; /// @@ -14,8 +12,6 @@ namespace LogExpert.UI.Extensions; [SupportedOSPlatform("windows")] internal static class ClipboardHelper { - private static readonly Logger _logger = LogManager.GetCurrentClassLogger(); - public static bool TrySetText (string text) { try @@ -23,9 +19,8 @@ public static bool TrySetText (string text) Clipboard.SetText(text); return true; } - catch (ExternalException e) + catch (ExternalException) { - _logger.Warn(e, "Clipboard is in use by another application, could not copy text"); return false; } } @@ -38,9 +33,8 @@ public static bool TrySetDataObject (object data) Clipboard.SetDataObject(data, copy: true); return true; } - catch (ExternalException e) + catch (ExternalException) { - _logger.Warn(e, "Clipboard is in use by another application, could not copy data"); return false; } } From 888fee3b9e8abf8af908f361fc8d26a2a7ba3599 Mon Sep 17 00:00:00 2001 From: BRUNER Patrick Date: Thu, 9 Jul 2026 10:41:56 +0200 Subject: [PATCH 3/6] update claude code, for better quality --- .github/CLAUDE.md | 98 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/.github/CLAUDE.md b/.github/CLAUDE.md index 7fab5908..394da5bd 100644 --- a/.github/CLAUDE.md +++ b/.github/CLAUDE.md @@ -334,4 +334,100 @@ To update this file, ensure that all sections are kept current with the latest a - Add new sections as needed for significant changes in the project structure or processes. - Ensure all technical terms are explained or linked to relevant documentation. - Periodically review for outdated information and remove or update as necessary. -- If told to not do something, ensure this is also added to the "Dont Do that" section. \ No newline at end of file +- If told to not do something, ensure this is also added to the "Dont Do that" section. + +# Karpathy Guidelines + +Behavioral guidelines to reduce common LLM coding mistakes. + +**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment. + +## 1. Clarify Before Executing + +**Resolve ambiguity up front — before spawning agents or starting any autonomous work.** + +Do this in order, then stop and ask before proceeding: + +1. **Understand the request.** Read what was asked closely and state your assumptions explicitly. +2. **Inspect the codebase.** Check the relevant code, conventions, and existing patterns. Note anything that's unclear or that could be interpreted in more than one way. +3. **Ask.** Raise every consequential question now, batched together — don't pick an interpretation silently, and don't defer the question into the work itself. + +Throughout: + +- If multiple interpretations exist, present them — don't choose one silently. +- If a simpler approach exists, say so. Push back when warranted. +- Don't invent APIs. Confirm a function, flag, or endpoint exists — and check its signature — before calling it. If you can't verify it, say so rather than guessing. + +Only once these questions are resolved do you move to execution (§4). + +## 2. Simplicity First + +**Minimum code that solves the problem. Nothing speculative.** + +- No features beyond what was asked. +- No abstractions for single-use code. +- No "flexibility" or "configurability" that wasn't requested. +- No error handling for impossible scenarios. +- If you write 200 lines and it could be 50, rewrite it. + +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. + +## 3. Surgical Changes + +**Touch only what you must. Clean up only your own mess.** + +When editing existing code: + +- Don't "improve" adjacent code, comments, or formatting. +- Don't refactor things that aren't broken. +- Match existing style, even if you'd do it differently. +- If you spot problems outside your task — dead code, bugs — mention them; don't fix or delete them unless asked. + +When your changes create orphans: + +- Remove imports/variables/functions that YOUR changes made unused. +- Don't remove pre-existing dead code unless asked. + +The test: every changed line should trace directly to the user's request. + +## 4. Goal-Driven Execution + +**Define success criteria. Loop until verified.** + +Begin only once §1's questions are resolved. Then turn the task into a verifiable goal with an observable check. A passing test is the strongest check; a clean typecheck, running it and inspecting output, or diffing against expected output also count. + +- "Add validation" → "Write tests for invalid inputs, then make them pass" +- "Fix the bug" → "Write a test that reproduces it, then make it pass" +- "Refactor X" → "Ensure tests pass before and after" +- "Update the config" → "Apply it, start the service, and confirm the setting takes effect" + +For multi-step tasks, state a brief plan: + +``` +1. [Step] → verify: [check] +2. [Step] → verify: [check] +3. [Step] → verify: [check] +``` + +When no obvious check exists (docs, exploration, some one-off scripts), ask how to verify success before starting. Don't report success you haven't actually observed. + +Strong, agreed-upon criteria — settled in §1 — let you loop independently. Weak criteria ("make it work") force constant clarification, which is why §1 comes first. + +## 5. Delegate Grunt Work Down a Tier + +**Spend the capable model on judgment; push mechanical, low-context work to a cheaper model.** + +Act as an orchestrator. Fan out grunt work — reading files, gathering context, simple searches, minor mechanical edits, other simple tool calls — to cheaper agents, then review their findings and any code changes yourself before acting on them. Keep the important and dangerous work — architecture, ambiguous decisions, risky or hard-to-reverse edits, final review — on the more capable model. + +Which tier to spawn depends on who you are: + +- **If you are Fable 5**, spawn Opus 4.8 agents for grunt work that still needs some capability; for genuinely simple tasks, hand off directly to Sonnet 5 instead. Review what they return. +- **If you are Opus 4.8**, and a task needs little knowledge or context, fan out to Sonnet 5 agents and review what they return. + +Match the model to the task: pick the cheapest tier that can do the job well, and skip an intermediate tier when the work is simple enough for a lower one. + +Guidelines: + +- Only delegate work that is genuinely low-context and low-risk. If doing it well requires the capable model's judgment, keep it. +- Give each agent a self-contained task with clear success criteria (per §4) so you can verify its output without redoing the work. +- Never merge a delegated finding or edit unblocked — review it first. The cheaper model did the legwork; you own the decision. \ No newline at end of file From 8e00a2e6e0510c4bb948d26a1e1857f33009d686 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 9 Jul 2026 09:01:08 +0000 Subject: [PATCH 4/6] chore: update plugin hashes [skip ci] --- .../PluginHashGenerator.Generated.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/PluginRegistry/PluginHashGenerator.Generated.cs b/src/PluginRegistry/PluginHashGenerator.Generated.cs index bc30d39e..d1915fe7 100644 --- a/src/PluginRegistry/PluginHashGenerator.Generated.cs +++ b/src/PluginRegistry/PluginHashGenerator.Generated.cs @@ -10,7 +10,7 @@ public static partial class PluginValidator { /// /// Gets pre-calculated SHA256 hashes for built-in plugins. - /// Generated: 2026-06-28 09:35:15 UTC + /// Generated: 2026-07-09 09:01:07 UTC /// Configuration: Release /// Plugin count: 21 /// @@ -18,27 +18,27 @@ public static Dictionary GetBuiltInPluginHashes() { return new Dictionary(StringComparer.OrdinalIgnoreCase) { - ["AutoColumnizer.dll"] = "ECB41011CE6D968C9DCF6264147E4DEEBB5E074C248DB234C982491241977EDD", + ["AutoColumnizer.dll"] = "3C53778AF3705B3430ED6E1EEA2F58BE19ED431CAE443B38AF2252F41F26FFC8", ["BouncyCastle.Cryptography.dll"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", ["BouncyCastle.Cryptography.dll (x86)"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", - ["CsvColumnizer.dll"] = "DBA5875075DA4B3D9DB472974238BBAC7F6E5D167CDB194D968E6776B5093763", - ["CsvColumnizer.dll (x86)"] = "DBA5875075DA4B3D9DB472974238BBAC7F6E5D167CDB194D968E6776B5093763", - ["DefaultPlugins.dll"] = "390C8FFA3DD9E02FBB3BC8EC564A33EAA1A2BF4E7D4908B550DEE5D98BE5D3B4", - ["FlashIconHighlighter.dll"] = "C1AEEF8357CC8373FCB583805299959BCBE408144389016E86177568D55AF879", - ["GlassfishColumnizer.dll"] = "B1AD18EFBBC2DAC96B28B118045334CB52B8CC36850B6212046E25FDEF65BBAC", - ["JsonColumnizer.dll"] = "A18B94F29841AB94CD8F4DE580959FF79AB889B5F7FC9D0ACA7A019D5C9293AF", - ["JsonCompactColumnizer.dll"] = "65F3EF5CC1A21B0946206CE6A72B5CFD1C1E3F773CA3BD2FEE93751F74B5C10E", - ["Log4jXmlColumnizer.dll"] = "20C9AB6C84C3D0B6D7AAF65475070061C2F3F2356162AF01D5C9177DEA3108FE", - ["LogExpert.Resources.dll"] = "1E795DD51B19A5E917934BBB3D70990362BF0C37D2976146F3FDA216506C81AD", + ["CsvColumnizer.dll"] = "A7CD190D80D095C6759C933B0CF50CBB7CCB636094D91E28F9C0DD0845717F1E", + ["CsvColumnizer.dll (x86)"] = "A7CD190D80D095C6759C933B0CF50CBB7CCB636094D91E28F9C0DD0845717F1E", + ["DefaultPlugins.dll"] = "637A125129F88032A26D36ED94D19839CA61CEB6E716B9B0C05509D9C4FF5522", + ["FlashIconHighlighter.dll"] = "71A31AEEB6AEAED61573EED75D2A6B55094CD1CEDCE98C81A4B93DFF9C892B91", + ["GlassfishColumnizer.dll"] = "C49E66A6C6CCE894AEA2A395933E038CD0B90499E2438974447EC82A9FD1019A", + ["JsonColumnizer.dll"] = "EF50D5D13AD79A6894EE6525240AC361492C69DDAC0E0430B84E2CF3661CE8AC", + ["JsonCompactColumnizer.dll"] = "31ED744159AC410DB52BA933FA7973F5432476C1B4901A4B89C0B9EE97BE2D86", + ["Log4jXmlColumnizer.dll"] = "BCA86307111F08E1D12050C5C15B95FCFCA03A1F4250C17C49667A6E3E474F93", + ["LogExpert.Resources.dll"] = "963F9C6F959FD4D2589A613B9C2674CC8474993090A58F4CD19A249EAFBFD127", ["Microsoft.Extensions.DependencyInjection.Abstractions.dll"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93", ["Microsoft.Extensions.DependencyInjection.Abstractions.dll (x86)"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93", ["Microsoft.Extensions.Logging.Abstractions.dll"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D", ["Microsoft.Extensions.Logging.Abstractions.dll (x86)"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D", - ["RegexColumnizer.dll"] = "B53EFBBB900FDEFB4757FDAB44E44985BBC99841F55FA6B2F44F646A093A1901", - ["SftpFileSystem.dll"] = "E8020D131D977B3A14768FA166D7BB11CDFDBA96AFBEDA5D86B0FC238F8386FA", - ["SftpFileSystem.dll (x86)"] = "A45F6EA1E9B39551361A9859D65A58A84E2A7862547679D7D63E9E3001AAB335", - ["SftpFileSystem.Resources.dll"] = "508D973F0ED43849088D84CC48ABE869C323F8BA878C5F022AE3C60CB4835B38", - ["SftpFileSystem.Resources.dll (x86)"] = "508D973F0ED43849088D84CC48ABE869C323F8BA878C5F022AE3C60CB4835B38", + ["RegexColumnizer.dll"] = "704D3CC72B064838D847DB90C28B0CBA4AFA7F101BE30F53C4D12D4D7E641DFE", + ["SftpFileSystem.dll"] = "6717900BE28BD4D5157DE298C5467FCC532A889F49039DF815E581E04F4F47DA", + ["SftpFileSystem.dll (x86)"] = "EFDB1C5077E2905F01F869228367FB10DB07458492D4ADED4C73E84131A26927", + ["SftpFileSystem.Resources.dll"] = "88EE58C310B2AA26C20E5A6258E50A8FE647396B33984BC0C0E1F272529B5954", + ["SftpFileSystem.Resources.dll (x86)"] = "88EE58C310B2AA26C20E5A6258E50A8FE647396B33984BC0C0E1F272529B5954", }; } From e153e6b64d83654dea8f31aa37c35565989a4f5f Mon Sep 17 00:00:00 2001 From: BRUNER Patrick Date: Thu, 9 Jul 2026 11:57:59 +0200 Subject: [PATCH 5/6] update --- src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs index 9acfcdf5..3ad5a813 100644 --- a/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs +++ b/src/LogExpert.UI/Dialogs/LogTabWindow/LogTabWindow.cs @@ -2417,7 +2417,11 @@ private void OnToolStripButtonBubblesClick (object sender, EventArgs e) [SupportedOSPlatform("windows")] private void OnCopyPathToClipboardToolStripMenuItemClick (object sender, EventArgs e) { - var logWindow = dockPanel.ActiveContent as LogWindow.LogWindow; + if (dockPanel.ActiveContent is not LogWindow.LogWindow logWindow) + { + return; + } + if (!ClipboardHelper.TrySetText(logWindow.Title)) { _ = MessageBox.Show(this, Resources.LogExpert_Common_UI_Message_ClipboardInUse, Resources.LogExpert_Common_UI_Title_LogExpert, MessageBoxButtons.OK, MessageBoxIcon.Warning); From b53d6d6acbcd7e784ff7af07e8bc49496d7fa8b6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 9 Jul 2026 10:00:41 +0000 Subject: [PATCH 6/6] chore: update plugin hashes [skip ci] --- .../PluginHashGenerator.Generated.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/PluginRegistry/PluginHashGenerator.Generated.cs b/src/PluginRegistry/PluginHashGenerator.Generated.cs index d1915fe7..a442ef7d 100644 --- a/src/PluginRegistry/PluginHashGenerator.Generated.cs +++ b/src/PluginRegistry/PluginHashGenerator.Generated.cs @@ -10,7 +10,7 @@ public static partial class PluginValidator { /// /// Gets pre-calculated SHA256 hashes for built-in plugins. - /// Generated: 2026-07-09 09:01:07 UTC + /// Generated: 2026-07-09 10:00:39 UTC /// Configuration: Release /// Plugin count: 21 /// @@ -18,27 +18,27 @@ public static Dictionary GetBuiltInPluginHashes() { return new Dictionary(StringComparer.OrdinalIgnoreCase) { - ["AutoColumnizer.dll"] = "3C53778AF3705B3430ED6E1EEA2F58BE19ED431CAE443B38AF2252F41F26FFC8", + ["AutoColumnizer.dll"] = "9AE096A54D4B084266B4F7453887D03DB80FFD0EB4188982EA7CA39D5749DAE2", ["BouncyCastle.Cryptography.dll"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", ["BouncyCastle.Cryptography.dll (x86)"] = "E5EEAF6D263C493619982FD3638E6135077311D08C961E1FE128F9107D29EBC6", - ["CsvColumnizer.dll"] = "A7CD190D80D095C6759C933B0CF50CBB7CCB636094D91E28F9C0DD0845717F1E", - ["CsvColumnizer.dll (x86)"] = "A7CD190D80D095C6759C933B0CF50CBB7CCB636094D91E28F9C0DD0845717F1E", - ["DefaultPlugins.dll"] = "637A125129F88032A26D36ED94D19839CA61CEB6E716B9B0C05509D9C4FF5522", - ["FlashIconHighlighter.dll"] = "71A31AEEB6AEAED61573EED75D2A6B55094CD1CEDCE98C81A4B93DFF9C892B91", - ["GlassfishColumnizer.dll"] = "C49E66A6C6CCE894AEA2A395933E038CD0B90499E2438974447EC82A9FD1019A", - ["JsonColumnizer.dll"] = "EF50D5D13AD79A6894EE6525240AC361492C69DDAC0E0430B84E2CF3661CE8AC", - ["JsonCompactColumnizer.dll"] = "31ED744159AC410DB52BA933FA7973F5432476C1B4901A4B89C0B9EE97BE2D86", - ["Log4jXmlColumnizer.dll"] = "BCA86307111F08E1D12050C5C15B95FCFCA03A1F4250C17C49667A6E3E474F93", - ["LogExpert.Resources.dll"] = "963F9C6F959FD4D2589A613B9C2674CC8474993090A58F4CD19A249EAFBFD127", + ["CsvColumnizer.dll"] = "21E681DD238038F026E79DD15BE9A34158D7C087BD60E7D8893BB710E5D7F566", + ["CsvColumnizer.dll (x86)"] = "21E681DD238038F026E79DD15BE9A34158D7C087BD60E7D8893BB710E5D7F566", + ["DefaultPlugins.dll"] = "8ED4B39B5A54A43A8C74BF6AF88D042C36B276EF13B8F670F12A7BE7B621A657", + ["FlashIconHighlighter.dll"] = "F2AED996E9F56F91014E9CFA2DC94F5F69FD6F365326382A7EBACA119770F224", + ["GlassfishColumnizer.dll"] = "571DCF5E49E39ABB8A439C7AE19DA58088E4ADBBEDE07B20D97AB983AE2E982D", + ["JsonColumnizer.dll"] = "A5161C33CA4912456CDBDEC742ADFDDFF592F15C80655B6F89BC8C322E77A395", + ["JsonCompactColumnizer.dll"] = "7DBC4C1C715AD8ADC32297AE415D50741CBE949E4180F87ACBA4DB4FFB491A96", + ["Log4jXmlColumnizer.dll"] = "BD3C1D71967A364C4666B3CDA4738C85677E42800C6364BC03768507F57FB624", + ["LogExpert.Resources.dll"] = "F7C7E2291D4CDB72C775B08D8E33637C1681B9557D24D3F214A8D3EFBDDEF875", ["Microsoft.Extensions.DependencyInjection.Abstractions.dll"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93", ["Microsoft.Extensions.DependencyInjection.Abstractions.dll (x86)"] = "67FA4325000DB017DC0C35829B416F024F042D24EFB868BCF17A895EE6500A93", ["Microsoft.Extensions.Logging.Abstractions.dll"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D", ["Microsoft.Extensions.Logging.Abstractions.dll (x86)"] = "BB853130F5AFAF335BE7858D661F8212EC653835100F5A4E3AA2C66A4D4F685D", - ["RegexColumnizer.dll"] = "704D3CC72B064838D847DB90C28B0CBA4AFA7F101BE30F53C4D12D4D7E641DFE", - ["SftpFileSystem.dll"] = "6717900BE28BD4D5157DE298C5467FCC532A889F49039DF815E581E04F4F47DA", - ["SftpFileSystem.dll (x86)"] = "EFDB1C5077E2905F01F869228367FB10DB07458492D4ADED4C73E84131A26927", - ["SftpFileSystem.Resources.dll"] = "88EE58C310B2AA26C20E5A6258E50A8FE647396B33984BC0C0E1F272529B5954", - ["SftpFileSystem.Resources.dll (x86)"] = "88EE58C310B2AA26C20E5A6258E50A8FE647396B33984BC0C0E1F272529B5954", + ["RegexColumnizer.dll"] = "456A2DBC538DCA15EB8135AE1D4CD990CEF07718331C281789ECDE6591C730B7", + ["SftpFileSystem.dll"] = "E0E304E9D1C957CF4E4A0C2EBD7C2D78F96A4D9133779E7E06AA70820DBDFCF2", + ["SftpFileSystem.dll (x86)"] = "E945D67FB8034C5FADE385EE76BBCDE3D09AF5322CE5D8B36E2B00E81B77728B", + ["SftpFileSystem.Resources.dll"] = "860BB8CC374983BDA9D0BEC8CE6A7E227FC9AA7DC68F47E21608C8BCD15E13ED", + ["SftpFileSystem.Resources.dll (x86)"] = "860BB8CC374983BDA9D0BEC8CE6A7E227FC9AA7DC68F47E21608C8BCD15E13ED", }; }