Skip to content

Commit 696cabc

Browse files
Terraphim CIclaude
authored andcommitted
fix(orchestrator): add local active_agents guard to prevent duplicate dispatch race
The should_skip_dispatch check relied solely on the Gitea API to detect assigned agents, but when two dispatch paths (webhook + mention poller) fire within milliseconds of each other, the API can return stale data. The second path sees the agent as unassigned and spawns a duplicate. Add a fast local active_agents.contains_key() check at the top of should_skip_dispatch, before any remote API call. This eliminates the TOCTOU race because the in-process HashMap is updated synchronously after spawn_agent inserts the ManagedAgent. Refs #326 Co-Authored-By: Terraphim AI <noreply@anthropic.com>
1 parent 12bab97 commit 696cabc

1 file changed

Lines changed: 14 additions & 1 deletion

File tree

  • crates/terraphim_orchestrator/src

crates/terraphim_orchestrator/src/lib.rs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1601,12 +1601,25 @@ impl AgentOrchestrator {
16011601
if issue_number == 0 {
16021602
return false;
16031603
}
1604+
1605+
// Fast local check: if agent is already running, skip immediately.
1606+
// This prevents races where the Gitea API returns stale assignee data
1607+
// because a concurrent dispatch path just assigned the issue milliseconds ago.
1608+
if self.active_agents.contains_key(agent_name) {
1609+
warn!(
1610+
agent = %agent_name,
1611+
issue = issue_number,
1612+
"skipping dispatch: agent already active (local guard)"
1613+
);
1614+
return true;
1615+
}
1616+
16041617
let Some(ref poster) = self.output_poster else {
16051618
return false;
16061619
};
16071620
let tracker = poster.tracker_for(agent_name);
16081621

1609-
// Check current assignees
1622+
// Remote check: if agent is assigned in Gitea but not active (crash recovery)
16101623
match tracker.fetch_issue_assignees(issue_number).await {
16111624
Ok(assignees) => {
16121625
if assignees.iter().any(|a| a == agent_name) {

0 commit comments

Comments
 (0)