Skip to content

fix(dashboard): SecretRef fields corrupt on config save; model picker shows Not set#57527

Closed
bgrubin wants to merge 6 commits into
openclaw:mainfrom
bgrubin:fix/dashboard-config-secretref-save
Closed

fix(dashboard): SecretRef fields corrupt on config save; model picker shows Not set#57527
bgrubin wants to merge 6 commits into
openclaw:mainfrom
bgrubin:fix/dashboard-config-secretref-save

Conversation

@bgrubin

@bgrubin bgrubin commented Mar 30, 2026

Copy link
Copy Markdown

Problem

Any config.set from the dashboard was rejected with:

GatewayRequestError: invalid config: channels.discord.token.id: Env secret reference id must match /^[A-Z][A-Z0-9_]{0,127}$/

Also: the Primary model picker on /agents always showed "Not set" after page load or refresh.

Root Cause

The Discord token ui-hint was missing sensitive: true. This caused restoreRedactedValues to use the pattern-guessing path, which checked isSensitivePath('channels.discord.token.id')false, since the path ends in id not token. The __OPENCLAW_REDACTED__ sentinel inside the SecretRef's id field was never restored before Zod validation, so validation failed.

Fixes

Fix 1 (extensions/discord/src/config-ui-hints.ts):
Add sensitive: true to the Discord token hint so the lookup-based restore path is used.

Fix 2 (src/config/redact-snapshot.ts):
In restoreRedactedValuesWithLookup, detect when a sensitive-hinted path holds a SecretRef object whose id field contains the redacted sentinel, and restore the whole original value rather than recursing into sub-fields that have no individual hint entries. Mirrors the asymmetric behavior in redactSecretRefId.

Fix 3 (ui/src/ui/views/config-form.node.ts):
renderTextInput was calling String(value) on the DOM input's .value property. When value is a SecretRef object and the field is revealed, this produces [object Object] which gets written into configForm state and sent to config.set. Guard: treat structured (non-primitive) values as permanently read-only with a "use Raw mode to edit" placeholder, and suppress the reveal toggle.

Fix 4 (ui/src/ui/views/agents-panels-overview.ts, agents-utils.ts):
The Primary model picker on /agents always showed "Not set" after load/refresh. Cause: <select>.value binding fires before async catalog options are in the DOM. Fix: add ?selected=${value === current} to each <option> so selection is set declaratively rather than relying on the parent <select>'s .value property.

Ben Grubin added 4 commits March 29, 2026 00:03
…API access

Add bearerToken and region to the amazon-bedrock plugin config schema.
When bearerToken is configured (as a string or secret ref), inject it
into process.env so the AWS SDK picks it up for both model discovery
(ListFoundationModels) and inference requests.

This allows configuring Bedrock credentials via openclaw.json instead
of requiring manual LaunchAgent plist edits or shell env setup.
Adds a new bundled hook that replaces workspace bootstrap file slots
(SOUL.md, IDENTITY.md, etc.) with content read from explicitly configured
external source paths.

Motivation: the workspace loader enforces a strict realpath boundary,
rejecting symlinks whose targets canonically escape the workspace root.
This silently marks files like SOUL.md/IDENTITY.md as [MISSING] when they
are symlinked to Dropbox-backed shared locations.

The new hook runs after the workspace loader via agent:bootstrap, and
replaces entries in-place using operator-configured paths. Because the
source paths are explicit in config rather than inferred, this is a
controlled escape hatch that does not weaken the general boundary model.

Changes:
- src/hooks/bundled/bootstrap-alternate-files/handler.ts
- src/hooks/bundled/bootstrap-alternate-files/HOOK.md
- src/hooks/bundled/bootstrap-alternate-files/handler.test.ts (10 tests)
- src/agents/workspace.ts: export VALID_BOOTSTRAP_NAMES (was private)
…r shows Not set

Three bugs found and fixed while diagnosing a config save failure where
any config.set from the dashboard was rejected with:
  channels.discord.token.id: Env secret reference id must match pattern

Root cause (restore): The Discord token ui-hint lacked sensitive:true, so
restoreRedactedValues used the pattern-guessing path which checked
isSensitivePath('channels.discord.token.id') - false, since the path ends
in 'id' not 'token'. The OPENCLAW_REDACTED sentinel in the SecretRef's
id field was never restored to its original value before Zod validation.

Fix 1 (extensions/discord/src/config-ui-hints.ts):
  Add sensitive:true to the token hint so the lookup-based restore path
  is used for channels.discord.token.

Fix 2 (src/config/redact-snapshot.ts):
  In restoreRedactedValuesWithLookup, detect when a sensitive-hinted path
  holds a SecretRef object whose id field contains the sentinel, and restore
  the whole original value rather than recursing into sub-fields that have
  no individual hint entries.

Fix 3 (ui/src/ui/views/config-form.node.ts):
  renderTextInput was applying String(value) to the DOM input .value
  property. When value is a SecretRef object and the field is revealed,
  this produces '[object Object]' which gets written into configForm state
  and then sent to config.set. Guard: treat structured (non-primitive)
  values as permanently read-only with a 'use Raw mode to edit' placeholder.

Fix 4 (ui/src/ui/views/agents-panels-overview.ts, agents-utils.ts):
  Primary model picker on /agents always showed 'Not set' on page
  load/refresh. Cause: select.value binding fires before async catalog
  options are in the DOM. Fix: add selected attribute to each option
  declaratively rather than relying on the parent select .value property.
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: discord Channel integration: discord app: web-ui App: web-ui agents Agent runtime and tooling size: L labels Mar 30, 2026
@greptile-apps

greptile-apps Bot commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes four related bugs affecting the dashboard config save flow and the agents model picker. The root cause chain is well-diagnosed: a missing sensitive: true hint on the Discord token field caused the redaction/restore pipeline to use a pattern-guessing path that couldn't find the sentinel inside a nested SecretRef.id, causing all config.set calls to fail Zod validation. All four fixes are logically correct and well-targeted. The new bootstrap-alternate-files bundled hook is clean and handles error paths gracefully. No P0 or P1 issues found.

Confidence Score: 5/5

All four bug fixes are logically sound and well-targeted; no critical issues found.

All four described bugs are correctly fixed. The restoreRedactedValuesWithLookup new branch has correct ordering. The isStructuredValue guard in renderTextInput correctly prevents [object Object] corruption. The declarative ?selected bindings fix the async-options race. No P0 or P1 issues remain.

No files require special attention.

Important Files Changed

Filename Overview
extensions/discord/src/config-ui-hints.ts Adds missing sensitive: true to Discord token hint, fixing the root cause of the SecretRef restore failure.
src/config/redact-snapshot.ts Adds new else-if branch to detect and wholesale-restore a SecretRef object whose id field contains the redacted sentinel.
ui/src/ui/views/config-form.node.ts Guards renderTextInput against structured (object) values being serialized as [object Object] and written back to form state.
ui/src/ui/views/agents-utils.ts Adds declarative ?selected bindings on option elements to fix async catalog-loading race in model picker.
ui/src/ui/views/agents-panels-overview.ts Adds ?selected to empty-value option elements for correct declarative selection state.
src/hooks/bundled/bootstrap-alternate-files/handler.ts New bundled hook that replaces workspace bootstrap file slots with content from external paths; handles errors gracefully.
src/agents/workspace.ts Exports VALID_BOOTSTRAP_NAMES for use by the new bootstrap-alternate-files hook.

Reviews (2): Last reviewed commit: "fix(review): address greptile feedback o..." | Re-trigger Greptile

Comment thread extensions/amazon-bedrock/discovery.ts Outdated
Comment on lines +252 to +257
if (params.bearerToken && !baseEnv["AWS_BEARER_TOKEN_BEDROCK"]?.trim()) {
process.env["AWS_BEARER_TOKEN_BEDROCK"] = params.bearerToken;
if (baseEnv !== process.env) {
env = { ...baseEnv, AWS_BEARER_TOKEN_BEDROCK: params.bearerToken };
}
}

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.

P1 Bearer token silently stale after first injection

process.env["AWS_BEARER_TOKEN_BEDROCK"] is only written when it is absent (!baseEnv["…"].trim()). On the very first call the value is injected, but on every subsequent catalog refresh baseEnv["AWS_BEARER_TOKEN_BEDROCK"] is already truthy, so the branch is skipped. If the user later rotates the token in their plugin config, params.bearerToken will hold the new value but the guard will prevent it from reaching process.env — the AWS SDK will keep using the stale token until the process restarts.

The intent of the guard is presumably to avoid clobbering a token that was set independently in the real OS environment. A simple way to honour both goals is to also overwrite when the existing value differs from the incoming one:

Suggested change
if (params.bearerToken && !baseEnv["AWS_BEARER_TOKEN_BEDROCK"]?.trim()) {
process.env["AWS_BEARER_TOKEN_BEDROCK"] = params.bearerToken;
if (baseEnv !== process.env) {
env = { ...baseEnv, AWS_BEARER_TOKEN_BEDROCK: params.bearerToken };
}
}
if (params.bearerToken && params.bearerToken !== baseEnv["AWS_BEARER_TOKEN_BEDROCK"]?.trim()) {
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/amazon-bedrock/discovery.ts
Line: 252-257

Comment:
**Bearer token silently stale after first injection**

`process.env["AWS_BEARER_TOKEN_BEDROCK"]` is only written when it is absent (`!baseEnv["…"].trim()`). On the very first call the value is injected, but on every subsequent catalog refresh `baseEnv["AWS_BEARER_TOKEN_BEDROCK"]` is already truthy, so the branch is skipped. If the user later rotates the token in their plugin config, `params.bearerToken` will hold the new value but the guard will prevent it from reaching `process.env` — the AWS SDK will keep using the stale token until the process restarts.

The intent of the guard is presumably to avoid clobbering a token that was set independently in the real OS environment. A simple way to honour both goals is to also overwrite when the existing value differs from the incoming one:

```suggestion
  if (params.bearerToken && params.bearerToken !== baseEnv["AWS_BEARER_TOKEN_BEDROCK"]?.trim()) {
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +159 to +163
region: {
type: "string",
description: "AWS region for Bedrock API calls. Defaults to us-east-1.",
},
},

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.

P1 region plugin config property is not wired up

The schema exposes a region field under plugins.entries.amazon-bedrock.config, but extensions/amazon-bedrock/index.ts only reads pluginConfig?.bearerToken — it never reads pluginConfig?.region. The actual region is resolved from config.models.bedrockDiscovery.region (a different config path) inside resolveImplicitBedrockProvider.

A user who sets plugins.entries.amazon-bedrock.config.region expecting it to control the Bedrock region will have that value silently ignored. Either wire it up in index.ts before the resolveImplicitBedrockProvider call, or remove this field from the schema until it is implemented.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugins/bundled-plugin-metadata.generated.ts
Line: 159-163

Comment:
**`region` plugin config property is not wired up**

The schema exposes a `region` field under `plugins.entries.amazon-bedrock.config`, but `extensions/amazon-bedrock/index.ts` only reads `pluginConfig?.bearerToken` — it never reads `pluginConfig?.region`. The actual region is resolved from `config.models.bedrockDiscovery.region` (a different config path) inside `resolveImplicitBedrockProvider`.

A user who sets `plugins.entries.amazon-bedrock.config.region` expecting it to control the Bedrock region will have that value silently ignored. Either wire it up in `index.ts` before the `resolveImplicitBedrockProvider` call, or remove this field from the schema until it is implemented.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +189 to +215
it("expands tilde in source paths", async () => {
// Write a file in tmpDir and create a tilde path that points to it.
// We can't easily test with a real home dir, so verify the file IS found
// by providing an absolute path that happens to use home dir prefix.
const homeDir = os.homedir();
const relToHome = path.relative(homeDir, tmpDir);
// Only run this sub-test if tmpDir is inside home
if (relToHome.startsWith("..")) {
return;
}
const tildePath = path.join("~", relToHome, "SOUL-tilde.md");
const absFile = path.join(tmpDir, "SOUL-tilde.md");
await fs.writeFile(absFile, "# Tilde Soul", "utf-8");

const context = makeContext(
[makeBootstrapFile("SOUL.md", { missing: true })],
createAlternateConfig({ "SOUL.md": tildePath }),
);

await handler(createHookEvent("agent", "bootstrap", "agent:main:main", context));

expect(context.bootstrapFiles[0]?.content).toBe("# Tilde Soul");
});

it("handles multiple replacements in a single pass", async () => {
const soulFile = path.join(tmpDir, "SOUL-multi.md");
const identFile = path.join(tmpDir, "IDENTITY-multi.md");

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.

P2 Silent no-op test when tmpDir is outside home dir

When relToHome.startsWith("..") the test body returns without any assertions — it passes unconditionally, giving no signal that tilde expansion was not exercised. CI will show a green checkmark even though the feature was never verified.

Use Vitest's conditional skip instead:

Suggested change
it("expands tilde in source paths", async () => {
// Write a file in tmpDir and create a tilde path that points to it.
// We can't easily test with a real home dir, so verify the file IS found
// by providing an absolute path that happens to use home dir prefix.
const homeDir = os.homedir();
const relToHome = path.relative(homeDir, tmpDir);
// Only run this sub-test if tmpDir is inside home
if (relToHome.startsWith("..")) {
return;
}
const tildePath = path.join("~", relToHome, "SOUL-tilde.md");
const absFile = path.join(tmpDir, "SOUL-tilde.md");
await fs.writeFile(absFile, "# Tilde Soul", "utf-8");
const context = makeContext(
[makeBootstrapFile("SOUL.md", { missing: true })],
createAlternateConfig({ "SOUL.md": tildePath }),
);
await handler(createHookEvent("agent", "bootstrap", "agent:main:main", context));
expect(context.bootstrapFiles[0]?.content).toBe("# Tilde Soul");
});
it("handles multiple replacements in a single pass", async () => {
const soulFile = path.join(tmpDir, "SOUL-multi.md");
const identFile = path.join(tmpDir, "IDENTITY-multi.md");
it.skipIf(path.relative(os.homedir(), tmpDir).startsWith(".."))("expands tilde in source paths", async () => {

or split the condition into beforeEach with it.skip(), so a skipped run is visible in the test report rather than silently passing.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/hooks/bundled/bootstrap-alternate-files/handler.test.ts
Line: 189-215

Comment:
**Silent no-op test when `tmpDir` is outside home dir**

When `relToHome.startsWith("..")` the test body returns without any assertions — it passes unconditionally, giving no signal that tilde expansion was not exercised. CI will show a green checkmark even though the feature was never verified.

Use Vitest's conditional skip instead:

```suggestion
  it.skipIf(path.relative(os.homedir(), tmpDir).startsWith(".."))("expands tilde in source paths", async () => {
```

or split the condition into `beforeEach` with `it.skip()`, so a skipped run is visible in the test report rather than silently passing.

How can I resolve this? If you propose a fix, please make it concise.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f6f05b88b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/amazon-bedrock/discovery.ts Outdated
Comment on lines +252 to +253
if (params.bearerToken && !baseEnv["AWS_BEARER_TOKEN_BEDROCK"]?.trim()) {
process.env["AWS_BEARER_TOKEN_BEDROCK"] = params.bearerToken;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid mutating process env with plugin bearer token

Writing params.bearerToken into process.env creates sticky global state that survives later config changes. If a user removes plugins.entries.amazon-bedrock.config.bearerToken, this value is never cleared, so subsequent discovery runs can continue authenticating with stale credentials and keep Bedrock implicitly enabled until process restart.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 64a3b9142f. When params.bearerToken is absent but a plugin-injected value exists in process.env, we now delete it so removing the config from plugin.entries.amazon-bedrock.config actually clears bearer-token auth on the next catalog run.

Comment thread extensions/amazon-bedrock/index.ts Outdated
const pluginConfig = ctx.config.plugins?.entries?.["amazon-bedrock"]?.config as
| Record<string, unknown>
| undefined;
const bearerToken = normalizeSecretInputString(pluginConfig?.bearerToken) ?? undefined;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Resolve SecretRef bearer tokens before normalization

The plugin now advertises bearerToken as string | object (SecretRef), but this path only calls normalizeSecretInputString, which returns undefined for objects. That means SecretRef-configured bearer tokens are silently ignored during Bedrock discovery, so users who follow the new schema/description will still fail auth unless they use a plain string or external env var.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 64a3b9142f. Added inline env-source SecretRef resolution in index.ts: if bearerTokenRaw is an object with source: "env", we read ctx.env[ref.id] directly. File/exec refs fall through to undefined in non-interactive catalog runs — env/credential discovery still works in that case.

- extensions/amazon-bedrock/discovery.ts: Fix stale bearer token after
  first injection. The guard previously skipped injection when the env var
  was already set, meaning a rotated token in plugin config would never
  reach the AWS SDK without a process restart. Changed to overwrite when
  the incoming token differs from the current env value.

- extensions/amazon-bedrock/discovery.ts + index.ts: Wire up the region
  plugin config field. The schema exposed plugins.entries.amazon-bedrock
  .config.region but index.ts never passed it to resolveImplicitBedrockProvider,
  so the field was silently ignored. Now read and forwarded, taking
  precedence over config.models.bedrockDiscovery.region and env vars.

- src/hooks/bundled/bootstrap-alternate-files/handler.test.ts: Replace
  silent early-return guard in tilde-expansion test with it.skipIf() so
  the test shows as skipped (not silently passing) when tmpDir is outside
  the home directory.
@bgrubin

bgrubin commented Mar 30, 2026

Copy link
Copy Markdown
Author

Thanks for the review @greptile-apps — addressed all three issues in the follow-up commit ce0ac7b:

Bearer token stale after first injection — Fixed. Changed the guard from !baseEnv[...].trim() to params.bearerToken !== baseEnv[...].trim() so a rotated token overwrites the env var on subsequent catalog refreshes without requiring a process restart.

region plugin config field not wired up — Fixed. Added region param to resolveImplicitBedrockProvider and read pluginConfig?.region in index.ts, passing it through with precedence over config.models.bedrockDiscovery.region and env vars.

Silent no-op test for tilde expansion — Fixed. Replaced the early return guard with it.skipIf() so the test shows as skipped in CI rather than silently passing.

extensions/amazon-bedrock/index.ts (P2 - SecretRef bearer tokens silently ignored):
  normalizeSecretInputString returns undefined for objects, so SecretRef-
  configured bearer tokens were silently dropped. Added inline resolution
  for env-source SecretRefs by reading ctx.env[ref.id] directly. File/exec
  refs are not supported in non-interactive catalog runs and fall through to
  undefined, allowing env/credential discovery to proceed as normal.

extensions/amazon-bedrock/discovery.ts (P1 - process.env mutation is sticky):
  The previous fix updated process.env only when a token was provided. If a
  user later removes bearerToken from plugin config, the stale value would
  remain in process.env indefinitely. Now: when params.bearerToken is absent
  but a plugin-injected value is present in process.env, we clear it so that
  removing the config actually stops bearer-token auth on the next catalog run.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 64a3b9142f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +264 to +267
} else if (currentEnvToken && baseEnv === process.env) {
// Token was removed from plugin config — clear the stale env var so the
// AWS SDK does not continue authenticating with it.
delete process.env["AWS_BEARER_TOKEN_BEDROCK"];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve env Bedrock token when plugin token is unset

This branch removes process.env.AWS_BEARER_TOKEN_BEDROCK whenever params.bearerToken is missing, regardless of whether the value came from plugin config or the operator environment. In normal catalog runs params.env is process.env, so users relying on AWS_BEARER_TOKEN_BEDROCK lose their credential on the first discovery pass, hasAwsCreds can flip false, and implicit Bedrock discovery may be disabled until restart. The cleanup should only clear a token that was explicitly injected by plugin config, not a pre-existing env credential.

Useful? React with 👍 / 👎.

return options.map((option) => html`<option value=${option.value}>${option.label}</option>`);
return options.map(
(option) =>
html`<option value=${option.value} ?selected=${option.value === current}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid preselecting inherited model as explicit override

buildModelOptions now marks every option selected when option.value === current, but current is passed as effectivePrimary (which includes inherited defaults). For non-default agents with no explicit primary, this makes a concrete model option selected at the same time as the empty “Inherit default” option, and single-select behavior resolves to the concrete option, so inherited agents can appear explicitly pinned after reload. Selection marking should use the explicit override value, not the effective inherited value.

Useful? React with 👍 / 👎.

@joshavant

Copy link
Copy Markdown
Contributor

Thanks @bgrubin for this PR and for grouping the related UI/config failures.

#58044 (merged) partially addresses this PR: the SecretRef config-save corruption path is now fixed (round-trip restore hardening, unsafe raw fallback handling, and write-path safeguards).

We’re leaving this PR open for the remaining scope: the separate primary-model picker “Not set” rendering issue described here.

@steipete

Copy link
Copy Markdown
Contributor

Closing this as implemented after Codex review.

Current main already covers the PR's reported behavior through landed work from other changes: SecretRef config-save hardening is present, the Control UI no longer stringifies SecretRef objects into [object Object], and the agents overview now uses effective runtime model metadata so defaulted models do not fall back to Not set.

What I checked:

  • Discord token hint is now explicitly sensitive: The Discord channel config UI hint now marks token as sensitive: true, matching the PR's root-cause analysis for SecretRef restore on config save. (extensions/discord/src/config-ui-hints.ts:212, 5dfc1b90e176)
  • SecretRef round-trip restore is implemented in main: maybeRestoreSecretRefId(...) restores redacted SecretRef ids back from the original value, and the lookup restore path calls it before recursing. This covers the broken channels.discord.token.id round-trip case described in the PR. (src/config/redact-snapshot.ts:570, 5dfc1b90e176)
  • Regression test covers the Discord SecretRef case: There is now a targeted restore test for channels.discord.token with a redacted SecretRef id, asserting the original env ref id is restored instead of failing validation. (src/config/redact-snapshot.restore.test.ts:231, 5dfc1b90e176)
  • Control UI prevents SecretRef object corruption: renderTextInput detects structured SecretRef values, keeps them read-only, and uses the structured-value placeholder instead of stringifying them. The browser test asserts the UI does not render [object Object] and does not write a corrupted patch back. (ui/src/ui/views/config-form.node.ts:678, 5dfc1b90e176)
  • Agents overview now uses effective runtime model metadata: agents.list computes each agent's effective model with resolveAgentEffectiveModelPrimary(...), and the overview falls back to agent.model when defaults are not present in the local config form. This matches the shipped fix for defaulted models showing Not set. (src/gateway/session-utils.ts:662, 5dfc1b90e176)
  • Both fixes are recorded as shipped in the changelog: CHANGELOG.md for release 2026.3.31 includes #58044 for SecretRef/Control UI hardening and #56637 for the agents overview/runtime-model fix that stops defaulted models from showing Not set. (CHANGELOG.md:1736, 5dfc1b90e176)

So I’m closing this as already implemented rather than keeping a duplicate issue open.

Review notes: reviewed against 5dfc1b90e176; fix evidence: release v2026.3.31, commit 5dfc1b90e176.

@steipete steipete closed this Apr 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: web-ui App: web-ui channel: discord Channel integration: discord docs Improvements or additions to documentation size: L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants