Repository navigation
Legitimate close-handler veto is logged with console.error; the replaceTab path calls close handlers without a catch
#9843
Replies: 2 comments
Confirmed at source — and the
|
|
Thank you for the careful read — the On the question you raised for the return-value shape: from a consumer's seat, we'd argue for keeping the single slot. One handler per kind maps cleanly to "the plugin that owns the tab kind owns the veto," and multi-handler composition (defining "any veto keeps the tab") is a much larger contract for a rare case. So your minimal shape — keep the throw, keep the single slot, distinguish a declared veto from a genuine failure at both catch sites, with the test rewritten to express the new intent — works for us as-is. For context: this plugin already moved to a removal-first protocol (our handler never throws), so we're indifferent to the veto channel itself; what we care about is the |
Uh oh!
There was an error while loading. Please reload this page.
TL;DR —
registerCloseHandlerdocuments the contract as “Failure preserves the tab”, and a synchronous throw is indeed the only way to preserve it (removeAfterCleanupignores the handler's return value). But the shipped tab chrome wraps the close path in atry/catchthat logs every such throw at error level:console.error("Sidebar tab close failed:", error). A plugin that uses the documented veto channel therefore emits an error-level console line on every legitimate, expected close, and cannot avoid it from the plugin side. A related call site makes it worse:place()routes tab replacement through close handlers without any catch, so one plugin's veto aborts another plugin'sopenTab.Evidence —
@deepseek-ai/dsh-client-ui-sidebar-right@0.2.0-rc.2,lib/client.js1. The documented contract (~line 6338):
2. The close path — synchronous, return value ignored (~lines 6429 / 6439):
3. The throw is then logged as an error (~line 9175):
4. A second path with no catch at all (~line 6488, inside
place()):Impact
replaceTaband the replaced tab belongs to a different plugin, that plugin's close handler runs on an uncaught path: a legitimate veto throws out of the other plugin'sopenTab.Suggested fix
false/{ keepOpen: true }, or await a returned Promise beforecommit()— and reserve exceptions for genuine failures (which should be logged).CloseVetomarker class), and wrap thereplaceTabcall site in atry/catchso one plugin cannot break another.How we worked around it
We moved to a “removal-first” protocol: the close handler never throws, the framework removes the tab in the same gesture, and cleanup (session destroy + bookkeeping) finishes in the background, with one bounded retry on failure. That removed the noise entirely. The cost is that the strict ordering “clean up before the tab is removed” can no longer be expressed through the current contract — restoring it is exactly what the preferred fix above would enable.
All reactions