Skip to content

fix(exit): give system, theme and apps the menu split the rest already had - #172

Merged
MCamner merged 5 commits into
mainfrom
fix/menu-exit-status-parity
Aug 3, 2026
Merged

fix(exit): give system, theme and apps the menu split the rest already had#172
MCamner merged 5 commits into
mainfrom
fix/menu-exit-status-parity

Conversation

@MCamner

@MCamner MCamner commented Aug 3, 2026

Copy link
Copy Markdown
Owner

Closes the last open finding from the P2 operator inventory, and writes down the contract the interactive entrypoints had been guessing at.

The contract

  • a valid interactive menu with no terminal available renders at most once and exits 0
  • an operation given arguments propagates its real status
  • an invalid argument is 2
  • repos keeps 1 as a documented exception — it asks for a terminal-dependent repo picker while also offering headless subcommands, so "there was no terminal" is the true answer there

Measured

All eleven interactive entrypoints, run headless, ended at their own prompt on EOF. Three answered differently:

git release shortcuts tools workflows dev hal performance   exit 0
system theme apps                                           exit 1

apps is a third outlier the first sweep missed — it was read as an AI command and excluded on cost. Without a question it runs hal-terminal-guide.sh, an interactive guide.

Separately, theme did not meet the third point: mq-zsh-theme-switcher.sh exited 1 for an unknown command word, for apply with no variant, and for a variant that does not exist. 1 reads as "the theme could not be applied" rather than "that is not a theme". system bogusverb already answered 2, which settled it.

After

all ten local menus, headless and on a pty   exit 0, prompt drawn once
mqlaunch system check / theme current        exit 0
mqlaunch system bogusverb                    exit 2
mqlaunch theme bogusverb                     exit 2
mqlaunch theme apply                         exit 2
mqlaunch theme apply no-such-variant         exit 2
mqlaunch repos                               exit 1  (documented exception)
missing theme file                           exit 1  (runtime, not usage)

The structural gate could not have caught this, then failed for the wrong reason

Step 7 of tests/delegated-exit-code-smoke.sh flags a branch only when it both invokes a $BASE_DIR script and ends in a bare return 0. theme invokes no script, and the mixed shape is not what the pattern describes. Applying the fix then made step 7 fail, because system and apps do call scripts and now hold a deliberate return 0.

I did not reword the rule until it passed. Step 7 carries a named exception list and honours an entry only when the same branch propagates somewhere — so an exception cannot cover a branch that discards status everywhere, and a stale entry fails the step.

Tests

tests/delegated-exit-code-smoke.sh (stubbed, precise): step 10 grew to ten propagating paths, step 11 to eight deliberate zeros, and a new step 12 stubs hal-terminal-guide.sh through a fake BASE_DIR for both apps paths.

tests/menu-exit-contract-smoke.sh (real commands, coarse): all four contract points end to end through bin/mqlaunch, headless and on a pty with closed stdin, including the "drawn exactly once" half — exiting 0 by looping forever would satisfy a status-only check.

Both proven able to fail:

theme menu path propagates again      stops at delegated-exit-code step 11
apps discards status on every path    stops at delegated-exit-code step 7
theme apply back to exit 1            stops at menu-exit-contract step 6
theme menu propagates again           stops at menu-exit-contract step 2

Three things the work surfaced

  1. The registry validator reads the literal menu) arm. Lifting the menu case into a guard above the case removed it, and the validator reported system: registry lists subcommand 'menu' but dispatch does not. The menu path went back inside the case and returns 0 from within it.

  2. hal is not a menu. The headless sweep found it answering 0 and I listed it as one. It delegates to mq_hal_run, a bridge into the mq-hal repo, so on a machine without mq-hal it returns 127 — the correct answer, and CI is exactly that machine. Removed from the list, with agent, obsidian and stack absent for the same reason.

  3. mq-zsh-theme-switcher.sh could only run from $HOME/macos-scripts. It read ${HOME}/macos-scripts outright where doctor.sh and scan.sh both read ${MACOS_SCRIPTS_HOME:-$HOME/macos-scripts}, so any other checkout exited 1 with Missing UI library before its first command. Found by CI. Fixed to match its siblings. terminal/themes/mq-theme-manager.sh has the same line and is untouched.

Full suite green, registry validator green, shellcheck clean, markdownlint clean.

With this the P2 section is Status: Complete.

🤖 Generated with Claude Code

MCamner and others added 5 commits August 3, 2026 02:23
…y had

Measured headless, all eleven menu entrypoints ended at their own prompt on
EOF and three answered differently:

  git release shortcuts tools workflows dev hal performance   exit 0
  system theme apps                                           exit 1

apps is a third outlier the first sweep missed, having read it as an AI
command and excluded it on cost.

#168 settled which answer is right: a menu loop exits non-zero without a
terminal by design, so propagating that reports "the command failed" for
"there was no terminal". All three take the split #168 gave workflows — menu
path 0, argument path untouched. theme apply bogus still exits 1, theme
bogusverb 1, system bogusverb 2, a failed apps ask keeps its status.

Step 7 could not have caught this: it flags a branch only when the branch both
invokes a $BASE_DIR script and ends in a bare return 0. theme invokes no
script, and the mixed shape is not what the pattern describes. The fix then
made the step fail for the wrong reason, since system and apps do call scripts
and now hold a deliberate return 0.

Rather than reword the rule until it passed, step 7 carries a named exception
list and honours an entry only when the same branch propagates somewhere. An
exception cannot cover a branch that discards status everywhere, and a stale
entry fails the step. Behavioural proof stays separate: step 10 is ten
propagating paths, step 11 eight deliberate zeros, step 12 stubs
hal-terminal-guide.sh through a fake BASE_DIR for both apps paths.

Both gates proven able to fail: restoring the theme defect stops the run at
step 11, removing the propagating path from apps stops it at step 7.

Lifting the menu case into a guard above the case removed the literal menu)
arm, which the registry validator reads to confirm the declared subcommand
exists. The menu path went back inside the case and returns 0 from within it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d to end

The contract, written out because three commands had been guessing at it:
a valid interactive menu with no terminal renders at most once and exits 0;
an operation with arguments propagates its real status; an invalid argument
is 2; repos keeps 1 as a documented exception, since it asks for a
terminal-dependent picker while also offering headless subcommands.

theme was the one command not meeting the third point.
mq-zsh-theme-switcher.sh exited 1 for an unknown command word, for `apply`
with no variant, and for a variant that does not exist — the code a caller
reads as "the theme could not be applied" rather than "that is not a theme".
All three are 2 now. Runtime failures keep 1, which is the point of the
distinction. theme-command-surface-smoke.sh was unaffected: its exit-code step
stubs theme_cmd with an arbitrary status and asserts propagation.

tests/menu-exit-contract-smoke.sh holds all four points end to end through
bin/mqlaunch rather than through stubs, headless and on a pty with closed
stdin. Proven able to fail: putting `theme apply` back to 1 stops it at step
6, letting the theme menu propagate again stops it at step 2.

The missing-theme-file case is checked by reading the switcher rather than
running it, and that is a deliberate retreat. `apply` with a valid variant
rewrites the caller's ~/.zshrc, and THEME_FILE is assigned unconditionally at
line 6 with no override to steer it elsewhere. A first version of the step set
THEME_FILE to force the runtime branch; it had no effect, and the switcher
applied the theme to the machine running the suite. Driving `apply` for real
needs that override to exist first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The headless sweep found `hal` answering 0 and I read that as a menu ending.
It is not: the arm calls mq_hal_run, a bridge into the mq-hal repo, and its
status is that delegate's. On a machine without mq-hal checked out it returns
127, which is the correct answer rather than a contract breach — and CI is
exactly that machine, where it failed as `hal=127`.

`agent`, `obsidian` and `stack` were never on the list for the same reason.
Every command that remains runs a menu or a script inside this repo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mq-zsh-theme-switcher.sh read `${HOME}/macos-scripts` outright, where
doctor.sh and scan.sh both read `${MACOS_SCRIPTS_HOME:-$HOME/macos-scripts}`.
A checkout anywhere else could not run the switcher at all: it exited 1 with
"Missing UI library" before reaching its first command. CI found it —
`mqlaunch theme current` returned 1 there while returning 0 locally, because
the runner checks out under /home/runner/work.

That also made the runtime branch testable without side effects. `apply` with
a valid variant rewrites $ZSHRC; the earlier attempt to steer it with
THEME_FILE did nothing, since the switcher assigns that unconditionally and
never reads the environment, and the theme was applied to the machine running
the suite. MACOS_SCRIPTS_HOME is the handle that works. The step now runs the
switcher against an isolated tree and an isolated HOME, requires exit 1 and
the "Missing theme file" message, and requires that no .zshrc was written on
the way there — replacing a structural read of the source.

terminal/themes/mq-theme-manager.sh still hardcodes the path and is untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both were written before the later commits on this branch and never revised.

The theme examples said `theme apply bogus` and `theme bogusverb` still exit
1. They exit 2 — that is what the third contract point required and what the
switcher change delivered.

The menu inventory counted `hal` among eleven local menus. The branch later
established that `hal` is a delegation to mq_hal_run, not a menu, and removed
it from the test list for that reason; the prose did not follow. The contract
covers ten local menus, and both files now say why `hal` is not one of them.

Documentation only. No code or test changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@MCamner
MCamner merged commit 12e77df into main Aug 3, 2026
3 checks passed
MCamner added a commit that referenced this pull request Aug 3, 2026
…173)

It read `${HOME}/macos-scripts` outright, where doctor.sh, scan.sh and — since
#172 — mq-zsh-theme-switcher.sh all read
`${MACOS_SCRIPTS_HOME:-$HOME/macos-scripts}`. This is the sibling #172 named
and deliberately left for its own change.

Measured with HOME pointed away from the checkout, only `preview` fails: it is
the one verb that reads BASE_DIR, through UI_LIB, and it dies sourcing a UI
library that is not there. list, current, apply and reset work anywhere, which
is why this stayed invisible — and on a developer machine $HOME/macos-scripts
exists, so preview works too.

THEME_FILE stays at ${HOME}/.mq-theme. The selected theme is user state, the
same class as ~/.zshrc, and belongs in $HOME wherever the checkout lives.

tests/theme-manager-path-smoke.sh runs the manager from a temporary tree
holding nothing but a symlinked ui/, requires preview to render its panel
rather than merely exit 0, and drives apply -> current -> reset. Proven able
to fail: with the hard-coded path back it stops at step 2.

Every run gets its own HOME, and the last step compares a checksum of the real
~/.mq-theme taken before the test. apply writes and reset deletes that file,
and an earlier probe in this work applied a theme to the machine running it.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant