Files
stack/docs/scratchpads/yolo-runtime-initial-arg-20260411.md
Jason Woltje 75abfc806b
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
docs(scratchpad): finalize yolo runtime hotfix evidence
Marks all checklist items complete and records final merge/CI/issue-close
evidence for mosaicstack/stack#454 / #455.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 12:09:51 -05:00

6.0 KiB

Hotfix Scratchpad — mosaic yolo <runtime> passes runtime name as initial user message

Objective

Stop mosaic yolo <runtime> from passing the runtime name (claude, codex, etc.) as the initial user message to the underlying CLI. Restore the mission-auto-prompt path for yolo launches.

Root cause (confirmed)

packages/mosaic/src/commands/launch.ts:779 — the yolo <runtime> action handler:

.action((runtime: string, _opts: unknown, cmd: Command) => {
  // ... validate runtime ...
  launchRuntime(runtime as RuntimeName, cmd.args, true);
});

Commander.js includes declared positional arguments in cmd.args. For mosaic yolo claude:

  • runtime (destructured) = "claude"
  • cmd.args = ["claude"] — the same value

launchRuntime treats ["claude"] as excess positional args, and for the claude case that becomes the initial user message. As a secondary consequence, hasMissionNoArgs evaluates false, so the mission-auto-prompt path is bypassed too.

Live reproduction (intercepted claude binary)

$ PATH=/tmp/fake-claude-bin:$PATH mosaic yolo claude
[mosaic] Launching Claude Code in YOLO mode...
argv[1]: --dangerously-skip-permissions
argv[2]: --append-system-prompt
argv[3] (len=25601): # ACTIVE MISSION — HARD GATE ...
argv[4]: claude                                       ← the bug

Non-yolo variant mosaic claude is clean:

argv[1]: --append-system-prompt
argv[2]: <prompt>
argv[3]: Active mission detected: MVP. Read the mission state files and report status.

Plan

  1. Refactor launch.ts: extract registerRuntimeLaunchers(program, handler) with an injectable handler so commander wiring is testable without spawning subprocesses. registerLaunchCommands delegates to it with launchRuntime as the handler.
  2. Fix: in the yolo <runtime> action, pass cmd.args.slice(1) instead of cmd.args.
  3. Add packages/mosaic/src/commands/launch.spec.ts:
    • Failing-first reproducer: parse ['node','x','yolo','claude'] and assert handler receives extraArgs=[] and yolo=true.
    • Regression test: parse ['node','x','claude'] asserts handler receives extraArgs=[] and yolo=false.
    • Excess args: parse ['node','x','yolo','claude','--print','hi'] asserts handler receives extraArgs=['--print','hi'] (with --print kept because allowUnknownOption is true).
    • Excess args non-yolo: parse ['node','x','claude','--print','hi'] asserts extraArgs=['--print','hi'].
    • Reject unknown runtime under yolo.
  4. Run typecheck, lint, format:check, vitest for @mosaicstack/mosaic.
  5. Independent code review (feature-dev:code-reviewer subagent, sonnet tier).
  6. Commit → push → PR via wrappers → merge → CI green → close issue #454.
  7. Release decision (mosaic-v0.0.30) deferred to Jason after merge.

Framework compliance sub-findings (out-of-scope; to capture in OpenBrain after)

  • ~/.config/mosaic/tools/git/issue-create.sh uses eval on $BODY; arbitrary bodies with backticks, $, or parens break catastrophically.
  • gitea_issue_create_api fallback uses curl -fsS without -L; after the mosaicstack/mosaic-stack → mosaicstack/stack rename, the API redirect is not followed and the fallback silently fails.
  • Local repo origin remote still points at old mosaic/mosaic-stack.git slug. Not touched here per git-config safety rule.
  • ~/.config/mosaic/TOOLS.md referenced by the global load order but does not exist on disk.

These will be captured to OpenBrain after the hotfix merges so they don't get lost, and filed as separate tracking items.

Progress checkpoints

  • Branch created (fix/yolo-runtime-initial-arg)
  • Issue #454 opened
  • Scratchpad scaffolded
  • Failing test added (red)
  • Refactor + fix applied
  • Tests green (launch.spec.ts 11/11)
  • Baselines green (typecheck, lint, format:check, vitest — pre-existing uninstall.spec.ts:138 failure on branch main acknowledged, not caused by this change)
  • Code review pass (feature-dev:code-reviewer, sonnet — no blockers)
  • Commit + push (commit 1dd4f59)
  • PR opened (mosaicstack/stack#455)
  • CI queue guard cleared (no pending pipelines pre-push or pre-merge)
  • PR merged (squash merge commit b2cec8c6ba)
  • CI green on main (ci/woodpecker/push/ci + ci/woodpecker/push/publish both success on merge commit)
  • Issue #454 closed
  • Scratchpad final evidence entry

Tests run

  • pnpm --filter @mosaicstack/mosaic run typecheck → green
  • pnpm --filter @mosaicstack/mosaic run lint → green
  • pnpm --filter @mosaicstack/mosaic exec prettier --check "src/**/*.ts" → green
  • pnpm --filter @mosaicstack/mosaic exec vitest run src/commands/launch.spec.ts → 11/11 pass
  • pnpm --filter @mosaicstack/mosaic exec vitest run → 270/271 pass (1 pre-existing uninstall.spec.ts:138 EACCES failure, confirmed on the branch before this change)
  • pnpm typecheck (repo) → green
  • pnpm lint (repo) → green
  • pnpm format:check (repo) → green (after prettier-writing the scratchpad)

Risks / blockers

None expected. Refactor is small and the Commander API is stable. Test needs exitOverride() to prevent process.exit on invalid runtime.

Final verification evidence

  • PR: mosaicstack/stack#455 — state closed, merged.
  • Merge commit: b2cec8c6bac29336a6cdcdb4f19806f7b5fa0054 (squash to main).
  • Post-merge CI (main @ b2cec8c6): ci/woodpecker/push/ci = success, ci/woodpecker/push/publish = success. (ci/woodpecker/tag/publish was last observed as a pre-existing failure on the prior release tag and is unrelated to this change.)
  • Issue mosaicstack/stack#454 closed with a comment linking the merge commit.
  • Launch regression suite: launch.spec.ts 11/11 pass on main.
  • Baselines on main after merge are inherited from the PR CI run.
  • Release decision (mosaicstack/mosaic 0.0.30) intentionally deferred to the user — the fix is now sitting on main awaiting a release cut.