Make the router's routing state authoritative instead of sniffed - #630
Draft
GoodForOneFare wants to merge 1 commit into
Draft
Make the router's routing state authoritative instead of sniffed#630GoodForOneFare wants to merge 1 commit into
GoodForOneFare wants to merge 1 commit into
Conversation
GoodForOneFare
force-pushed
the
gordo-stdout-router-reenable
branch
from
August 14, 2026 19:33
bb136f6 to
d9e06ba
Compare
Disabling the router restored the original write method but left the write_without_cli_ui marker method installed on the stream. Since enabled? checked for that marker, the router reported itself enabled while routing was actually inactive, and a later enable refused to do anything. The process was stuck: output permanently bypassed routing, and Capture#run's assert_enabled! still passed while captures silently collected nothing. with_enabled compounded this by always disabling on exit, so entering that scope in a process that had already enabled the router (the common production state) permanently broke routing. Routing state now lives in a private stream => original-write registry rather than being inferred from a marker method on the stream, so nothing on the stream can make enabled? lie: * enable/disable act per stream instead of all-or-nothing, so a process that ends up half-routed (e.g. $stdout reassigned after enable, or $stderr aliased to $stdout) can recover. with_enabled unroutes exactly the streams it routed, leaving an already-enabled router alone. * Writer holds the stream's pre-routing write method, so it no longer dispatches through write_without_cli_ui. A write already in flight on another thread survives a concurrent disable. * deactivate restores only a write it displaced. Previously every disable left a singleton copy of the class's write behind, shadowing the class method for the life of the stream. * write_without_routing is the supported way to bypass the router. Callers currently reach for the write_without_cli_ui alias directly (dev's cd/edit commands do, unguarded); that alias only exists while the stream is routed, and now really does disappear on disable. * NotEnabled moves out of class << self, where it was only reachable as StdoutRouter.singleton_class::NotEnabled - i.e. nobody could rescue the error Capture#run raises. * ensure_activated is now just enable, which is idempotent. Co-authored-by: River <river@shopify.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Assisted-By: devx/c0685ec8-2be2-4abc-9131-691e185f92ae
GoodForOneFare
force-pushed
the
gordo-stdout-router-reenable
branch
from
August 14, 2026 20:53
d9e06ba to
67db0b9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
StdoutRouter.disablerestored the originalwritemethod but left thewrite_without_cli_uimarker method installed on the stream.enabled?checked only for that marker, so after a disable the process reached a contradictory, unrecoverable state:$stdout.writeis the original method),enabled?still reportstrue,enablesees "already enabled" and refuses to do anything, forever.Worse,
Capture#run'sassert_enabled!still passed in this state (the marker is there), so captures ran but their hook never fired — captures silently returned empty output instead of raising.with_enabledcompounded this: it unconditionally disabled on exit, so entering that scope in a process that had already enabled the router — the normal production state, where the router is enabled once at startup — permanently broke routing for the rest of the process (verified onmain).Fix
Routing state now lives in a private
stream => original writeregistry rather than being inferred from a marker method on the stream, so nothing on the stream can makeenabled?lie:enable/disableact per stream instead of all-or-nothing, so a process that ends up half-routed (e.g.$stdoutreassigned afterenable, or$stderraliased to$stdout— which previously crashedenableoutright) can recover.enablereturns whether it changed anything.with_enabledunroutes exactly the streams it routed, so nesting inside an already-enabled router leaves that router alone.Writerholds the stream's pre-routingwritemethod, so it no longer dispatches through thewrite_without_cli_uialias. A write already in flight on another thread survives a concurrentdisable.deactivaterestores only awriteit displaced. Previously every disable left a singleton copy of the class'swritebehind, shadowing the class method for the life of the stream; a pre-existing foreign singletonwriteis still restored intact.write_without_routingis the supported way to bypass the router. Callers currently reach for thewrite_without_cli_uialias directly (dev'scd/editcommands do, unguarded); that alias only exists while the stream is routed, and now really does disappear on disable. The alias itself is kept for compatibility while routed.NotEnabledmoves out ofclass << self, where it was only reachable asStdoutRouter.singleton_class::NotEnabled— i.e. nobody could actually rescue the errorCapture#runraises.ensure_activatedis now justenable, which is idempotent.Tests
enabled?is truthful at each step and a capture works after re-enabling.disableleaves no singleton residue on the streams.writeis restored after disable (and still sees writes while routed).$stderrreassigned after enable) and$stderraliased to$stdout.writecaptured beforedisablestill works after it.Captureraises the (now rescuable)NotEnabledwhen the router is disabled.write_without_routingpaths (routed bypass, unrouted fallback).with_enablednesting in both directions.All run against
capture_io's swapped-in StringIO streams, so they can't interfere with a router installed on the real stdio streams.🤖 Generated with Claude Code
(posted by an LLM bot on behalf of Gord)