diff --git a/internal/store/pg/providers.go b/internal/store/pg/providers.go index 1c42b1f3..581c6879 100644 --- a/internal/store/pg/providers.go +++ b/internal/store/pg/providers.go @@ -52,11 +52,22 @@ func (s *PGProviderStore) CreateProvider(ctx context.Context, p *store.LLMProvid p.UpdatedAt = now tid := tenantIDForInsert(ctx) p.TenantID = tid - _, err := s.db.ExecContext(ctx, + // UPSERT: if provider with same (tenant_id, name) exists, update it and return its ID. + // This handles orphaned providers left after agent deletion (#295). + var actualID uuid.UUID + err := s.db.QueryRowContext(ctx, `INSERT INTO llm_providers (id, name, display_name, provider_type, api_base, api_key, enabled, settings, created_at, updated_at, tenant_id) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`, + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) + ON CONFLICT (tenant_id, name) DO UPDATE SET + display_name = EXCLUDED.display_name, provider_type = EXCLUDED.provider_type, + api_base = EXCLUDED.api_base, api_key = EXCLUDED.api_key, + enabled = EXCLUDED.enabled, settings = EXCLUDED.settings, updated_at = EXCLUDED.updated_at + RETURNING id`, p.ID, p.Name, p.DisplayName, p.ProviderType, p.APIBase, apiKey, p.Enabled, settings, now, now, tid, - ) + ).Scan(&actualID) + if err == nil { + p.ID = actualID // sync in-memory ID with actual DB row + } return err } diff --git a/internal/store/sqlitestore/providers.go b/internal/store/sqlitestore/providers.go index be8a6854..0555bae6 100644 --- a/internal/store/sqlitestore/providers.go +++ b/internal/store/sqlitestore/providers.go @@ -54,11 +54,24 @@ func (s *SQLiteProviderStore) CreateProvider(ctx context.Context, p *store.LLMPr p.UpdatedAt = now tid := tenantIDForInsert(ctx) p.TenantID = tid - _, err := s.db.ExecContext(ctx, + // UPSERT: if provider with same (tenant_id, name) exists, update it and return its ID. + // This handles orphaned providers left after agent deletion (#295). + var actualID string + err := s.db.QueryRowContext(ctx, `INSERT INTO llm_providers (id, name, display_name, provider_type, api_base, api_key, enabled, settings, created_at, updated_at, tenant_id) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(tenant_id, name) DO UPDATE SET + display_name = excluded.display_name, provider_type = excluded.provider_type, + api_base = excluded.api_base, api_key = excluded.api_key, + enabled = excluded.enabled, settings = excluded.settings, updated_at = excluded.updated_at + RETURNING id`, p.ID, p.Name, p.DisplayName, p.ProviderType, p.APIBase, apiKey, p.Enabled, settings, now, now, tid, - ) + ).Scan(&actualID) + if err == nil { + if parsed, parseErr := uuid.Parse(actualID); parseErr == nil { + p.ID = parsed // sync in-memory ID with actual DB row + } + } return err }