semaphoreui/semaphore · error
invalid operation
Error message
invalid operation
What it means
ErrInvalidOperation is the sentinel error ('invalid operation') returned by store/service methods when an operation is not permitted in the current state — e.g. deleting an environment that is still referenced by templates, or deleting integrations/matchers that are in use. The API layer maps it to HTTP 409 Conflict via WriteError, or a 400 with a domain message.
Solutions
- Delete the dependent resources first (e.g. remove templates referencing the environment) and retry
- Check the sentinel with errors.Is(err, db.ErrInvalidOperation) to surface a 409/400 with a helpful message instead of a generic 500
- Query the references (templates using the environment, integration usages) before attempting deletion to warn users up front
Example fix
// before
if err := c.environmentService.Delete(env.ProjectID, env.ID); err != nil {
helpers.WriteError(w, err, http.StatusInternalServerError)
}
// after
if err := c.environmentService.Delete(env.ProjectID, env.ID); err != nil {
if errors.Is(err, db.ErrInvalidOperation) {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]any{"error": "Environment is in use by one or more templates"})
return
}
helpers.WriteError(w, err, http.StatusInternalServerError)
} Defensive patterns
Strategy: try-catch
Validate before calling
// before deleting, check references
templates, err := templateService.GetAll(env.ProjectID)
if err == nil {
for _, t := range templates {
if usesEnvironment(t, env.ID) {
return errors.New("environment is in use by templates")
}
}
} Try / catch
err := c.environmentService.Delete(env.ProjectID, env.ID)
if errors.Is(err, db.ErrInvalidOperation) {
helpers.WriteJSON(w, http.StatusBadRequest, map[string]any{"error": "Environment is in use by one or more templates"})
return
}
if err != nil {
helpers.WriteError(w, err.Error(), http.StatusInternalServerError)
} Prevention
- Map db.ErrInvalidOperation to 409/400 responses so clients get actionable messages
- Delete or reassign dependent resources before removing environments/integrations
- Warn users in the UI when an entity is still referenced
When it happens
Trigger: environmentService.Delete for an environment used by one or more templates (api/projects/environment.go:299); DeleteIntegration, DeleteIntegrationExtractValue, DeleteIntegrationMatcher, or RemoveInventory on entities that are in use.
Common situations: Users trying to remove an environment still referenced by project templates; deleting an integration whose extracted values/matchers are wired into resources; cascading cleanup scripts hitting in-use entities.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- no rows in result set
- Failed to link external account.
- This external account is already linked to another user.
- Your account already has a linked identity for this…
- Internal Server Error
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/0670fc28257cffa1.
Report an issue: GitHub.
Appendix: source
Thrown at db/Store.go:159
// ObjectProps describe database entities.
// It mainly used for NoSQL implementations (currently BoltDB) to preserve same
// data structure of different implementations and easy change it if required.
type ObjectProps struct {
TableName string
Type reflect.Type // to which type the table bust be mapped.
IsGlobal bool // doesn't belong to other table, for example to project or user.
ReferringColumnSuffix string
PrimaryColumnName string
SortableColumns []string
DefaultSortingColumn string
SortInverted bool // sort from high to low object ID by default. It is useful for some NoSQL implementations.
Ownerships []*ObjectProps
SelectColumns []string
}
var ErrNotFound = errors.New("no rows in result set")
var ErrInvalidOperation = errors.New("invalid operation")
type TaskStatUnit string
const TaskStatUnitDay TaskStatUnit = "day"
const TaskStatUnitWeek TaskStatUnit = "week"
const TaskStatUnitMonth TaskStatUnit = "month"
type TaskFilter struct {
Start *time.Time `json:"start"`
End *time.Time `json:"end"`
UserID *int `json:"user_id"`
Status []task_logger.TaskStatus
}
type TaskStat struct {
Date string `json:"date"`
CountByStatus map[task_logger.TaskStatus]int `json:"count_by_status"`
AvgDuration int `json:"avg_duration"`View on GitHub (pinned to 1774ccb71a)