fix(studio): auto-launch — confine the substrate plant, and read the off switch case-insensitively - #518
Merged
KnockOutEZ merged 2 commits intoAug 28, 2026
Conversation
…casing
Both arms are red against the source in the parent commit.
The plant helper writes a forged acquisition record at `substrateRoot()`'s
ambient answer and its caller `rmSync(root, { recursive: true, force: true })`s
that directory. `substrateRoot()` follows the memoized data dir, and
`tests/setup.ts` repoints `WIGOLO_DATA_DIR` only when unset — deliberately — so
a developer who exported the documented knob and ran `npm test` lost their real
substrate root while the suite reported 37/37 and exited 0. The new arm forces
the export, proves the forcing took effect via `resetConfig()`, and asserts the
plant lands in the per-test dir with the decoy's file surviving both the plant
and the cleanup. It reds naming the decoy root as the plant target.
The off switch compares exact-match against two lowercase literals, so `False`,
`FALSE` and `Off` all read as "not disabled" and the substrate spawns detached
and hidden against the operator's stated intent. Eight spellings plus two
anti-vacuity arms — an unset variable and a set-but-unrecognised one must both
still launch, or a bare `return null` satisfies the whole suite.
…e-insensitively The plant helper repoints `WIGOLO_DATA_DIR` at its per-test temp dir and drops the memoized config, so `substrateRoot()` — which takes no data dir, because `studioLaunchable()` reaches it through `substratePresent()` — answers inside a directory this file created. Its caller's recursive remove then targets that, not a root the suite never made. The containment check stays as the assert that the repoint took effect: a dropped `resetConfig()` or a config that stopped reading the var would silently restore the original defect, and the refusal is what fails loudly instead. It also moves the SIGKILL residue — a forged record naming a real on-disk executable — out of the path the launcher reads. The off switch was exact-match against two lowercase literals, so `False`, `FALSE` and `Off` read as "not disabled" and the substrate spawned detached and hidden against the operator's stated intent. It now matches lowercased and trimmed against `0`/`false`/`off`. `envBool` (src/config.ts) is the house comparison and normalises for the same reason; the two `WIGOLO_STUDIO_HIDDEN` fixes this phase were the env KEY's casing, which no value comparison inherits. Both widenings past the house set — `off`, and the trim — are recorded with their reversal conditions as A-189-1 and A-189-2.
|
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
wigolo-studio-run#189— PX0 exit-6 (Kimi K3 MED, measured live + security-review LOW-1, verified at tip).Two defects in one file family.
(a) The suite deleted the user's real substrate root
plantAcquiredSubstrateplanted a forged acquisition record atsubstrateRoot()'s ambient answer, and its callerrmSync(root, { recursive: true, force: true })d that directory — one the suite never created.substrateRoot()takes no data dir (studioLaunchable()reaches it throughsubstratePresent()), so it follows the memoized config, so it followsWIGOLO_DATA_DIR. Andtests/setup.ts:179repoints that variable only when unset, deliberately (:23— "the guard respects it"). So a developer who exported the documented config knob and rannpm testlost their substrate root while the suite reported green.Fix: the helper now moves the answer rather than accepting it — it repoints
WIGOLO_DATA_DIRat its per-test temp dir and callsresetConfig()(the assignment alone moves nothing; the data dir is memoized on first resolve). The containment check stays as the assert that the repoint took effect: a droppedresetConfig(), or a config that stopped reading the var, would silently restore the original defect, and the refusal fails loudly instead. That also moves the SIGKILL residue — a forged record naming a real on-disk executable — out of the path the launcher reads.Both shapes the issue offered are in, and they are not one layer keyed on one predicate: the repoint decides where, the refusal answers "did the repoint bite". Recorded as A-189-1 with its reversal condition.
Demo — the decoy repro
BEFORE (tip
06a9469f, unfixed):Green suite, exit 0, product silently uninstalled.
AFTER:
The suite stays green and the decoy survives, so the fix is not "refuse on a supported configuration".
The refusal is not dead code
Mutation probe —
resetConfig()removed from the helper, nothing else changed:The decoy arm is what kills the mutation, and that is the point of asserting the forcing took effect: with the config left cold by the previous
afterEach, the bare assignment happens to bite, so the other plant arm passes under the mutation. Only an arm that warms the cache with a foreign root first can see the decay. Constant restored by reverse-edit and re-verified green.(b) The off switch was case-sensitive
=== '0' || === 'false'exact-match, soWIGOLO_STUDIO_AUTO_LAUNCH=False(orFALSE,Off) read as "not disabled" and the substrate was spawned detached and hidden on the fetch path against the operator's stated intent. A hidden desktop process the human asked not to have is a consent surface in this design language, not a preference that failed to apply, so a near-miss spelling has to fail toward off.Now matched lowercased and trimmed against
0/false/off.envBool(src/config.ts) is the house comparison and normalises for the same reason; the twoWIGOLO_STUDIO_HIDDENfixes this phase (#179, #187) were the env key's casing, which no value comparison inherits.offand the trim go past the house set and are recorded as A-189-2 with the reversal condition (it reverses if the variable ever regains an opt-in half, at which point it is an ordinary two-direction bool and belongs onenvBoolverbatim).Demo — the real built
dist/, not the test seamThe last two rows are the anti-vacuity pair, and they are two distinct claims: an unset variable is a different code path from one that is set to an unrecognised value. Without both, a
return nullat the top ofensureStudioRunningsatisfies all eight disable arms forever.Red-then-green
f9054891(test) is the direct parent of0a287cdb(fix). Against unfixed source the new arms were red on exactly the two defects and nothing else —7 failed | 41 passed (48):Expected: ".../wig-launch-Obweyl/substrate"/Received: ".../wig-decoy-kYANEI/substrate"30254ms,30058ms,30044ms,30025ms,30062ms,30006ms) — the file took180.84s, because a value that failed to disable does not merely mis-answer, it enters the handle poll and burns the full shipped budget. The existing'0'/'false'arms stayed green throughout.After:
48 passed (48)in320ms. The 180s → 320ms collapse is itself evidence the short-circuit is the one being taken.Suites — local
npm run build→ 0npm test→ 12081 passed | 20 skipped | 7 todo (12108) / 955 files passed, 5 skipped (960), exit 0, zero failures and zero unhandled errorsnpx tsc --noEmit→ 0npm run gate:studio→ 0 (debt ratchet holds at baseline 363)CI deferred — quota (CEO directive 2026-08-28): merged on local green, issue labelled
ci-deferred, backfill tracked inwigolo-studio-run#184.Non-goals held
known-issues.md).'1', pinned by sd-164/165.Territory
Lane
core:studio-core:src/studio/auto-launch.ts+tests/unit/studio/auto-launch.test.tsonly. Two files, no widening, nolane-extra. Decoy fixtures under$TMPDIRonly; nothing written inside the repo.