fix: normalizeProviders flip-flop bug when using env var apiKey references#47305
fix: normalizeProviders flip-flop bug when using env var apiKey references#47305slideshow-dingo wants to merge 7 commits intoopenclaw:mainfrom
Conversation
…ences
Removes reverse-lookup logic that converts resolved apiKey values back to
env var names, causing models.json to flip-flop on successive normalizations.
Bug: When openclaw.json configures apiKey using { source: 'env' }, the
resolved value is written to models.json. On next normalization, the code
detects the resolved value matches an env var and converts it back to the
env var name, causing instability.
Fix: Delete lines 504-519 in models-config.providers.ts that perform the
reverse-lookup conversion. models.json should contain resolved values, not
env var names.
Test: Add models-config.providers.flipflop.test.ts demonstrating the bug
and verifying the fix preserves resolved values and manual edits.
Related: #14808 (security: plaintext in cache), #11202 (keys in prompt)
Greptile SummaryThis PR fixes a flip-flop bug in Key observations:
Confidence Score: 4/5
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fd29a9cbde
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 05db60fe60
ℹ️ 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".
af3acd6 to
a80a21e
Compare
|
|
||
| // First normalization | ||
| normalizeProviders({ providers: originalProviders, agentDir }); | ||
|
|
||
| // Simulate user editing models.json | ||
| const editedProviders: NonNullable<NonNullable<OpenClawConfig["models"]>["providers"]> = { | ||
| openai: { |
There was a problem hiding this comment.
"Manual edits preserved" test does not chain the two normalizations
The first normalizeProviders call result is discarded (not assigned). The editedProviders object is then constructed from originalProviders directly and normalized in isolation. This correctly exercises the no-sourceProviders code path, but it does not cover the scenario described by the comment — where the output of a first normalization is manually edited and then re-normalized. A regression where the first pass mutates the value in a way that causes the second pass to revert it would go undetected.
For stronger coverage, consider passing the output of the first call as the base for the edit, so the test genuinely validates round-trip stability.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/models-config.providers.flipflop.test.ts
Line: 112-118
Comment:
**"Manual edits preserved" test does not chain the two normalizations**
The first `normalizeProviders` call result is discarded (not assigned). The `editedProviders` object is then constructed from `originalProviders` directly and normalized in isolation. This correctly exercises the no-`sourceProviders` code path, but it does not cover the scenario described by the comment — where the *output* of a first normalization is manually edited and then re-normalized. A regression where the first pass mutates the value in a way that causes the second pass to revert it would go undetected.
For stronger coverage, consider passing the output of the first call as the base for the edit, so the test genuinely validates round-trip stability.
How can I resolve this? If you propose a fix, please make it concise.|
Greptile review comment fix - The third test 'FIX VERIFICATION: manual edits to models.json are preserved' had an issue where the first normalization result was discarded and edits were built from the original providers instead of the normalized output. Fix applied: - // First normalization
- normalizeProviders({ providers: originalProviders, agentDir });
+ // First normalization — capture the output
+ const normalized1 = normalizeProviders({ providers: originalProviders, agentDir });
- // Simulate user editing models.json
+ // Simulate user editing the normalized output from models.json
const editedProviders = {
openai: {
- ...originalProviders.openai,
- apiKey: "edited-value",
+ ...normalized1!.openai!,
+ apiKey: "edited-value", // User manually edits after first normalization
},
};All 4 tests pass with this fix. |
slideshow-dingo
left a comment
There was a problem hiding this comment.
The "manual edits preserved" test should capture the first normalizeProviders result and use it as the base for editing. Currently editedProviders is built from originalProviders instead of the normalized output, so round-trip stability is not actually validated.
// First normalization — capture the output
const normalized1 = normalizeProviders({ providers: originalProviders, agentDir });
// Simulate user editing the normalized output from models.json
const editedProviders: NonNullable<NonNullable<OpenClawConfig["models"]>["providers"]> = {
openai: {
...normalized1.openai!,
apiKey: "edited-value", // User manually edits after first normalization
},
};
// Second normalization: should preserve the manually edited value
const normalized2 = normalizeProviders({ providers: editedProviders, agentDir });
expect(normalized2?.openai?.apiKey).toBe("edited-value");
|
The "manual edits preserved" test should capture the first Fix: // First normalization — capture the output
const normalized1 = normalizeProviders({ providers: originalProviders, agentDir });
// Simulate user editing the normalized output from models.json
const editedProviders = {
openai: {
...normalized1.openai!,
apiKey: "edited-value", // User manually edits after first normalization
},
};
// Second normalization: should preserve the manually edited value
const normalized2 = normalizeProviders({ providers: editedProviders, agentDir });
expect(normalized2?.openai?.apiKey).toBe("edited-value"); |
Bug Description
When
openclaw.jsonconfigures a provider apiKey using an env var reference, thenormalizeProviders()function creates a flip-flop cycle:models.jsonThis causes
models.jsonto alternate between env var name and resolved value on every normalization.Root Cause
Located in
src/agents/models-config.providers.tslines 504-519 (v2026.3.13) - a reverse-lookup block that converts resolved values back to env var names.Fix
Remove the reverse-lookup block entirely.
models.jsonshould contain resolved values, not env var names.Testing
Added
src/agents/models-config.providers.flipflop.test.tswith:{source: "env"}configRelated Issues
OpenClaw Version: v2026.3.13