fix(config): respect provider choice and embeddings disabled state#138
fix(config): respect provider choice and embeddings disabled state#138bigguybobby wants to merge 6 commits intonearai:mainfrom
Conversation
Fixes nearai#129 — When a user selects OpenAI-compatible provider and disables embeddings during onboard, the config resolver no longer overrides those choices. Root cause: EmbeddingsConfig::resolve() used openai_api_key.is_some() as a fallback for the enabled flag, which meant having OPENAI_API_KEY in the environment would force-enable embeddings even when the user explicitly disabled them in the wizard. Changes: - EmbeddingsConfig::resolve(): use settings.embeddings.enabled as the sole fallback (env var EMBEDDING_ENABLED still overrides when explicitly set) - Updated comment on NEAR AI config resolution for accuracy - Added regression tests for embeddings config resolution
Summary of ChangesHello @bigguybobby, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses a critical configuration bug where embedding functionality was inadvertently activated due to an environment variable, overriding explicit user preferences. The changes ensure that user-defined settings for embeddings are respected, preventing unintended behavior and improving configuration reliability. Comprehensive tests have been added to validate the corrected logic and prevent future regressions. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request correctly fixes a bug where the presence of an OPENAI_API_KEY would incorrectly enable embeddings, even when explicitly disabled by the user. The logic change in EmbeddingsConfig::resolve() is direct and effective. The addition of regression tests is excellent for preventing this issue from recurring. I've added one comment regarding the new tests to make them more robust against parallel execution, which could cause flakiness.
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
| use crate::settings::{EmbeddingsSettings, Settings}; | ||
|
|
||
| /// Helper to safely set/remove env vars in tests. | ||
| unsafe fn clear_embedding_env() { | ||
| unsafe { | ||
| std::env::remove_var("EMBEDDING_ENABLED"); | ||
| std::env::remove_var("EMBEDDING_PROVIDER"); | ||
| std::env::remove_var("EMBEDDING_MODEL"); | ||
| std::env::remove_var("OPENAI_API_KEY"); | ||
| } | ||
| } | ||
|
|
||
| /// Regression test for #129: when the user disables embeddings in the wizard, | ||
| /// the presence of OPENAI_API_KEY in the environment must not re-enable them. | ||
| #[test] | ||
| fn embeddings_disabled_not_overridden_by_openai_key() { | ||
| unsafe { | ||
| clear_embedding_env(); | ||
| std::env::set_var("OPENAI_API_KEY", "sk-test-key-for-issue-129"); | ||
| } | ||
|
|
||
| let settings = Settings { | ||
| embeddings: EmbeddingsSettings { | ||
| enabled: false, | ||
| ..Default::default() | ||
| }, | ||
| ..Default::default() | ||
| }; | ||
|
|
||
| let config = EmbeddingsConfig::resolve(&settings).expect("resolve should succeed"); | ||
| assert!( | ||
| !config.enabled, | ||
| "embeddings should remain disabled when settings.embeddings.enabled=false, \ | ||
| even when OPENAI_API_KEY is set (issue #129)" | ||
| ); | ||
|
|
||
| unsafe { std::env::remove_var("OPENAI_API_KEY"); } | ||
| } | ||
|
|
||
| /// When the user enables embeddings in settings, it should be enabled. | ||
| #[test] | ||
| fn embeddings_enabled_from_settings() { | ||
| unsafe { clear_embedding_env(); } | ||
|
|
||
| let settings = Settings { | ||
| embeddings: EmbeddingsSettings { | ||
| enabled: true, | ||
| ..Default::default() | ||
| }, | ||
| ..Default::default() | ||
| }; | ||
|
|
||
| let config = EmbeddingsConfig::resolve(&settings).expect("resolve should succeed"); | ||
| assert!(config.enabled, "embeddings should be enabled when settings say so"); | ||
| } | ||
|
|
||
| /// EMBEDDING_ENABLED env var should override settings (explicit user override). | ||
| #[test] | ||
| fn embeddings_env_override_takes_precedence() { | ||
| unsafe { | ||
| clear_embedding_env(); | ||
| std::env::set_var("EMBEDDING_ENABLED", "true"); | ||
| } | ||
|
|
||
| let settings = Settings { | ||
| embeddings: EmbeddingsSettings { | ||
| enabled: false, | ||
| ..Default::default() | ||
| }, | ||
| ..Default::default() | ||
| }; | ||
|
|
||
| let config = EmbeddingsConfig::resolve(&settings).expect("resolve should succeed"); | ||
| assert!(config.enabled, "EMBEDDING_ENABLED=true env var should override settings"); | ||
|
|
||
| unsafe { std::env::remove_var("EMBEDDING_ENABLED"); } | ||
| } | ||
| } |
There was a problem hiding this comment.
These tests modify environment variables, which is a global state. When tests are run in parallel (the default for cargo test), this can lead to race conditions and flaky tests. For example, one test might be clearing an environment variable while another test expects it to be set.
To ensure these tests are robust, they should be serialized. A common way to achieve this in Rust is to use a static Mutex to ensure only one test that modifies the environment can run at a time.
Additionally, the unsafe block inside clear_embedding_env is redundant because the function itself is already marked as unsafe.
mod tests {
use super::*;
use crate::settings::{EmbeddingsSettings, Settings};
use std::sync::Mutex;
// Use a static mutex to serialize tests that modify environment variables.
static ENV_MUTEX: Mutex<()> = Mutex::new(());
/// Helper to set/remove env vars in tests. Marked unsafe because it modifies global state.
unsafe fn clear_embedding_env() {
std::env::remove_var("EMBEDDING_ENABLED");
std::env::remove_var("EMBEDDING_PROVIDER");
std::env::remove_var("EMBEDDING_MODEL");
std::env::remove_var("OPENAI_API_KEY");
}
/// Regression test for #129: when the user disables embeddings in the wizard,
/// the presence of OPENAI_API_KEY in the environment must not re-enable them.
#[test]
fn embeddings_disabled_not_overridden_by_openai_key() {
let _guard = ENV_MUTEX.lock().unwrap();
unsafe {
clear_embedding_env();
std::env::set_var("OPENAI_API_KEY", "sk-test-key-for-issue-129");
}
let settings = Settings {
embeddings: EmbeddingsSettings {
enabled: false,
..Default::default()
},
..Default::default()
};
let config = EmbeddingsConfig::resolve(&settings).expect("resolve should succeed");
assert!(
!config.enabled,
"embeddings should remain disabled when settings.embeddings.enabled=false, \
even when OPENAI_API_KEY is set (issue #129)"
);
unsafe { std::env::remove_var("OPENAI_API_KEY"); }
}
/// When the user enables embeddings in settings, it should be enabled.
#[test]
fn embeddings_enabled_from_settings() {
let _guard = ENV_MUTEX.lock().unwrap();
unsafe { clear_embedding_env(); }
let settings = Settings {
embeddings: EmbeddingsSettings {
enabled: true,
..Default::default()
},
..Default::default()
};
let config = EmbeddingsConfig::resolve(&settings).expect("resolve should succeed");
assert!(config.enabled, "embeddings should be enabled when settings say so");
}
/// EMBEDDING_ENABLED env var should override settings (explicit user override).
#[test]
fn embeddings_env_override_takes_precedence() {
let _guard = ENV_MUTEX.lock().unwrap();
unsafe {
clear_embedding_env();
std::env::set_var("EMBEDDING_ENABLED", "true");
}
let settings = Settings {
embeddings: EmbeddingsSettings {
enabled: false,
..Default::default()
},
..Default::default()
};
let config = EmbeddingsConfig::resolve(&settings).expect("resolve should succeed");
assert!(config.enabled, "EMBEDDING_ENABLED=true env var should override settings");
unsafe { std::env::remove_var("EMBEDDING_ENABLED"); }
}
}References
- While rules for
unsafe std::env::set_varspecifically address application startup or setup wizards, the underlying principle of ensuring single-threaded access to environment variables to prevent race conditions and maintain safety invariants is also critical in test environments, especially when tests run in parallel. The suggestedstatic Mutexaligns with this principle by serializing access.
|
Addressed in Changes made:
Validation:
|
|
Follow-up in Re-validated with |
|
Addressed in follow-up commits:
The env-mutating tests are now serialized via a static mutex, and the helper structure was updated to avoid |
…ol schemas) Includes all changes from bigguybobby's PR #138: - Use Chat Completions API for OpenAI-compatible providers (avoids Responses API assumptions like required tool call IDs) - Fall back to settings.selected_model when LLM_MODEL env var is unset - Update OpenAI model list (add gpt-5 family) with priority-based sorting - Add is_openai_chat_model() filter with broader exclusion patterns - Fix http tool: headers schema → array of {name,value}, body → string type, parse_headers_param() accepts both legacy object and array formats - Fix json tool: data schema → string type, parse_json_input() normalizer, validate uses strict string-only check - Add mutex-serialized config tests for env var manipulation - Update NEAR AI config comment for accuracy Co-Authored-By: Bobby (bigguybobby) <bobbymini@proton.me> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
#177) * fix: persist OpenAI-compatible provider and respect embeddings disable (#129) Three interrelated bugs caused the agent to ignore user choices made during onboarding when using an OpenAI-compatible LLM provider: 1. Session auth ran before DB config reload, so Config::from_env() defaulted to NearAi and attempted Clerk auth before the real backend was known. Moved session auth to after final config resolution. 2. EmbeddingsConfig::resolve() force-enabled embeddings whenever OPENAI_API_KEY was present, ignoring the user's explicit disable. Changed to respect the stored setting as source of truth. 3. LLM_BACKEND was not saved to the bootstrap .env file, so Config::from_env() always defaulted to NearAi before the DB was connected. Now saves LLM_BACKEND, LLM_BASE_URL, and OLLAMA_BASE_URL alongside the database bootstrap vars. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add SAFETY comments and sanitize .env value escaping Address PR review feedback: - Add SAFETY comments to all unsafe env var manipulation in config tests (gemini-code-assist). - Escape backslashes and double quotes in save_bootstrap_env() to prevent env var injection via malicious URLs (gemini-code-assist). - Add test verifying injection attempt is neutralized. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: incorporate PR #138 changes (chat completions, model sorting, tool schemas) Includes all changes from bigguybobby's PR #138: - Use Chat Completions API for OpenAI-compatible providers (avoids Responses API assumptions like required tool call IDs) - Fall back to settings.selected_model when LLM_MODEL env var is unset - Update OpenAI model list (add gpt-5 family) with priority-based sorting - Add is_openai_chat_model() filter with broader exclusion patterns - Fix http tool: headers schema → array of {name,value}, body → string type, parse_headers_param() accepts both legacy object and array formats - Fix json tool: data schema → string type, parse_json_input() normalizer, validate uses strict string-only check - Add mutex-serialized config tests for env var manipulation - Update NEAR AI config comment for accuracy Co-Authored-By: Bobby (bigguybobby) <bobbymini@proton.me> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Illia Polosukhin <ilblacdragon@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Bobby (bigguybobby) <bobbymini@proton.me>
|
Thanks, I merged it into #177 - added co-authored there |
nearai#177) * fix: persist OpenAI-compatible provider and respect embeddings disable (nearai#129) Three interrelated bugs caused the agent to ignore user choices made during onboarding when using an OpenAI-compatible LLM provider: 1. Session auth ran before DB config reload, so Config::from_env() defaulted to NearAi and attempted Clerk auth before the real backend was known. Moved session auth to after final config resolution. 2. EmbeddingsConfig::resolve() force-enabled embeddings whenever OPENAI_API_KEY was present, ignoring the user's explicit disable. Changed to respect the stored setting as source of truth. 3. LLM_BACKEND was not saved to the bootstrap .env file, so Config::from_env() always defaulted to NearAi before the DB was connected. Now saves LLM_BACKEND, LLM_BASE_URL, and OLLAMA_BASE_URL alongside the database bootstrap vars. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add SAFETY comments and sanitize .env value escaping Address PR review feedback: - Add SAFETY comments to all unsafe env var manipulation in config tests (gemini-code-assist). - Escape backslashes and double quotes in save_bootstrap_env() to prevent env var injection via malicious URLs (gemini-code-assist). - Add test verifying injection attempt is neutralized. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: incorporate PR nearai#138 changes (chat completions, model sorting, tool schemas) Includes all changes from bigguybobby's PR nearai#138: - Use Chat Completions API for OpenAI-compatible providers (avoids Responses API assumptions like required tool call IDs) - Fall back to settings.selected_model when LLM_MODEL env var is unset - Update OpenAI model list (add gpt-5 family) with priority-based sorting - Add is_openai_chat_model() filter with broader exclusion patterns - Fix http tool: headers schema → array of {name,value}, body → string type, parse_headers_param() accepts both legacy object and array formats - Fix json tool: data schema → string type, parse_json_input() normalizer, validate uses strict string-only check - Add mutex-serialized config tests for env var manipulation - Update NEAR AI config comment for accuracy Co-Authored-By: Bobby (bigguybobby) <bobbymini@proton.me> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Illia Polosukhin <ilblacdragon@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Bobby (bigguybobby) <bobbymini@proton.me>
nearai#177) * fix: persist OpenAI-compatible provider and respect embeddings disable (nearai#129) Three interrelated bugs caused the agent to ignore user choices made during onboarding when using an OpenAI-compatible LLM provider: 1. Session auth ran before DB config reload, so Config::from_env() defaulted to NearAi and attempted Clerk auth before the real backend was known. Moved session auth to after final config resolution. 2. EmbeddingsConfig::resolve() force-enabled embeddings whenever OPENAI_API_KEY was present, ignoring the user's explicit disable. Changed to respect the stored setting as source of truth. 3. LLM_BACKEND was not saved to the bootstrap .env file, so Config::from_env() always defaulted to NearAi before the DB was connected. Now saves LLM_BACKEND, LLM_BASE_URL, and OLLAMA_BASE_URL alongside the database bootstrap vars. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: add SAFETY comments and sanitize .env value escaping Address PR review feedback: - Add SAFETY comments to all unsafe env var manipulation in config tests (gemini-code-assist). - Escape backslashes and double quotes in save_bootstrap_env() to prevent env var injection via malicious URLs (gemini-code-assist). - Add test verifying injection attempt is neutralized. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: incorporate PR nearai#138 changes (chat completions, model sorting, tool schemas) Includes all changes from bigguybobby's PR nearai#138: - Use Chat Completions API for OpenAI-compatible providers (avoids Responses API assumptions like required tool call IDs) - Fall back to settings.selected_model when LLM_MODEL env var is unset - Update OpenAI model list (add gpt-5 family) with priority-based sorting - Add is_openai_chat_model() filter with broader exclusion patterns - Fix http tool: headers schema → array of {name,value}, body → string type, parse_headers_param() accepts both legacy object and array formats - Fix json tool: data schema → string type, parse_json_input() normalizer, validate uses strict string-only check - Add mutex-serialized config tests for env var manipulation - Update NEAR AI config comment for accuracy Co-Authored-By: Bobby (bigguybobby) <bobbymini@proton.me> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Illia Polosukhin <ilblacdragon@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Bobby (bigguybobby) <bobbymini@proton.me>
Fixes #129
Summary
Respects user-selected provider and explicit embeddings disable state during config resolution.
Root Cause
EmbeddingsConfig::resolve()used this fallback forenabled:settings.embeddings.enabled || openai_api_key.is_some()If
OPENAI_API_KEYexisted, embeddings could be re-enabled even when disabled by settings.Changes
settings.embeddings.enabledas the fallback forenabled.EMBEDDING_ENABLEDenv var as explicit override.Validation
cargo test embeddings_passes locally, including all new regression tests.