Skip to content

[fix] Stop asking for a provider key the vault already holds [AGE-4289] - #6677

Closed
mmabrouk wants to merge 5 commits into
release/v0.115.3from
fix/release-1153-provider-key-banner
Closed

[fix] Stop asking for a provider key the vault already holds [AGE-4289]#6677
mmabrouk wants to merge 5 commits into
release/v0.115.3from
fix/release-1153-provider-key-banner

Conversation

@mmabrouk

@mmabrouk mmabrouk commented Sep 8, 2026

Copy link
Copy Markdown
Member

Context

Issue: #6660

The playground keeps asking for a model provider key after one has been added, while the agent runs on that same key. Two independent surfaces say it, and they were wrong for different reasons.

The Model section, reproduced on staging. Fresh project, Settings, AI providers, Add provider, OpenAI, paste a working key, Test (133 models fetched), Done. The provider row lists the key. Back in the playground the Model section still shows the amber Connect key badge and its "Connect the model's provider key to run this agent." tooltip, across reloads, while the run reaches OpenAI. Vault secrets are write-only on staging and on dev stacks, so /secrets/ answers with no value at all:

{"kind":"provider_key","data":{"kind":"openai","provider":{}},
 "write_only":true,"value_status":{"configured":true,"preview":"sk-****AAA"}}

Presence rides on value_status.configured, which the transform surfaces as hasKey, and hasStoredKey is the one rule that reads it. The section tested !providerVaultEntry.key instead, which is true for every write-only row, so a connected project read as keyless forever.

The composer banner is a project-wide count of runnable model routes. Two of its inputs could be read as a definitive "nothing is runnable" when they had established no such thing, and each of those raises the add-a-key message on a claim we never made.

Changes

The provider-key prompt asks hasStoredKey:

Before:  vaultLoaded && !!providerVaultEntry && !providerVaultEntry.key
After:   vaultLoaded && !!standardProviderEntry && !hasStoredKey(standardProviderEntry)

It lives in shouldPromptForProviderKey (providerKeyPrompt.ts) with its exemptions unchanged. A self_managed connection signs itself in, and a named agenta connection points at one vault record that this rule never looks up, so a missing standard key for the family says nothing about it. vaultLoaded still gates everything, so nothing prompts while the vault query is pending.

An empty harness catalog is no longer a catalog. Every route is built by asking a harness what it supports, so an empty capability map answers "nothing is runnable" for a project whose vault holds working keys. fetchHarnessCapabilities built that map from any 200 that carried no harnesses, and the catalog query persists to IndexedDB, so one bad answer outlived reloads. It now rejects instead. A copy an older build already wrote to disk needs three more guards, because it keeps data defined and therefore looked like a catalog downstream: candidate resolution treats it as unresolved, the capability atom and the candidate atom surface the refetch error instead of swallowing it (which brings back the catalog-unavailable notice and its Retry), and the imperative loader forces one refetch.

An unknown subscription source no longer reads as "no subscriptions." fetchSubscriptionStatus returns null for an answer that fails the boundary schema, and the service answers incompatible for a runner whose shape it could not read. Both were counted as absence. The state now carries subscriptionUnknown and stays ready, so the routes we do know about keep working: agent creation, the model picker and the slash commands all key off status, and turning this into an error blocked creation outright. Only the reading of an EMPTY list changes. connectModelGate stands down when the source is unknown, which is the one place the claim was being made. A check that could not be made travels the same way, so the two unknowns no longer behave differently. One classifier decides all of this, and the imperative loader uses it to refetch such an answer once, because ensureQueryData serves whatever is cached and a retry after the runner recovered made no request at all.

unavailable deliberately stays a true negative, and this one is a judgement call. The service also answers it for a deployment with no runner configured, which is the common self-hosted case, so reading it as unknown would silence the add-a-key prompt for exactly the users who need it. With no vault key and no reachable runner nothing is runnable, and adding a provider key is the remedy, so the message is still correct advice there. Giving the service the vocabulary to tell a confirmed absence from a failed check belongs in its own change.

One comment in the vault persister claimed !!secret.key was the presence rule. It is not, and it now points at hasStoredKey.

Tests

  • packages/agenta-entity-ui/tests/unit/providerKeyPrompt.test.ts covers the row shapes the vault really serves: unconnected, write-only connected, readable connected, both restored from IndexedDB, and a record that says the key is gone while a stale value lingers. It fails against the old rule.
  • packages/agenta-entities/tests/unit/agent-model-candidate-sources.test.ts gains the empty catalog, a failure beside a cached empty map, an unreadable subscription answer, a runner reported incompatible, a runner reported unavailable, and a connected runner reporting unsupported for a harness.
  • packages/agenta-entities/tests/unit/harness-catalog-recovery.test.ts drives the real query cache: a cached empty map and a cached unreadable subscription answer are both refetched and recover, their failures are reported, and usable cached values are still served without a request.
  • web/oss/src/components/AgentChatSlice/hooks/useAgentModelKeyStatus.test.ts pins the gate standing down on an unknown subscription source.
  • pnpm --filter @agenta/entities exec vitest run tests/unit: 1614 passed. @agenta/entity-ui: 695. @agenta/chat: 894. @agenta/playground: 288.
  • Types, pnpm lint-fix and pnpm run format: clean.
  • Four Codex reviews at xhigh. Their prioritized items are in, except two pre-existing picker items and the unavailable split above, all noted in the comments.

Browser verification

Before, on staging v0.115.3, with a real OpenAI key: the Connect key badge stands over a working key across reloads. After, on this PR's preview, same vault row shape:

key present -> bannerInstances 2, bannerVisible 0, connectKeyBadges 0
key deleted -> bannerInstances 2, bannerVisible 2, connectKeyBadges 1

Screenshots and the probe are on the dev box under ~/agenta-qa-evidence/2026-09-08-issue-6660/. The playground mounts the banner more than once, so a first-match DOM probe reports either answer at random; the probe counts visible instances.

What to QA

  • Fresh project. Settings, AI providers, add an OpenAI key, Test, Done. Open an agent. The Model row shows the model with no Connect key badge, and no "Connect the model's provider key" tooltip on the section header.
  • Reload the playground. The badge stays away, and the composer shows no Add your model provider key to run this agent. line.
  • Create a brand new blank agent draft in a project whose subscription status cannot be read. The draft is created, rather than failing with a retry prompt.
  • Regression: a project with no provider key at all. Both messages come back and the composer is disabled.
  • Regression: an agent on a self-managed connection, and one pointed at a named connection. Neither asks for a key.
  • Regression: the model picker still lists models, since the harness catalog now fails loudly rather than resolving to nothing.

The Model section's "Connect key" badge and its "Connect the model's
provider key to run this agent." tooltip read presence off the vault
row's value. A write-only record never returns its value; it reports
presence through hasKey instead. So on every write-only deployment the
playground kept asking for a key while the agent ran on that same key.

The presence check now goes through hasStoredKey, the one vault presence
rule, and the whole gate moves into agentProviderNeedsKey so it cannot
drift back.
@linear-code

linear-code Bot commented Sep 8, 2026

Copy link
Copy Markdown

AGE-4289

@mmabrouk

mmabrouk commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@vercel

vercel Bot commented Sep 8, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
agenta-documentation Ready Ready Preview Sep 8, 2026 7:53pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

📝 Summary

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of unavailable or empty model-provider catalogs, including clearer loading and error states.
    • Automatically retries catalog loading when previously saved catalog data is unusable.
    • Reports an error when no model providers are available instead of showing an empty catalog.
    • Prevented incorrect provider-key prompts for self-managed, named, write-only, or unresolved connections.
    • Correctly recognizes restored provider-key presence without relying on displayed key values.
    • Treats unreadable subscription status responses as errors while preserving available vault-backed models.

Walkthrough

The change treats empty harness catalogs as unusable, refetches unusable cached catalogs, preserves loading and error states, and centralizes provider-key prompt decisions around stored-key presence and connection state.

Changes

Harness catalog validation and recovery

Layer / File(s) Summary
Catalog usability contract
web/packages/agenta-entities/src/workflow/api/api.ts, web/packages/agenta-entities/src/workflow/state/inspectMeta.ts
Empty harness catalogs now raise an API error and resolve to null in state selectors.
Candidate loading and recovery
web/packages/agenta-entities/src/workflow/state/agentModelCandidates.ts, web/packages/agenta-entities/tests/unit/agent-model-candidate-sources.test.ts, web/packages/agenta-entities/tests/unit/harness-catalog-recovery.test.ts
Candidate resolution treats empty catalogs as unresolved, surfaces unreadable subscription errors, and refetches unusable cached catalogs. Tests cover recovery, failure, usable-cache, and subscription-status paths.

Provider-key prompt handling

Layer / File(s) Summary
Shared provider-key decision
web/packages/agenta-entities/src/secret/state/persistence.ts, web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/providerKeyPrompt.ts, web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx, web/packages/agenta-entity-ui/tests/unit/providerKeyPrompt.test.ts
Provider-key prompting now uses hasStoredKey, vault state, provider catalog presence, and connection mode. Tests cover write-only, restored, stale, named, and self-managed records.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Severity of issue fixed: Low

Merge Risk: 🔵 Low · up to 1ccdc

Provider-key prompting and catalog recovery are covered for stored keys, unreadable subscriptions, and empty cached catalogs. An empty catalog returned during a cold load may still trigger an extra request, and the recovery test setup needs lifecycle-rule alignment before merge.

Sequence Diagram(s)

sequenceDiagram
  participant CandidateLoader
  participant QueryClient
  participant HarnessCatalogAPI
  CandidateLoader->>QueryClient: inspect cached catalog
  QueryClient-->>CandidateLoader: return empty or usable catalog
  CandidateLoader->>HarnessCatalogAPI: refetch when catalog is unusable
  HarnessCatalogAPI-->>QueryClient: store catalog result or error
  QueryClient-->>CandidateLoader: return loading, error, or usable catalog
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary change: preventing prompts for provider keys already stored in the vault. It is concise and specific.
Description check ✅ Passed The description is detailed and directly explains the provider-key fix, catalog recovery changes, subscription handling, tests, and QA results.
Docstring Coverage ✅ Passed Docstring coverage is 66.67% which is sufficient. The required threshold is 60.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 9 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/release-1153-provider-key-banner

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

…pty catalog

Every model route is built by asking a harness what it supports, so an empty
capability map answers "nothing is runnable" for a project whose vault holds
working keys. The connect-a-model gate then tells the user to add a provider
key they already added, and it keeps telling them across reloads because the
catalog query persists to IndexedDB.

fetchHarnessCapabilities built that empty map from any 200 that carried no
harnesses. It now rejects instead, so the query reports the failure and the
persister stores nothing. Candidate resolution also treats an empty map as an
unresolved source, so a copy cached by an older build cannot activate the gate
either.

Also applies the Codex review of the first commit: the prompt rule is named
shouldPromptForProviderKey in providerKeyPrompt.ts, takes the normalized
ConnectionMode, and its comments no longer claim more than it proves.
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Railway Preview Environment

Preview URL https://gateway-pr-6677.up.railway.app/w
Project agenta-oss-clone-spike
Image tag pr-6677-9a98e5e
Status Deployed
Railway logs Open logs
Workflow logs View workflow run
Updated at 2026-09-08T20:04:05.915Z

@mmabrouk

mmabrouk commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

Browser verification, before and after.

Before, on staging v0.115.3 (https://staging.preview.agenta.dev, fresh project, the issue's own steps). Added a working OpenAI key through Settings, AI providers. Test reported 133 models fetched, Done stored it, and the provider row listed sk-****AAA with Defaults active models. The playground's Model row then showed Pi · GPT-5.6 Luna with the amber Connect key badge next to it, and it survived a reload. The turn that followed reached OpenAI and came back with a billing error, so the key was plainly in use.

After, on this PR's preview (https://gateway-pr-6677.up.railway.app). Same vault row shape, confirmed on the wire:

{"kind":"provider_key","data":{"kind":"openai","provider":{},"harnesses":["pi_core"]},
 "write_only":true,"configured":true}

The Model row reads Pi · GPT-5.6 Luna with no badge, and the composer shows no add-a-key line. Counted in the DOM after a reload: {"connectKeyBadges":0,"bannerHeights":"46,46,0,0"}, where a zero height is the collapsed banner.

Regression, same preview project. Deleted the only secret and reloaded. Both messages come back and the composer is disabled again: {"connectKeyBadges":1,"bannerHeights":"46,46,54,54"}.

Screenshots are on the dev box under ~/agenta-qa-evidence/2026-09-08-issue-6660/.

The empty-catalog half of the second commit is covered by unit tests only. Forcing a 200 that carries no harnesses needs request interception, which the QA browser cannot do against a deployed stack.

@mmabrouk

mmabrouk commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Rejecting the empty map at the API boundary stops new ones, but a copy an
older build already wrote to IndexedDB keeps `data` defined, so the refetch
error was swallowed and the playground sat in loading with no notice and no
retry. The capability atom and the candidate atom now both read an empty map
as no catalog, which surfaces the error and brings back the retry.

The imperative loader forces one refetch when the cache holds an unusable
map. `ensureQueryData` serves whatever is cached, so a retry after the server
recovered made no request at all.

From the Codex review of the previous commit.
@mmabrouk

mmabrouk commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

Two Codex reviews at xhigh, one per fix. Both verdicts and what I did with them.

On the provider-key prompt. "The presence fix is correct, and I would ship it as a Model-badge fix." It asked for three changes, all applied: the helper is shouldPromptForProviderKey in providerKeyPrompt.ts and takes the normalized ConnectionMode; its comments no longer claim the named-connection exemption means the vault is not the credential source (it is, this rule just never looks it up); and the tests gained a record that says the key is gone while a stale value lingers, which pins the rule against a future hasKey || key. It also corrected two of my claims: the sweep for raw-value presence checks misses a static pre-migration copy in Storybook, which is not live code, and my "restored from disk" fixture invented a sentinel on a write-only row, which redaction never adds because there was no value to replace.

On the empty catalog. "Keep both guards, but don't ship unchanged." It reproduced, against the real query cache and the pinned persister, that rejecting the empty map at the boundary does not recover a copy an older build already persisted: the error was swallowed while data stayed defined, so the playground sat in loading with no notice and no retry, and the imperative loader kept serving the cached map without ever contacting a healthy server. Both are fixed in the last commit, with a test that fails without it.

It also answered the design questions. Throwing belongs at the API boundary, because this endpoint is built from the shipped harness table and is never legitimately empty. Counting harness keys is the right emptiness test, and demanding a non-empty provider list would be wrong, since the schema permits a harness that genuinely reaches nothing. No request loop: consumers share one query key, focus refetching is off, and the imperative path disables retries.

Two things it raised that I did not take, both stated in the PR body: dropping an invalid entry at restore time, and the subscriptionStatus === null contract.

@mmabrouk

mmabrouk commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 4 seconds.

…t none

`fetchSubscriptionStatus` returns null when the runner's answer fails the
boundary schema. Candidate resolution read that as "this deployment has no
subscriptions", so a project with no vault key got the add-a-key banner on a
claim we never established. The subscription card already calls the same null
a failed check.

Unknown now joins the request we could not make: reported only where it
changes the answer, which stands the gate down instead of asking for a key.
Two tests said null meant "none"; they now spell a genuine none as the runner
answering not_configured for every harness.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Advanced

Run ID: f87e7c4b-59d6-4288-94ae-99a51f9b73b2

📥 Commits

Reviewing files that changed from the base of the PR and between f84fa7b and c51fa82.

📒 Files selected for processing (9)
  • web/packages/agenta-entities/src/secret/state/persistence.ts
  • web/packages/agenta-entities/src/workflow/api/api.ts
  • web/packages/agenta-entities/src/workflow/state/agentModelCandidates.ts
  • web/packages/agenta-entities/src/workflow/state/inspectMeta.ts
  • web/packages/agenta-entities/tests/unit/agent-model-candidate-sources.test.ts
  • web/packages/agenta-entities/tests/unit/harness-catalog-recovery.test.ts
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/providerKeyPrompt.ts
  • web/packages/agenta-entity-ui/src/DrillInView/SchemaControls/agentTemplate/useModelHarness.tsx
  • web/packages/agenta-entity-ui/tests/unit/providerKeyPrompt.test.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 0 remain after this review.

let client: QueryClient

beforeEach(() => {
client = new QueryClient({defaultOptions: {queries: {retry: false}}})

@coderabbitai coderabbitai Bot Sep 8, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🤖 get_repo_knowledge executed:

get_repo_knowledge Agenta-AI/agenta /tmp/coderabbit-repo-knowledge/agenta-ai-agenta-4b53879a/conventions /tmp/coderabbit-repo-knowledge/agenta-ai-agenta-4b53879a/learnings

Length of output: 44334


🏁 Script executed:

#!/bin/bash
set -eu
file="web/packages/agenta-entities/tests/unit/harness-catalog-recovery.test.ts"
printf '%s\n' '--- file context ---'
cat -n "$file" | sed -n '1,90p'
printf '%s\n' '--- nearby QueryClient usage in the same test scope ---'
rg -n -C 3 'QueryClient|queryClient|renderHook|wrapper' web/packages/agenta-entities/tests web/packages/agenta-entities/src 2>/dev/null | head -240

Repository: Agenta-AI/agenta

Length of output: 28602


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- query-client helpers and test setup candidates ---'
rg -n -C 4 'getHostQueryClient|createQueryClient|queryClientAtom|QueryClientProvider|new QueryClient' \
  web/packages/agenta-shared web/packages/agenta-entities/tests web/packages/agenta-entities/src \
  -g '*.{ts,tsx}' 2>/dev/null | head -320
printf '%s\n' '--- package test helper files ---'
find web/packages/agenta-entities/tests web/packages/agenta-shared -maxdepth 4 -type f \
  \( -iname '*setup*' -o -iname '*helper*' -o -iname '*query*' \) -print | sort | head -160

Repository: Agenta-AI/agenta

Length of output: 32229


Use the shared host query client instead of constructing new QueryClient(...) in this test. The web convention prohibits direct QueryClient construction.

Source: Coding guidelines

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Keeping the local client here, deliberately.

The contract in web/AGENTS.md exists so that package code never addresses an orphan cache: whoever hosts must hydrate queryClientAtom with the same object that package code will reach through getHostQueryClient(). This test is the host, and it does exactly that: it creates one client per test and sets it on queryClientAtom, so loadAgentModelCandidates resolves that client and the assertions are about the cache it really used. The invariant holds.

Reaching for the process-wide singleton instead would share one cache across test files, which is what these cases must not do. Each one seeds a specific cached value, an empty catalog or a null subscription answer, and asserts how many requests follow. A shared cache would make them order dependent.

It is also the established pattern for this suite. create-ephemeral-app-from-template.test.ts, session-interaction-answer.test.ts, commit-invalidations.test.ts, session-mounts-store.test.ts and others construct a client the same way and hydrate the atom with it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- repository guidance ---'
if [ -f web/AGENTS.md ]; then
  rg -n -C 4 'QueryClient|queryClientAtom|getHostQueryClient|orphan cache|test' web/AGENTS.md
else
  echo 'web/AGENTS.md not found'
fi

printf '%s\n' '--- target test ---'
cat -n web/packages/agenta-entities/tests/unit/harness-catalog-recovery.test.ts | sed -n '40,106p'

printf '%s\n' '--- host query-client symbols and atom hydration ---'
rg -n -C 5 '\bgetHostQueryClient\b|\bqueryClientAtom\b' web/packages -g '*.{ts,tsx}'

printf '%s\n' '--- cited test patterns ---'
for name in \
  create-ephemeral-app-from-template.test.ts \
  session-interaction-answer.test.ts \
  commit-invalidations.test.ts \
  session-mounts-store.test.ts
do
  fd -a -t f "$name" web/packages | while IFS= read -r file; do
    echo "--- $file ---"
    rg -n -C 5 '\bnew QueryClient\b|\bqueryClientAtom\b' "$file"
  done
done

Length of output: 238030


@mmabrouk, the atom hydration is correct. It ensures that loadAgentModelCandidates() uses the cache that the test seeds.

However, web/AGENTS.md also explicitly requires the shared @agenta/shared/api queryClient singleton for a test harness. It states: “Never construct your own new QueryClient().” The local client therefore does not meet the documented host contract.

The cited tests use the same local-client pattern, but they do not establish an exception to that rule. Please use the shared host client and clear or reset its cache and defaults during test setup and cleanup.

You are interacting with an AI system.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
web/packages/agenta-entities/src/workflow/state/agentModelCandidates.ts (2)

66-68: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep each source comment to one short line.

The new explanatory comments use multi-line blocks. Apply the same one-line format at every listed site.

  • web/packages/agenta-entities/src/workflow/state/agentModelCandidates.ts#L66-L68: shorten the empty-catalog explanation.
  • web/packages/agenta-entities/src/workflow/state/agentModelCandidates.ts#L95-L103: shorten the subscription-unknown explanation.
  • web/packages/agenta-entities/src/workflow/state/agentModelCandidates.ts#L130-L131: shorten the cached-catalog explanation.
  • web/packages/agenta-entities/tests/unit/agent-model-candidate-sources.test.ts#L194-L195: shorten the unreadable-answer explanation.

Source: Coding guidelines


166-172: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Limit recovery refetches to invalid cached data.

When the cold-load ensureQueryData request returns an empty catalog, harnessCatalogIsUsable(data) is false and fetchQuery sends a second request. Read the cache before ensureQueryData, and refetch only when an existing cached value is unusable.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Advanced

Run ID: 8fec56de-b092-4a28-8551-21601d14cb39

📥 Commits

Reviewing files that changed from the base of the PR and between c51fa82 and 1ccdc4f.

📒 Files selected for processing (2)
  • web/packages/agenta-entities/src/workflow/state/agentModelCandidates.ts
  • web/packages/agenta-entities/tests/unit/agent-model-candidate-sources.test.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 0 remain after this review.

@mmabrouk

mmabrouk commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

Re-verified on the preview after the later commits, with a probe that counts every banner instance rather than the first one. The playground mounts the banner more than once, and two of the instances sit in collapsed wrappers, so a first-match probe can report either answer. My earlier readings were only trustworthy because screenshots stood beside them.

Same preview project, minutes apart, on the build that carries the empty-catalog commit:

key present -> bannerInstances 2, bannerVisible 0, connectKeyBadges 0
key deleted -> bannerInstances 2, bannerVisible 2, connectKeyBadges 1

So the message still appears when the project really has no key, and the composer is still disabled there. Screenshots for both are on the dev box under ~/agenta-qa-evidence/2026-09-08-issue-6660/, along with the probe itself.

The preview's runner answers subscription status properly, not_configured for all three harnesses with the runner connected, so the unknown-source path is not exercised by these runs. That path is covered by unit tests: an unreadable answer, a runner reported incompatible, and a check that could not be made all leave the state ready with subscriptionUnknown set, and the gate stands down on it while creation, the model picker and the slash commands keep working from the routes that are known.

One correction to my first comment on this PR. The empty-catalog theory does not explain the session in the issue. The QA engineer reproduced the banner in a brand new browser profile whose cached catalog holds all three harnesses, on a project whose vault holds two configured provider keys. I also pulled the deployed staging chunk and the compiled rule matches this branch. So the three inputs I can see cannot produce that banner, and one of them must reach the app differently than it reaches a raw fetch. That project's transformed vault rows are the next measurement, and the issue should stay open until they explain it.

@mmabrouk

mmabrouk commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

Second and third Codex passes, and where I disagreed with one of them.

Pass two, on the empty-catalog commit. "Keep both guards, but do not ship unchanged." It reproduced, against the real query cache, that rejecting the empty map at the boundary does not recover a copy an older build already persisted: the refetch error was swallowed while data stayed defined, so the playground sat in loading with no notice and no retry, and the imperative loader kept serving the cached map without contacting a healthy server. Both fixed, with a test that fails without the fix.

Pass three, on the unknown-subscription commit. "Request changes." Turning an unreadable answer into a candidate error blocked agent creation, which returns null for any non-ready state, and left the model picker on its loading label. That is why the state now carries subscriptionUnknown and stays ready: the routes that are known stay usable, and only the reading of an empty list changes. Codex confirmed the reshape afterwards: "the P1 creation blocker is fixed, and the state reshape is right."

Pass four, on the reshape. Three P2 items. Two are in: one classifier now decides whether an answer left the pairs unknown, so a cached incompatible runner is refetched rather than served forever, and there are new cases for a recovered runner and for a connected runner reporting unsupported.

The third I did not take, and this is a judgement call worth a second opinion. Codex argues unavailable should also count as unknown, because runtime_status.py returns it for timeouts, connection failures and non-200 responses as well as for a runner that is not configured. That is true. I am keeping it a true negative anyway: the same word is what a deployment with no runner answers, which is the common self-hosted case, and treating it as unknown would silence the add-a-key prompt for exactly the users who need it. With no vault key and no reachable runner, nothing is runnable and adding a provider key is the remedy, so the message is still correct advice. The real fix is for the service to distinguish a confirmed absence from a failed check, and that belongs in its own change.

Also left for later, both P2 and both pre-existing: the model picker keeps a subscription-backed selection when the subscription source is unknown, and a settled required-source failure deserves a factual state with Retry rather than the loading label.

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.

1 participant