Twinned from https://github.com/cfsh/ninefold/issues/812 by tools/beads/import_github.py's reconcile pass.
Prescription, not a design question. Filed by the CTO — a gap in the CTO's own tooling, hit directly on 2026-08-10.
claude supports --model <model> (an alias — fable/opus/sonnet — or a full name like claude-sonnet-5), composes fine with --remote-control and --permission-mode, and is exactly how a CTO would pick which model does a chunk of coding work. tools/office/spawn_dev.py has no way to pass it through: launch_command() (spawn_dev.py:950) hardcodes argv = ["claude", "--remote-control", names.session, "--permission-mode", "auto"] with no model option, and main()'s argparse block (spawn_dev.py:~1692 onward) has no --model flag to plumb one in.
What this cost, concretely: spawning a dev on Sonnet for #795/#728 required the CTO to hand-reproduce the tool's own steps — worktree add -b, the NuGet.Config copy, the identity check, settings.local.json, and (to stay faithful) importing spawn_dev.py's own build_kickoff() rather than retyping the kickoff — solely to insert one flag on the final tmux new-session line. That is exactly the hand-transcription failure this tool exists to prevent (spawn-dev.md: *"this file used to transcribe the commands by hand and that transcription is what broke twice in one day"*), reintroduced through the one gap in the tool's own surface.
Add --model <model> to spawn_dev.py's argparse (main()), and thread it through to launch_command():
--model to claude at all, so the session gets whatever claude's own default resolves to. This must not change any existing test's expected argv.claude invocation — ["claude", "--model", <value>, "--remote-control", ...] or equivalent ordering; claude --help doesn't document that order matters, but keep --model immediately after claude for readability, matching how this was done by hand on 2026-08-10.claude itself will refuse a bad one, and a hardcoded allowlist here is exactly the kind of fact that goes stale the day a new model ships. Pass the string through.--resume too. A resumed session's claude -c can still take --model to switch what a re-standed session runs on — there's no reason to refuse it there, and refusing would be an arbitrary asymmetry the flag's own callers would trip over.--dry-run must print the flag when given. This tool has a tested invariant that the dry-run transcript matches what a real spawn issues (test_the_dry_run_fetch_matches_the_one_the_spawn_really_issues already pins this for the fetch step) — the printed tmux new-session … line and the launch_command() output must agree, so update whichever print statement constructs that transcript (spawn_dev.py, the --dry-run branch around the spawn() function) alongside launch_command() itself, not one without the other.Once shipped, the CTO's board rows for hand-spawned sessions (e.g. #795/#728's nf-dev-merge-reset-guard, which currently carries a *"Spawned by hand, not via spawn_dev.py"* caveat) can go back through the normal tool. That's a nice-to-have signal this issue exists, not a requirement of it.
Extend tools/tests/test_spawn_dev.py (or wherever launch_command()'s existing tests live):
--model passed → argv/the joined command has no --model token, unchanged from today (regression guard, since this must not be a breaking change for every existing caller).--model sonnet passed → the launch command contains --model sonnet in the claude invocation, correctly shlex-quoted alongside everything else (reuse whatever the existing kickoff-quoting tests already exercise for apostrophes etc. — a model string won't need escaping, but don't special-case it either).--model composes with --resume → present in the resume launch command too.--dry-run output matches the real launch_command() result when --model is passed — this is the fidelity invariant above; assert the two are the same, not just that each independently "looks right".None.
No comments.