Skip to content

fix(engines): stop a nested moshcode child opening a second TUI#40

Merged
ralyodio merged 1 commit into
moshcoder:mainfrom
clawedassistant26:fix/nested-run-opens-tui
Jul 26, 2026
Merged

fix(engines): stop a nested moshcode child opening a second TUI#40
ralyodio merged 1 commit into
moshcoder:mainfrom
clawedassistant26:fix/nested-run-opens-tui

Conversation

@clawedassistant26

Copy link
Copy Markdown
Contributor

Calling any CLI verb from a moshscript (run(), install(), agents(), upgrade()) hangs the script on a terminal, and an executable .mosh file never returns to your shell.

Reproduce

# child.mosh
say("from the child");

# parent.mosh
say("parent before");
run("child.mosh");
say("parent after");
$ moshcode run parent.mosh          # from a real terminal
  💬 parent before
  ▶ moshcode run child.mosh
  💬 from the child
↩ moshscript exited (code 0) — back in the mosh pit. /quit to leave.
  ███╗   ███╗ ██████╗ ...           ← nested TUI banner
mosh ▸                              ← blocks here

parent after never runs. The script is now sitting in a second mosh pit that shares the parent's TTY.

The same happens via the documented shebang path: ./deploy.mosh lands in a TUI instead of exiting to your shell.

Cause

cliVerb spawns the child with stdio: "inherit" (src/cli.mjs:44), so the child inherits a TTY stdin whenever a human runs the script from a terminal. At the end of the verb the child reaches backToPit (bin/moshcode.mjs:320), whose only guard is isTTY (bin/moshcode.mjs:59):

function backToPit(label, code, signal) {
  if (!process.stdin.isTTY) process.exit(code ?? 0);
  ...
  return tui();            // child opens a full TUI
}

That guard is right for the top-level process but wrong for a spawned child: "has a TTY" is not the same as "is the session the user is driving".

The existing tests miss it because test/run-options.test.mjs feeds stdin from a file, so the child is never a TTY.

Fix

Mark spawned children with MOSHCODE_NESTED=1 and treat that like a non-TTY in backToPit. Three call sites: bin/moshcode.mjs:59, src/cli.mjs:44, bin/moshscript.mjs:17.

The parent's own drop into the mosh pit after the script finishes is unchanged — that is intended interactive behaviour.

Verification

$ moshcode run parent.mosh
  💬 parent before
  💬 from the child
  💬 parent after                   ← now runs
↩ moshscript exited (code 0) — back in the mosh pit.   ← parent's own pit, as intended

$ ./deploy.mosh
  💬 deploy ran
$ echo $?
0                                   ← exits instead of hanging

Added run() marks the nested moshcode child as nested to test/run-options.test.mjs, which asserts the child sees the flag. It fails on main (not ok 89) and passes with the fix. Full suite: 146/146 pass, up from 145 baseline.

A CLI verb called from a script (run(), install(), agents(), upgrade())
spawns `moshcode <verb>` with stdio inherited, so the child sees a TTY
whenever a human runs the script from a terminal. At the end of the verb
the child called backToPit(), printed the banner and blocked on its own
`mosh >` prompt, so the parent script never resumed.

Mark spawned children with MOSHCODE_NESTED=1 and treat that like a
non-TTY in backToPit, so a nested run exits and hands control back.
Same fix for the moshscript shim, which made an executable .mosh file
land in a TUI instead of returning to the user's shell.
@ralyodio
ralyodio merged commit 775a7e4 into moshcoder:main Jul 26, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants