Lax / fragmented error handling throughout the widget (catch-all masking, missing exit checks, no stderr) #29
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Error handling across the widget is fragmented and often masks root causes:
try/catchinstatusCheckassumes every exception is a JSON parse error (#21)onExited/ exit-status check oncopyProcess(#17)A single
catchblock or missing handler frequently swallows the real failure mode and shows a generic or misleading toast.Impact
Users (and developers) cannot reliably determine why an operation failed. The same class of problem appears in multiple places, making the widget fragile and hard to debug.
FYI from cross-model audit (gemini-3.1-pro-preview):
The
try...catchinstatusCheck.onStreamFinishedcatches all errors and assumes every exception is a JSON parse error. IfparsePeersthrows a TypeError due to an unexpected API response shape, it gets swallowed and reported to the user as the generic “Failed to parse Tailscale status” toast, hiding the real cause.See also #31 (duplicate state-reset logic in statusCheck handlers).
Proposed Path Forward — Error Handling Consolidation
Resolves: #13 (double error toast spam), #14 (stale isConnected in error message), #29 (umbrella: fragmented error handling), #31 (duplicate state-reset logic)
Overview
Issues #13, #14, #29, and #31 all stem from the same root cause: error handling in
statusCheckandtoggleProcessis split across multiple handlers (onStreamFinishedcatch vsonExited), with duplicated state-reset logic and no single source of truth for what happened. This plan consolidates all error handling into a unified, single-path flow.Plan
Step 1 — Extract state-reset to a helper (fixes #31)
Create a
resetState()function inTailscaleWidget.qmlthat performs the four property resets (peers = [],ip = "",exitNode = "",binaryAvailable = false). Replace both thecatchblock and theonExitedblock's inline resets with calls to this helper. This eliminates the duplication identified in #31.Step 2 — Single-path error handling for statusCheck (fixes #13)
Currently,
statusCheckhas two independent error paths:onStreamFinishedcatch → "Failed to parse Tailscale status"onExited(code !== 0)→ "Failed to read Tailscale status" or "Tailscale binary not found"Both fire on the same failure event, producing double toasts. Consolidate into a single
onExitedhandler that:resetState(), shows exactly one toast, and returns early.resetState()and shows a single "Failed to parse" toast.Remove the
try/catchfromonStreamFinishedentirely. Move the JSON parsing intoonExited(after the exit-code guard). This ensures exactly one toast per failure event.Step 3 — Capture intended action at click time (fixes #14)
In
toggleTailscale(), capture the intended action before starting the process:In
toggleProcess.onExited, read the captured action instead of the liveroot.isConnected. This prevents the stale-state bug where a statusCheck poll flipsisConnectedbetween the user's click and the process exit.Use a simple
rootproperty likeproperty string lastToggleAction: ""to store the intended action.Step 4 — Cross-reference #25 and #17
Note that stderr capture (#25) and copyProcess exit checking (#17) are handled in the clipboard security plan. Once both plans are implemented, #29's checklist of sub-issues will all be resolved.
Success Criteria
resetState()is called from exactly two places: theonExitederror path and the JSON parse failure path.statusCheckfailure produces exactly one toast, never two.toggleProcess.onExiteduses a captured action string, not the liveroot.isConnectedproperty.try/catchinstatusCheck.stdout.onStreamFinishedis removed.AFK Classification
AFK. This is pure QML logic refactoring. The agent can verify correctness by reading the code, ensuring the control flow is sound, and running
node --test test/lib.test.js(which is unaffected sincelib.jsis not modified). The QML changes cannot be unit-tested without a DMS runtime, but the logic is straightforward enough that the agent can verify it statically.Files Modified
tailscalectl/TailscaleWidget.qml— statusCheck handlers, toggleTailscale function, toggleProcess.onExitedAlso resolves: #13, #14, #31
Not sold on step 3. This is a simple toggle. Its objective should be a dirt simple toggle. You're storing state all over the place. Why even bother telling the user what state the action failed to set? That is easy enough troubleshooting for an unlikely case. Just have a catch-all error message for the
toggleProcess.onExitedhandler that says something like "Could not toggle Tailscale connection." Easy-peasy. Lemon squeezy.All the end-user cares about is that the toggle didn't work. All the troubleshooter cares about is that the toggle isn't working. They'll need to test the raw Tailscale commands and make sure the code actually executes those Tailscale commands.
Stored state introduces potential for desynchronization between the stored state and reality. That is fertile ground for edge cases and code maintenance headaches. Sometimes that kind of thing is necessary. Usually not. As a rule-of-thumb, it's better to not seek state knowledge if you don't need it. If you do, then poll state on-demand, rather than store it, unless timing or performance demand otherwise. Now? For an error message for the toggle command? I don't think state is necessary.
Proposed Path Forward — Error Handling Consolidation (Revised)
Resolves: #13 (double error toast spam), #14 (stale isConnected in error message), #29 (umbrella: fragmented error handling), #31 (duplicate state-reset logic)
Overview
Per @jtmorris's feedback, Step 3 (storing the intended toggle action) is overengineered. A simple toggle needs a simple error message. No stored state, no desync risk. The revised plan removes that step entirely and uses a generic toggle error.
Plan
Step 1 — Extract state-reset to a helper (fixes #31)
Create a
resetState()function inTailscaleWidget.qmlthat resets the three core properties:Note:
binaryAvailableis being removed by the process safety plan (#15 / #11), so it's not included here. Replace both thecatchblock and theonExitedblock's inline resets with calls to this helper. This eliminates the duplication identified in #31.Step 2 — Single-path error handling for statusCheck (fixes #13, addresses gemini audit)
Currently,
statusCheckhas two independent error paths:onStreamFinishedcatch → "Failed to parse Tailscale status"onExited(code !== 0)→ "Failed to read Tailscale status" or "Tailscale binary not found"Both fire on the same failure event, producing double toasts. Additionally, the
try/catchinonStreamFinishedcatches all errors (including TypeErrors fromparsePeerson unexpected API shapes) and masks them as a generic JSON parse error.Consolidate into a single
onExitedhandler:resetState(), show exactly one toast, and return early.JSON.parsefails orparsePeersthrows, callresetState()and show a single "Failed to parse Tailscale status" toast.Remove the
try/catchfromonStreamFinishedentirely. Move the JSON parsing andparsePeerscall intoonExited(after the exit-code guard). This ensures exactly one toast per failure event and prevents the catch-all from swallowing TypeErrors.Step 3 — Generic toggle error (fixes #14, per @jtmorris feedback)
Remove the original Step 3 entirely. No stored state for the intended toggle action.
In
toggleProcess.onExited, use a single catch-all error message:The end user cares that the toggle didn't work. A troubleshooter will test raw Tailscale commands. Storing state for an error message introduces desync risk for no meaningful benefit.
Step 4 — Cross-reference #25
Stderr capture (#25) is handled in the clipboard security plan (#12). Once both plans are implemented, #29's checklist of sub-issues will all be resolved.
Success Criteria
resetState()is called from exactly two places: theonExitederror path and the JSON parse failure path.statusCheckfailure produces exactly one toast, never two.toggleProcess.onExitedshows a generic "Could not toggle Tailscale connection" error — no stored state, no connect/disconnect distinction.try/catchinstatusCheck.stdout.onStreamFinishedis removed.AFK Classification
AFK. Pure QML logic refactoring. The agent can verify correctness by reading the code, ensuring the control flow is sound, and running
node --test test/lib.test.js(which is unaffected sincelib.jsis not modified). The QML changes cannot be unit-tested without a DMS runtime, but the logic is straightforward enough that the agent can verify it statically.Files Modified
tailscalectl/TailscaleWidget.qml— statusCheck handlers, toggleTailscale function, toggleProcess.onExitedGood enough to give it a shot.
@vibe's latest error handling consolidation plan needs update because:
The single-path onExited + resetState helper is still sound, but implementation now involves ensuring lib.js parse functions throw appropriately for error paths, and tests cover the new flows. Generic toggle error is good.
Update plan to edit both lib.js (if needed for parse errors) + QML, plus add test assertions.
Written by AI agent working for @jtmorris. Model: Grok 4.3.
Consolidated fragmented error paths into consistent onExited + stderr pattern. Merged to vibes.
Resolved via merge
daab237. The plugin is confirmed working by @jtmorris. Human code review required before merging to testing.