Skip to content

fix(coding-agent): show current role bindings in the model assignment menu - #3718

Merged
Yeachan-Heo merged 1 commit into
Yeachan-Heo:devfrom
yazzang-homelab:fix/model-role-menu-current-binding
Aug 1, 2026
Merged

fix(coding-agent): show current role bindings in the model assignment menu#3718
Yeachan-Heo merged 1 commit into
Yeachan-Heo:devfrom
yazzang-homelab:fix/model-role-menu-current-binding

Conversation

@yazzang-homelab

@yazzang-homelab yazzang-homelab commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Problem

After selecting a model, the TUI assignment menu showed only role names. With 800+ available models, finding the model currently assigned to a role required scanning role badges throughout the model list, making reassignment unnecessarily blind.

Change

Each role row now shows its current binding inline:

Set as DEFAULT (Default) — now: anthropic/claude-sonnet-4-5 (low)
Set as EXECUTOR (Executor) — now: anthropic/claude-haiku-4-5
Set as ARCHITECT (Architect) — now: inherits default
Set as PLANNER (Planner) — now: retired-provider/retired-model (unavailable)

The menu distinguishes resolved bindings, unset defaults, effective defaults supplied by an active profile, scalar and qualified pi/default inheritance aliases, literal model IDs named default, fallback chains, and unavailable selectors. Resolved and unavailable provider/model text is sanitized and width-bounded before rendering.

Catalog and role refreshes now share a staged materialization boundary. Provider refresh failures preserve the last coherent view, show the relevant provider/catalog diagnostics, and avoid stale rows, roles, tabs, or ranking. Presentation failures roll back to the last coherent view.

Verification

Frozen against current dev:

  • Base: a8757cb22aea36ee7be10b14306dfdb479d1beff

  • Head: 04d7dd2f99d365ffbc85b284fa9302c79133d249

  • Diff hash: sha256:25ab42cfae1841e100f655d5e539177bf4d397aad18e14bd54ba60a37f91dc5b

  • bun test packages/coding-agent/test/model-selector-action-menu-role-binding.test.ts — 15 passed

  • Focused selector regression suites — 66 passed

  • bun --cwd=packages/coding-agent run check — passed

  • git diff --check — passed

  • Bounded PTY red-team harness — 15/15 passed; artifacts: artifacts/pr3718-qa-report.json, artifacts/pr3718-qa-pty.txt

  • Exact-head PR checks — all completed checks passed or were skipped

@yazzang-homelab
yazzang-homelab force-pushed the fix/model-role-menu-current-binding branch from 736d414 to 511ff38 Compare August 1, 2026 18:59
@yazzang-homelab

Copy link
Copy Markdown
Contributor Author

Updated after review: three defects found by the boundary review lanes are fixed in the pushed revision.

  • pi/default / default alias misreported as a concrete model. The alias resolves to the default's model, so the row rendered a model id and hid the inheritance. #formatRoleBinding now classifies inheritance aliases from the configured value before any resolved lookup, for non-default roles only (the default role never claims to inherit itself).
  • Stale (unavailable). #roles only holds bindings that resolved when the catalog was last loaded, so a provider refreshed afterwards kept reporting its models as unavailable. When #roles[role] is empty the label is now re-resolved against the live registry at render time.
  • Unsanitized selector echoed into the TUI. Configured selectors are permissively validated, so the raw value could carry CSI/OSC control bytes and newlines into the menu. It is now sanitizeText-cleaned, collapsed to one line, and width-bounded (truncateToWidth, cap 48).

Tests grew to four cases: resolved bindings, unset/inherited/unresolvable states, both alias forms with a resolvable default, and a hostile ANSI/multi-line selector asserting no escape survives and the row stays bounded.

bun test packages/coding-agent/test/model-selector-action-menu-role-binding.test.ts   # 4 pass
bun test packages/coding-agent/test/model-selector-*.test.ts                          # 66 pass
bun --cwd=packages/coding-agent run check                                             # clean

@yazzang-homelab
yazzang-homelab force-pushed the fix/model-role-menu-current-binding branch from 511ff38 to ec05468 Compare August 1, 2026 19:10
@Yeachan-Heo

Copy link
Copy Markdown
Owner

Signed: Yeachan-Heo / hostile-only batch reassignment review

VERDICT: REQUEST_CHANGES

Reviewed exact head a993a903eaf437bfa1a9cb3708541bc8ca9d91e7 only. The PR is based on stale c36376844a20e7e83f72da8bd29c3f09db4d0ad6; current dev is a8757cb22aea36ee7be10b14306dfdb479d1beff after #3719 and #3694. The currently reported green CI is therefore not exact-head CI against current dev. Rebase this PR onto current dev and provide a fresh terminal exact-head CI result before approval or merge.

Concrete hostile finding: the new assignment-menu path sanitizes and bounds only the unresolved-selector branch. When a configured selector resolves, #formatRoleBinding() calls #formatAssignedModelLabel(), which interpolates model.provider and model.id without sanitizeText() or a width bound. Provider/model IDs are user/config/discovery-controlled strings, so a malicious custom model such as an ANSI/OSC-bearing or extremely long ID can inject terminal control sequences or expand the role-action row. This is newly exposed in the assignment menu by this PR, even though the same helper had existing preview uses. Sanitize and bound the resolved-model label as well, and add a regression covering control sequences and an oversized resolved provider/model ID.

The existing tests cover alias classification, stale/unavailable bindings, refresh rollback, and hostile unresolved selectors, but do not cover hostile resolved model metadata. Do not treat the stale-base CI as sufficient evidence for merge safety.

No branch mutation or merge performed. #3694 was not touched.


[repo owner's gaebal-gajae (clawdbot) 🦞]

… menu

After picking a model the assignment menu listed bare role rows ("Set as EXECUTOR (Executor)"), so the model each role is bound to was invisible; the only way to learn it was to scan the whole 800+ entry model list for role badges. The menu now renders each role's current binding inline.

Lore-id: 7c41ab9e
Constraint: keep one #roles snapshot for labels, badges, and ranking, rebuilt at catalog boundaries
Constraint: literal pi/default inherits; literal default remains a valid bare model selector
Constraint: resolved and unavailable selector text must be sanitized and width-bounded before TUI rendering
Constraint: failed refresh recovery must preserve the last coherent catalog and surface both errors
Constraint: provider refreshes stage strict catalog loading before committing rows, roles, and tabs
Constraint: presentation failures roll back to the last coherent catalog view before surfacing errors
Constraint: role-refresh diagnostics are assigned before rollback rendering
Rejected: separate role-overview screen | adds a step to the flow the user is already in
Rejected: badges only on model rows | that is the status quo that fails at 800+ models
Confidence: high
Scope-risk: narrow
Reversibility: easy
Tested: resolved/unset/effective-default/inherited/qualified-alias/unavailable, fallback chains, catalog arrival/removal, provider/offline refresh recovery, presentation rollback, hostile resolved and unresolved metadata
Not-tested: live provider failure against a production discovery service
@yazzang-homelab
yazzang-homelab force-pushed the fix/model-role-menu-current-binding branch from a993a90 to 04d7dd2 Compare August 1, 2026 22:14
@Yeachan-Heo

Copy link
Copy Markdown
Owner

Signed: Yeachan-Heo / hostile-only final-head re-review

VERDICT: MERGE_READY

Reviewed exact final head 04d7dd2f99d365ffbc85b284fa9302c79133d249 against current dev/base a8757cb22aea36ee7be10b14306dfdb479d1beff. The stale-base blocker is resolved: the PR is now based on current dev. GitHub reports CLEAN / MERGEABLE.

The prior hostile finding is fixed precisely. #formatAssignedModelLabel() now sanitizes the combined resolved provider/id text, normalizes whitespace, and applies the same ROLE_BINDING_MAX_WIDTH bound before the assignment row renders it. The new regression injects OSC control data, newlines, and an oversized resolved provider/model ID and verifies that control sequences are absent and the rendered row remains bounded. The unresolved-selector path remains sanitized and bounded as well.

Adversarial scope checks passed:

  • Resolved, unresolved, unset, inherited, literal-default, alias-headed fallback, and effective-profile bindings retain their intended distinctions.
  • Catalog arrival/removal and provider/offline refresh failures re-resolve role bindings without replacing the last coherent view on failed recovery.
  • Presentation failures roll back roles, catalog rows, provider tabs, selection state, and filtered state before surfacing diagnostics.
  • The final diff contains only the intended selector implementation, changelog, and focused regression coverage; no unrelated product or generated-surface drift was found.

Exact Dev CI is terminal green, including affected-path TypeScript validation and state gates (run 30720832055); Public site sync 30720832065 is also terminal successful. No merge performed.


[repo owner's gaebal-gajae (clawdbot) 🦞]

@Yeachan-Heo
Yeachan-Heo merged commit c1bf3be into Yeachan-Heo:dev Aug 1, 2026
21 checks passed
@Yeachan-Heo

Copy link
Copy Markdown
Owner

This item is being moved to the Tier 3 review lane. Further review is deferred until after the current emergency stabilization period.


[repo owner's gaebal-gajae (clawdbot) 🦞]

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.

2 participants