Conversation
tusharmath
commented
Mar 24, 2026
- feat(forge_config): add forge configuration crate and loader
- refactor(forge_config): switch config serde to snake_case
- refactor(forge_config): load global config with oncecell panic-on-fail
- feat(forge_config): load user config from home directory
- refactor(forge_config): rename retry and parallel read config fields
- refactor(forge_config): add provider and model selection fields
- feat(forge_config): add FORGE env var overrides for config
- refactor(forge_config): switch config loading from json to toml
- chore(forge_config): add default toml config for tool limits
- refactor(forge_config): rename forge_config module to config
- refactor(forge_config): switch to async config reader/writer
- feat(forge_config): add merge strategies to config structs
- feat(forge_config): implement multi-source config loading order
- refactor(forge_config): remove merge derive and use toml_edit serialization
- refactor(forge_config): load config from resolved path
- feat(forge_config): add temperature, sampling, update, compact types
- feat(forge_config): add tool limits and compact/update config
- refactor(forge_config): make config path optional in reader
- refactor(forge_repo): decouple disk appconfig from domain type
- refactor(domain): rename AppConfig to ForgeConfig everywhere
41a0729 to
5ce8133
Compare
5ce8133 to
f12fe62
Compare
| impl From<f32> for Temperature { | ||
| fn from(value: f32) -> Self { | ||
| Temperature::new_unchecked(value) | ||
| } |
There was a problem hiding this comment.
The From<f32> implementation for Temperature bypasses validation by calling new_unchecked(). This allows creating invalid Temperature values (outside the 0.0-2.0 range) in production builds where debug assertions are disabled.
Impact: Code like Temperature::from(5.0) will create an invalid state that violates the type's invariants, potentially causing incorrect behavior downstream.
Fix: Remove this implementation or change to TryFrom:
impl TryFrom<f32> for Temperature {
type Error = String;
fn try_from(value: f32) -> Result<Self, Self::Error> {
Temperature::new(value)
}
}| impl From<f32> for Temperature { | |
| fn from(value: f32) -> Self { | |
| Temperature::new_unchecked(value) | |
| } | |
| impl TryFrom<f32> for Temperature { | |
| type Error = String; | |
| fn try_from(value: f32) -> Result<Self, Self::Error> { | |
| Temperature::new(value) | |
| } | |
| } |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
crates/forge_config/src/config.rs
Outdated
| let home_dir = dirs::home_dir().ok_or_else(|| { | ||
| std::io::Error::new(std::io::ErrorKind::NotFound, "home directory not found") | ||
| })?; | ||
| Ok(home_dir.join("forge").join(".forge.toml")) |
There was a problem hiding this comment.
Critical Path Mismatch Bug
The config path uses home_dir.join("forge") which creates ~/forge/.forge.toml, but the test fixtures in app_config.rs expect ~/.forge/.forge.toml (using .forge directory).
This will cause production to read/write config to the wrong location, breaking all config persistence.
// Fix: Change line 202 to:
Ok(home_dir.join(".forge").join(".forge.toml"))| Ok(home_dir.join("forge").join(".forge.toml")) | |
| Ok(home_dir.join(".forge").join(".forge.toml")) |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
crates/forge_config/src/reader.rs
Outdated
| let json_contents = tokio::fs::read_to_string(&path).await?; | ||
| if let Ok(json_config) = serde_json::from_str::<LegacyConfig>(&json_contents) { | ||
| let config = json_config.into_forge_config(); | ||
| let toml_contents = toml_edit::ser::to_string(&config).unwrap_or_default(); |
There was a problem hiding this comment.
Silent serialization failure could corrupt legacy config migration. The unwrap_or_default() returns an empty string if serialization fails, which would then be parsed as an empty TOML document. This loses the user's legacy JSON config data without any error being logged or propagated. The error should be handled explicitly:
let toml_contents = toml_edit::ser::to_string(&config)
.map_err(|e| {
debug!("Failed to serialize legacy config: {:?}", e);
e
})?;Or at minimum log the error:
let toml_contents = toml_edit::ser::to_string(&config).unwrap_or_else(|e| {
debug!("Failed to serialize legacy config, skipping: {:?}", e);
String::new()
});| let toml_contents = toml_edit::ser::to_string(&config).unwrap_or_default(); | |
| let toml_contents = toml_edit::ser::to_string(&config) | |
| .map_err(|e| { | |
| debug!("Failed to serialize legacy config: {:?}", e); | |
| e | |
| })?; |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
ed07d6d to
63f03c2
Compare
| pub fn base_path() -> PathBuf { | ||
| dirs::home_dir().unwrap_or(PathBuf::from(".")).join("forge") | ||
| } |
There was a problem hiding this comment.
If dirs::home_dir() returns None, config files will be written to the current working directory (".") instead of failing safely. This causes config files to be scattered across different directories depending on where the command is run.
pub fn base_path() -> PathBuf {
dirs::home_dir()
.expect("Could not determine home directory")
.join("forge")
}| pub fn base_path() -> PathBuf { | |
| dirs::home_dir().unwrap_or(PathBuf::from(".")).join("forge") | |
| } | |
| pub fn base_path() -> PathBuf { | |
| dirs::home_dir().expect("Could not determine home directory").join("forge") | |
| } |
Spotted by Graphite
Is this helpful? React 👍 or 👎 to let us know.
0d3a328 to
cd337b3
Compare