diff --git a/hive-agent/src/harness_state.rs b/hive-agent/src/harness_state.rs index 6f7273686..df46d71e3 100644 --- a/hive-agent/src/harness_state.rs +++ b/hive-agent/src/harness_state.rs @@ -264,8 +264,52 @@ pub fn context_window_tokens(model: &str) -> u64 { mod tests { use super::{ DEFAULT_EFFORT, DEFAULT_MODEL, Source, context_window_tokens, effort_from, model_from, + read_harness_state, write_harness_state, }; + /// A state file an older harness wrote, with the `active_model` nothing + /// reads any more, neither stops the agent reading its flags nor is lost + /// or turned into an error by the next write: the file is read as a JSON + /// value, not a typed struct, so a leftover field cannot fail to parse. + #[test] + fn a_leftover_active_model_is_ignored_and_survives_a_write() { + // `HYPERHIVE_STATE_DIR` is process-wide; this is the only test that + // sets it, so a lock of its own is enough. + static LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + let _lock = LOCK + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); + let dir = tempfile::tempdir().unwrap(); + let previous = std::env::var_os("HYPERHIVE_STATE_DIR"); + // SAFETY: serialised by `LOCK`, and restored below. + unsafe { std::env::set_var("HYPERHIVE_STATE_DIR", dir.path()) }; + let file = dir.path().join("hyperhive-harness.json"); + std::fs::write( + &file, + r#"{"rate_limited":true,"needs_login":false,"active_model":"opus"}"#, + ) + .unwrap(); + + let read = read_harness_state(); + write_harness_state(false, true); + let after = read_harness_state(); + let stored: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(&file).unwrap()).unwrap(); + + // SAFETY: still holding `LOCK`. + unsafe { + match previous { + Some(v) => std::env::set_var("HYPERHIVE_STATE_DIR", v), + None => std::env::remove_var("HYPERHIVE_STATE_DIR"), + } + } + assert_eq!(read, (true, false)); + assert_eq!(after, (false, true)); + assert_eq!(stored["active_model"], "opus", "the leftover is kept"); + assert_eq!(stored["rate_limited"], false); + assert_eq!(stored["needs_login"], true); + } + #[test] fn context_window_defaults_per_model_family() { assert_eq!(context_window_tokens("claude-haiku-4-5"), 200_000);