From 41c827c65f77d120cbd236d99e6072d307aeb2fb Mon Sep 17 00:00:00 2001 From: Huy Doan Date: Wed, 1 Apr 2026 18:07:43 +0700 Subject: [PATCH] fix(agent): update setAgentStatus to include tenantID parameter (#621) Summoning failures (e.g. provider timeout) left agents stuck in summoning because failure paths called setAgentStatus(context.Background(), ...) without tenant scope, causing agent not found on update. Pass tenantID to setAgentStatus and re-attach tenant context when missing. --- internal/http/summoner.go | 8 ++++---- internal/http/summoner_regenerate.go | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/internal/http/summoner.go b/internal/http/summoner.go index e633900e..299fb04d 100644 --- a/internal/http/summoner.go +++ b/internal/http/summoner.go @@ -104,7 +104,7 @@ func (s *AgentSummoner) SummonAgent(agentID uuid.UUID, tenantID uuid.UUID, provi if !isRetryableError(err) { slog.Warn("summoning: single-call failed (non-retryable)", "agent", agentID, "error", err) s.emitEvent(agentID, tenantID, SummonEventFailed, "", err.Error()) - s.setAgentStatus(context.Background(), agentID, store.AgentStatusSummonFailed) + s.setAgentStatus(context.Background(), tenantID, agentID, store.AgentStatusSummonFailed) return } @@ -127,7 +127,7 @@ func (s *AgentSummoner) SummonAgent(agentID uuid.UUID, tenantID uuid.UUID, provi if soulErr != nil { slog.Warn("summoning: SOUL.md generation failed", "agent", agentID, "error", soulErr) s.emitEvent(agentID, tenantID, SummonEventFailed, "", soulErr.Error()) - s.setAgentStatus(context.Background(), agentID, store.AgentStatusSummonFailed) + s.setAgentStatus(context.Background(), tenantID, agentID, store.AgentStatusSummonFailed) return } soulContent = soulFiles[bootstrap.SoulFile] @@ -155,7 +155,7 @@ func (s *AgentSummoner) SummonAgent(agentID uuid.UUID, tenantID uuid.UUID, provi if idErr != nil { slog.Warn("summoning: IDENTITY.md generation failed", "agent", agentID, "error", idErr) s.emitEvent(agentID, tenantID, SummonEventFailed, "", idErr.Error()) - s.setAgentStatus(context.Background(), agentID, store.AgentStatusSummonFailed) + s.setAgentStatus(context.Background(), tenantID, agentID, store.AgentStatusSummonFailed) return } identityContent = idFiles[bootstrap.IdentityFile] @@ -198,7 +198,7 @@ func (s *AgentSummoner) finishSummon(ctx context.Context, agentID, tenantID uuid slog.Warn("summoning: failed to save agent metadata", "agent", agentID, "error", err) } } - s.setAgentStatus(ctx, agentID, store.AgentStatusActive) + s.setAgentStatus(ctx, tenantID, agentID, store.AgentStatusActive) s.emitEvent(agentID, tenantID, SummonEventCompleted, "", "") slog.Info("summoning: completed", "agent", agentID) } diff --git a/internal/http/summoner_regenerate.go b/internal/http/summoner_regenerate.go index 9f182dcc..2475387a 100644 --- a/internal/http/summoner_regenerate.go +++ b/internal/http/summoner_regenerate.go @@ -33,7 +33,7 @@ func (s *AgentSummoner) RegenerateAgent(agentID uuid.UUID, tenantID uuid.UUID, p if err != nil { slog.Warn("summoning: failed to read existing files", "agent", agentID, "error", err) s.emitEvent(agentID, tenantID, SummonEventFailed, "", err.Error()) - s.setAgentStatus(context.Background(), agentID, store.AgentStatusSummonFailed) + s.setAgentStatus(context.Background(), tenantID, agentID, store.AgentStatusSummonFailed) return } @@ -44,7 +44,7 @@ func (s *AgentSummoner) RegenerateAgent(agentID uuid.UUID, tenantID uuid.UUID, p slog.Warn("summoning: regeneration failed", "agent", agentID, "error", err) s.emitEvent(agentID, tenantID, SummonEventFailed, "", err.Error()) // Use fresh context — the original may have timed out, but we still need to update status. - s.setAgentStatus(context.Background(), agentID, store.AgentStatusSummonFailed) + s.setAgentStatus(context.Background(), tenantID, agentID, store.AgentStatusSummonFailed) return } @@ -64,7 +64,7 @@ func (s *AgentSummoner) RegenerateAgent(agentID uuid.UUID, tenantID uuid.UUID, p } } - s.setAgentStatus(ctx, agentID, store.AgentStatusActive) + s.setAgentStatus(ctx, tenantID, agentID, store.AgentStatusActive) s.emitEvent(agentID, tenantID, SummonEventCompleted, "", "") slog.Info("summoning: regeneration completed", "agent", agentID, "files", len(files)) @@ -191,7 +191,15 @@ func (s *AgentSummoner) ensureUserPredefined(ctx context.Context, agentID uuid.U } } -func (s *AgentSummoner) setAgentStatus(ctx context.Context, agentID uuid.UUID, status string) { +func (s *AgentSummoner) setAgentStatus(ctx context.Context, tenantID, agentID uuid.UUID, status string) { + // Summoning frequently calls this after a timeout/cancel path with context.Background(). + // Re-attach tenant scope so AgentStore.Update targets the right tenant row. + if store.TenantIDFromContext(ctx) == uuid.Nil && !store.IsCrossTenant(ctx) { + if tenantID == uuid.Nil { + tenantID = store.MasterTenantID + } + ctx = store.WithTenantID(ctx, tenantID) + } if err := s.agents.Update(ctx, agentID, map[string]any{"status": status}); err != nil { slog.Warn("summoning: failed to update agent status", "agent", agentID, "status", status, "error", err) }