fix(exit): give system, theme and apps the menu split the rest already had - #172
Merged
Conversation
…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
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>
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 the last open finding from the P2 operator inventory, and writes down the contract the interactive entrypoints had been guessing at.
The contract
reposkeeps 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 thereMeasured
All eleven interactive entrypoints, run headless, ended at their own prompt on EOF. Three answered differently:
appsis a third outlier the first sweep missed — it was read as an AI command and excluded on cost. Without a question it runshal-terminal-guide.sh, an interactive guide.Separately,
themedid not meet the third point:mq-zsh-theme-switcher.shexited 1 for an unknown command word, forapplywith 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 bogusverbalready answered 2, which settled it.After
The structural gate could not have caught this, then failed for the wrong reason
Step 7 of
tests/delegated-exit-code-smoke.shflags a branch only when it both invokes a$BASE_DIRscript and ends in a barereturn 0.themeinvokes no script, and the mixed shape is not what the pattern describes. Applying the fix then made step 7 fail, becausesystemandappsdo call scripts and now hold a deliberatereturn 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 stubshal-terminal-guide.shthrough a fakeBASE_DIRfor bothappspaths.tests/menu-exit-contract-smoke.sh(real commands, coarse): all four contract points end to end throughbin/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:
Three things the work surfaced
The registry validator reads the literal
menu)arm. Lifting the menu case into a guard above thecaseremoved it, and the validator reportedsystem: registry lists subcommand 'menu' but dispatch does not. The menu path went back inside thecaseand returns 0 from within it.halis not a menu. The headless sweep found it answering 0 and I listed it as one. It delegates tomq_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, withagent,obsidianandstackabsent for the same reason.mq-zsh-theme-switcher.shcould only run from$HOME/macos-scripts. It read${HOME}/macos-scriptsoutright wheredoctor.shandscan.shboth read${MACOS_SCRIPTS_HOME:-$HOME/macos-scripts}, so any other checkout exited 1 withMissing UI librarybefore its first command. Found by CI. Fixed to match its siblings.terminal/themes/mq-theme-manager.shhas 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