Skip to content

fix(agents): explain why Create/Save is disabled and fix Codex/Claude gate#2050

Merged
wesbillman merged 2 commits into
mainfrom
fix/agent-dialog-disabled-reason
Jul 18, 2026
Merged

fix(agents): explain why Create/Save is disabled and fix Codex/Claude gate#2050
wesbillman merged 2 commits into
mainfrom
fix/agent-dialog-disabled-reason

Conversation

@atishpatel

Copy link
Copy Markdown
Contributor

Problem

Two related defects surfaced in the New/Edit Agent dialog:

  1. Ian — Save permanently disabled when editing a Codex/Claude agent. agentAiConfigurationModeSatisfied always required a provider for a Customize pair, but Codex/Claude intentionally hide the provider picker (they drive their own provider). Save was gated on a value the user could never set.
  2. Mat — Create disabled with no explanation. Selecting Use AI defaults while the global defaults have no provider/model leaves Create greyed out with nothing telling the user why.

Both share a root UX gap: the dialog exposed only a grey button, never the reason.

Fix

  • Provider-aware agentAiConfigurationModeSatisfied (agentAiConfigurationPolicy.ts): new needsProviderSelection param (defaults true, so existing callers are unchanged). The call site passes runtimeSupportsLlmProviderSelection(runtime) — Buzz Agent/Goose still require provider+model; Codex/Claude need only a model.
  • Inline disabled reason (personaSubmitBlock.ts, new pure module): maps the existing canSubmit gate outputs to one deterministic, human-readable reason rendered next to Create/Save. Precedence mirrors the canSubmit term order, so the reason is null exactly when the form can submit (transient Saving/Uploading aside) and advances/disappears as each input is fixed. Incomplete AI defaults name the actual missing pieces (provider/model/credential env keys) and point to Settings → AI defaults — never a generic "configuration incomplete".

No policy logic is duplicated in presentation code: the dialog passes the same gate results it already computes for canSubmit.

Validation

  • desktop unit tests (node --test): 18 pass, 0 fail across personaSubmitBlock.test.mjs (new) and agentAiConfigurationPolicy.test.mjs (extended). Coverage includes: Buzz Agent + defaults missing provider/model shows the reason; Codex custom with model + empty provider is submittable; a valid form has no reason; the reason disappears once the blocking input is corrected; and reason precedence.
  • tsc --noEmit: clean.
  • biome check: clean.

Rust pre-push hooks were skipped on push (change is JS-only); CI runs the full gate.

Remaining UX note

The reason is a single line derived by strict canSubmit-order precedence — it names the first blocking input, not all of them at once. This is intentional (actionable + deterministic) and matches the acceptance criteria, but is worth a look during review.

@atishpatel
atishpatel marked this pull request as ready for review July 17, 2026 23:02
@atishpatel
atishpatel requested a review from a team as a code owner July 17, 2026 23:02
@atishpatel
atishpatel force-pushed the fix/agent-dialog-disabled-reason branch from f9d7521 to 08fdefa Compare July 17, 2026 23:20

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Codex/Claude correction is sound, but the new gate regresses the existing runtime-less legacy-definition path. Please use the already-derived provider-field capability (runtimeCanChooseLlmProvider, which includes blankRuntimeModelProviderEditable) rather than raw runtime support, and add coverage for a runtime-less edit before merge. The branch also needs rebasing onto current main and fresh CI after the fix.

const customAiPairSatisfied = agentAiConfigurationModeSatisfied(
aiConfigurationMode,
{ provider, model },
runtimeSupportsLlmProviderSelection(runtime),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: this conflates "runtime drives its own provider" with "runtime is blank." Existing legacy/builtin definitions can intentionally edit model/provider with no runtime (blankRuntimeModelProviderEditable), and this dialog already exposes the provider field for that case via runtimeCanChooseLlmProvider. Passing raw runtimeSupportsLlmProviderSelection("") === false now makes Customize require only a model, so after choosing No preference (or editing a runtime-less definition) Save can persist provider: undefined despite the visible provider picker. Pass the actual field capability (runtimeCanChooseLlmProvider) here and to the reason input, and add a runtime-less edit regression test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 (posted by Atish's AI agent) Fixed in 0b30d07. The three "provider is required" expressions — providerIsRequired, customAiPairSatisfied, and the submit-block reason input — now key off runtimeCanChooseLlmProvider (the same capability that governs whether the picker is shown) instead of the raw runtimeSupportsLlmProviderSelection(runtime). So a runtime-less legacy/builtin definition (blank runtime + saved model/provider, exposed via blankRuntimeModelProviderEditable) keeps requiring the visible provider, while Codex/Claude stay exempt (picker hidden). Added a runtime-less edit regression e2e (case 11 in global-agent-config-screenshots.spec.ts) that seeds no available runtime so the dialog does not auto-seed a runtime — verified it fails (Save enabled) without the change and passes with it — plus a policy unit test.

@wesbillman

Copy link
Copy Markdown
Collaborator

Code blocker resolved; merge gate still blocked on freshness. The new head correctly uses runtimeCanChooseLlmProvider for all three gates, and the runtime-less edit regression covers the failure mode while preserving Codex/Claude behavior. Current-head CI is green. However, this head is now 5 commits behind current main (79598bd), with overlapping changes in desktop/src/testing/e2eBridge.ts and desktop/tests/helpers/bridge.ts. Please rebase onto current main and rerun CI; I will clear the change request once that rebased head is green.

atishpatel and others added 2 commits July 18, 2026 08:56
… gate

The agent definition dialog only exposed a greyed-out Create/Save button
with no indication of what was incomplete. Two related problems fed this:

1. `agentAiConfigurationModeSatisfied` always required a provider for a
   Customize pair, but Codex/Claude intentionally hide the provider picker
   (they drive their own provider). Editing such an agent left Save
   permanently disabled on a value the user could never set — Ian's
   "Save button stays disabled" regression. The check is now
   provider-aware: Buzz Agent/Goose still require provider+model, while
   Codex/Claude need only a model.

2. Nothing told the user why the button was disabled. A new pure module,
   `personaSubmitBlock`, maps the existing `canSubmit` gate OUTPUTS to a
   single, deterministic reason rendered next to the action. Precedence
   mirrors the `canSubmit` term order, so the reason is `null` exactly when
   the form can submit (transient Saving/Uploading states aside) and
   advances/disappears as the user fixes each input. Incomplete AI defaults
   name the actual missing pieces (provider/model/credential keys) and point
   to Settings → AI defaults; it is never reduced to a generic message.

No gate/policy logic is duplicated in presentation code — the dialog passes
the same gate results it already computes for `canSubmit`.

Tests: focused policy coverage for the provider-aware seam, and full
reason-mapping coverage for the pure module (including the assertion that the
reason disappears once the blocking input is corrected). Playwright coverage
in global-agent-config-screenshots.spec.ts now drives the real dialog footer:
the blocked-create case asserts the visible provider/Settings reason, the
enabled-create case asserts no reason, a CLI-login create case asserts the
hidden provider does not block or explain, and a persona-linked Codex agent is
opened in the actual Edit/Save path to prove the original defect is fixed
(provider picker hidden, Save enabled, no reason). A narrow, test-only mock
bridge extension lets a persona seed pin its runtime/model/provider.

Co-authored-by: Atish Patel <atish@squareup.com>
Signed-off-by: Atish Patel <atish@squareup.com>
Co-authored-by: WorkerBeeGPT <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address wesbillman's blocking review: the Customize provider gate keyed off
runtimeSupportsLlmProviderSelection(runtime), which is false for a blank
runtime. But a runtime-less legacy/builtin definition still exposes the
provider picker via blankRuntimeModelProviderEditable, so Save could persist
`provider: undefined` despite the visible picker (also reachable after picking
"No preference").

Gate the provider requirement on the field-visibility capability
runtimeCanChooseLlmProvider instead — the same predicate that drives whether
the picker is shown — for providerIsRequired, customAiPairSatisfied, and the
submit-block reason. Codex/Claude stay unaffected (picker hidden -> provider
not required), preserving Ian's original fix.

Adds a runtime-less edit regression test (e2e case 11, seeded with no
available runtime so the dialog does not auto-seed a runtime) plus a policy
unit test; verified the e2e fails without the gate change.

Co-authored-by: Atish Patel <atish@squareup.com>
Signed-off-by: Atish Patel <atish@squareup.com>
Co-authored-by: WorkerBeeGPT <c5c455215c2506cb8ba776518cec804af62d3a0526e32d496a22072e395042b9@buzz>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@wesbillman
wesbillman force-pushed the fix/agent-dialog-disabled-reason branch from 0b30d07 to 8a5ad09 Compare July 18, 2026 14:56
@wesbillman
wesbillman enabled auto-merge (squash) July 18, 2026 14:58
@wesbillman
wesbillman merged commit 87dc4dc into main Jul 18, 2026
25 checks passed
@wesbillman
wesbillman deleted the fix/agent-dialog-disabled-reason branch July 18, 2026 15:07
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