You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
if !is_fork {self.running_tasks.insert((agent_id, effective_session_id),RunningTask{abort: handle.abort_handle(), ...});}
DashMap::insert returns the old value and silently drops it. If two concurrent non-fork streaming invocations target the same (agent_id, session_id) key (which can happen with session_mode = Persistent when two callers both compute the same effective_session_id), the earlier entry's abort_handle is silently dropped.
stop_agent_run calls running_tasks.remove(&(agent_id, session_id)) and aborts whichever entry is currently stored — typically the second invocation's handle. The first invocation continues running, burning tokens, with no way to stop it externally.
When This Triggers
session_mode = Persistent (default) + two concurrent calls through the streaming path that both resolve to the same session ID
With the streaming bypass already present, a channel bridge + dashboard concurrent send can both land here simultaneously
Impact
User clicks "stop"; UI says stopped; LLM call continues; budget drains; no log explains why
Impact severity depends on how often two concurrent streaming calls target the same session — uncommon in single-user single-session setups, more likely with channel bridges + dashboard open simultaneously
Fix
entry(...).or_insert(...) and return Err("agent already running") for the second insert, OR
Store Vec<AbortHandle> and abort all in stop_agent_run
Location
crates/librefang-kernel/src/kernel/mod.rs:6303-6309Finding
DashMap::insertreturns the old value and silently drops it. If two concurrent non-fork streaming invocations target the same(agent_id, session_id)key (which can happen withsession_mode = Persistentwhen two callers both compute the sameeffective_session_id), the earlier entry'sabort_handleis silently dropped.stop_agent_runcallsrunning_tasks.remove(&(agent_id, session_id))and aborts whichever entry is currently stored — typically the second invocation's handle. The first invocation continues running, burning tokens, with no way to stop it externally.When This Triggers
session_mode = Persistent(default) + two concurrent calls through the streaming path that both resolve to the same session IDsession_msg_locks) serializes non-streaming calls but the streaming path only doestry_readonconfig_reload_lock, notagent_msg_locks(see send_message_streaming_* path bypasses agent_msg_locks — concurrent stream + non-stream loses messages #3737)Impact
Fix
entry(...).or_insert(...)and returnErr("agent already running")for the second insert, ORVec<AbortHandle>and abort all instop_agent_runagent_msg_locks) to prevent concurrent session access entirely