fix(studio): auto-launch honours its own seam contracts — validate LaunchOutcome.failed, strip HIDDEN case-insensitively - #516
Merged
KnockOutEZ merged 2 commits intoAug 28, 2026
Conversation
…tracts Three arms, all red on this tip: normalizeLaunch keeps a truthy non-function `failed` (the `??` merge only replaces nullish), the poll's first `failed()` call sits outside the try/catch so that shape rejects out of a path documented never to throw, and defaultLaunch's env spread leaves two hidden-flag keys when the parent carried another casing.
normalizeLaunch now rejects a defined non-function `failed` the same way it rejects a non-boolean `started`. The `??` merge only replaced a nullish probe, so a truthy non-function reached the handle poll, and the poll's first `failed()` call sits a tick past the try/catch that guards the launcher: the TypeError rejected the shared promise, the memo mapper had no rejection branch, and `await inFlight` handed it to studioBridgeFetch, which awaits with no catch. Thrown at the seam instead, it is the synchronous throw the try/catch already turns into a clean decline — so the never-throws contract holds and the non-memo rule for an unattempted launch still applies. defaultLaunch strips the inherited hidden flag in any casing before re-adding the canonical one, mirroring runStudio's mechanism. The env spread collapses win32's case-insensitive proxy into a plain object, so an inherited `wigolo_studio_hidden=0` left two keys and the child resolved whichever the OS handed back first — a window could surface for a session that exists only to serve the agent.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Closes KnockOutEZ/wigolo-studio-run#187.
Two contract gaps in
src/studio/auto-launch.ts, both introduced by this phase's auto-launch work, both confirmed at the tip before the fix.Gap 1 —
normalizeLaunchvalidatedstartedbut notfailed. The merge wasresult.failed ?? NEVER_FAILED, which only replaces a nullish probe, so a truthy non-function survived normalization and was called by the handle poll. That call sits one tick past the try/catch that guards the launcher itself, so{ started: true, failed: someError }— the plausible misreading of storing the error rather than a probe of it — rejected the shared promise, the.thenmemo mapper had no rejection branch, andawait inFlighthanded the TypeError tostudioBridgeFetch, which awaits with no catch. The documented never-throws contract broke at the one seam that exists to be supplied, and the rejection also bypassed the memo write whilefinallyclearedinFlight, so every fan-out URL re-attempted. Validated at the seam instead, it is a synchronous throw the try/catch already turns into a clean decline — and, per A-185-2, an unattempted launch is still not memoized.Gap 2 —
defaultLaunchsetWIGOLO_STUDIO_HIDDENwithout the case-insensitive strip its sibling has.{ ...process.env, WIGOLO_STUDIO_HIDDEN: '1' }collapses win32's case-insensitive env proxy into a plain object: an inheritedwigolo_studio_hidden=0left two keys and the child resolved whichever the OS handed back first, so a window could surface for a session that exists only to serve the agent — a consent surface in this design language, not cosmetics. Now filtered by uppercased key before the canonical one goes back, mirroringrunStudio's mechanism atsrc/cli/studio.ts:1207.What auto-launch SETS is unchanged: hidden stays
'1', pinned by sd-164/165.Red-then-green
The tests were written and committed against the unfixed tip, so the tip itself was the reverted state — no separate revert was needed. All three arms red there, each for its own diagnosed mechanism:
The middle arm is the load-bearing one: it reproduces the escaping rejection end-to-end rather than at
normalizeLaunch, because the rejection does not happen there.After:
Verification
npm run build0 ·npx tsc --noEmit0 ·npm run gate:studio0 (debt ratchet holds at baseline 363) · fullnpm test— see the closing comment on the issue.CI deferred — quota.
Territory
src/studio/**+tests/unit/studio/**only, 2 files. No widening, nolane-extra:. TheWIGOLO_STUDIO_HIDDENspelling stays duplicated rather than shared withsrc/cli/studio.ts, because unifying it means editing a file in lanecore:cli.