Skip to content

[Bug]: harnesscli --tui -model X silently ignores the flag #1426

Description

@dennisonbertram

Work type

Bug / regression

Observed behavior

harnesscli --tui -model X silently ignores -model. The TUI starts on whatever the daemon defaults to (or, since #1424, on the remembered model), and nothing tells the user their flag was discarded.

Silent is the operative word: no warning, no error, no note in the status bar. The flag parses, validates, and is thrown away.

Found while building #1424. Recorded in docs/logs/engineering-log.md under the 2026-09-08 #1424 entry as a known gap.

Expected behavior

harnesscli --tui -model X starts the TUI on model X.

Invariant: an explicitly requested model outranks every other source. Precedence, highest first: the -model flag, then the model remembered from the last session (#1424), then the daemon default.

A flag is a one-off instruction, not a preference: -model X must not overwrite the stored last-used model. If the user then picks a different model with /model inside that session, that selection is a choice and is persisted as usual.

Reproduction

  1. harnesscli --tui -model gpt-4.1-mini (or via a go-code TUI launch).
  2. Look at the status-bar model.

Actual: the model is the daemon default, or the remembered model from a previous session. The requested model is absent, with no diagnostic.

Reproduction rate: 100%.

User and operational impact

Affected users: anyone scripting or aliasing a TUI launch against a specific model, and anyone trying a one-off model without disturbing their saved preference.

Severity: moderate on its own — the workaround is /model after launch. It is sharper now than before #1424, because the TUI has a persisted model to fall back to, so the flag is not merely ignored, it is overridden by a stale preference. A user who passes -model to escape their remembered model gets exactly the thing they were trying to override.

The worst part is the silence. A flag that errors teaches; a flag that is quietly dropped trains people to distrust the CLI.

Data/security: none.

Workaround: launch the TUI and run /model.

Suspected seam and search evidence

Owning code: cmd/harnesscli/main.go.

  • main.go:149flags.String("model", "", "model override for this run"). Parsed.
  • main.go:179runTUI(*baseURL, workspacePath, *resume, *planMode). *model is not among the arguments, so it cannot reach the TUI. This is the defect.
  • main.go:574func runTUI(baseURL, workspace, resumeConversationID string, planMode bool).
  • main.go:538newTUIConfig(baseURL, workspace, resumeConversationID) builds tui.TUIConfig and never sets Model.
  • main.go:216 — the non-TUI path passes Model: *model into runCreateRequest, so the flag works correctly there. The bug is confined to the TUI branch.

Precedent for the fix: planMode reaches the TUI as an explicit runTUI parameter, then tuiCfg.PlanMode = planMode (main.go:579). The same route is available for the model.

Consumer already in place: cmd/harnesscli/tui/model.go sets selectedModel: cfg.Model at construction, and #1424's restore is guarded on cfg.Model == "", so a non-empty TUIConfig.Model already wins over the remembered value. TestExplicitModelBeatsRememberedModel pins that precedence today with no live producer of a non-empty cfg.Model — this issue supplies the producer.

Commands used: grep -n "runTUI(" cmd/harnesscli/main.go; grep -n "func newTUIConfig\|newTUIConfig(" cmd/harnesscli/*.go; sed -n 170,185p and 574,596p of main.go.

Evidence that rules out adjacent seams: the TUI already honors TUIConfig.Model (proven by TestExplicitModelBeatsRememberedModel and by selectedModel: cfg.Model at tui/model.go:501), and the non-TUI path already honors -model. Nothing needs to change in either; only the wiring between the flag and TUIConfig is missing.

Blast-radius impact map

Callers and data flow: main.go only — the runTUI call, its signature, and newTUIConfig. No TUI, server, protocol, or provider change.

Config/env/defaults: none. -model keeps its "" default, so "empty means no override" is unchanged and the precedence guard needs no edit.

API/CLI/wire formats/tools: -model begins doing what its help text already claims. No new flag, no changed help semantics.

Persistence/schema/cache: none, and deliberately so — the flag must not write to ~/.config/harnesscli/config.json. A one-off override that silently rewrote the saved preference would be a worse bug than the one being fixed.

Concurrency/lifecycle: none.

Security/auth/permissions/privacy: none. A model ID is not a secret.

TUI/web/macOS/other clients: TUI only. macapp builds its own config; the non-TUI streaming path is untouched.

Provider/model/tool catalog: a flag may name a model that does not exist or whose provider has no key. The TUI already handles an unavailable model with the #1404 redirect and explanation; that path should be exercised rather than assumed, and must not become a hard startup failure.

Deployment/observability/runbooks: website/docs/cli/ if it documents --tui flags.

Compatibility: strictly additive in effect. Anyone currently passing -model with --tui is having it ignored, so no working behavior changes — only a broken one starts working. Someone relying on the flag being ignored is relying on a bug.

Existing tests/fixtures: cmd/harnesscli/main_test.go calls newTUIConfig at four sites (:187, :203, :331, :342). If the model becomes a parameter rather than a post-hoc field assignment, those four calls need updating — worth it for a directly testable seam, since runTUI itself needs a terminal and cannot be unit tested.

Documentation: docs/logs/engineering-log.md, and the #1424 entry's "noted, not fixed" line should be updated so the log does not keep advertising a gap that has been closed.

Regression test first

cmd/harnesscli/main_test.go:

  1. TestNewTUIConfigCarriesExplicitModelnewTUIConfig with a model returns a TUIConfig whose Model is that value. Red today: the function neither accepts nor sets it.
  2. TestNewTUIConfigWithoutModelLeavesItEmpty — the control. An empty model must stay empty, so the remembered model and the daemon default still apply. Without this, a fix that defaulted to something would pass test 1 and break feat(tui): remember the last used model across restarts #1424.

Red command: go test ./cmd/harnesscli -run TestNewTUIConfig -v.

Expected failure: a compile error (no such parameter) followed, once wired, by Model = "" want "gpt-4.1-mini" if the value is dropped.

Why that proves the bug: newTUIConfig is the exact point where the flag's value must become TUIConfig.Model. runTUI cannot be unit tested — it requires a terminal — so this is the closest honest seam.

False-positive controls: keep TestExplicitModelBeatsRememberedModel (in tui) green, which proves the value is not merely stored but actually outranks the remembered model. Together the two tests cover producer and consumer; neither alone proves the flag works.

Fix boundaries

In scope:

Out of scope:

Diagnostic and observability evidence

Before: launch with -model X, and the status bar shows the daemon default or the remembered model. No message anywhere mentions X.

After: the status bar shows X, and ~/.config/harnesscli/config.json is unchanged — the second half matters as much as the first, since a flag that rewrites the saved preference would be a regression against #1424.

Verification plan

  • Red: go test ./cmd/harnesscli -run TestNewTUIConfig -v before the change.
  • Green: same after.
  • Targeted: go test ./cmd/harnesscli/... -race.
  • Full regression: go test ./cmd/... ./internal/....
  • Real path, required, and it has two halves:
    1. With a model already remembered from a previous session, launch harnesscli --tui -model <different-model> and confirm the status bar shows the flag's model, not the remembered one.
    2. Quit and confirm ~/.config/harnesscli/config.json still holds the previously remembered model — proving a one-off override did not overwrite the preference.
  • Adjacent: confirm the non-TUI -model path still works, and that launching with no -model still resumes the remembered model (feat(tui): remember the last used model across restarts #1424 unbroken).

Rollout and rollback

Single PR to main; picked up on the next scripts/install.sh or Homebrew --HEAD. No migration or persisted-state change. Rollback is reverting the commit, which restores the flag being ignored.

Rollback trigger: -model overwriting the stored preference, or a bad model ID turning into a hard startup failure instead of the existing redirect.

Documentation and handoff

  • docs/logs/engineering-log.md: new entry, and amend the feat(tui): remember the last used model across restarts #1424 entry's "noted, not fixed" line so the log stops advertising a closed gap.
  • website/docs/cli/: if --tui flags are documented, state the precedence — flag, then remembered, then daemon default.

Definition of done

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions