SigNoz/signoz · warning · model.ApiError
query %s already started
Error message
query %s already started
What it means
BadRequest from ReportQueryStarted on the in-memory query progress tracker when a queryId is already registered and not yet finished. The tracker keys live queries by ID; registering a duplicate ID is rejected to prevent overwriting progress state.
Source
Thrown at pkg/query-service/app/clickhouseReader/query_progress/inmemory_tracker.go:29
"golang.org/x/exp/maps"
)
// tracks progress and manages subscriptions for all queries
type inMemoryQueryProgressTracker struct {
logger *slog.Logger
queries map[string]*queryTracker
lock sync.RWMutex
}
func (tracker *inMemoryQueryProgressTracker) ReportQueryStarted(
queryId string,
) (postQueryCleanup func(), apiErr *model.ApiError) {
tracker.lock.Lock()
defer tracker.lock.Unlock()
_, exists := tracker.queries[queryId]
if exists {
return nil, model.BadRequest(fmt.Errorf(
"query %s already started", queryId,
))
}
tracker.queries[queryId] = newQueryTracker(tracker.logger, queryId)
return func() {
tracker.onQueryFinished(queryId)
}, nil
}
func (tracker *inMemoryQueryProgressTracker) ReportQueryProgress(
queryId string, chProgress *clickhouse.Progress,
) *model.ApiError {
queryTracker, err := tracker.getQueryTracker(queryId)
if err != nil {
return err
}View on GitHub (pinned to 5069bf80b0)
Solutions
- Generate a fresh unique queryId (uuid) for every query start instead of reusing one
- Ensure the returned postQueryCleanup is always called (defer) so IDs are freed
- If retrying after a failure, only retry start when the previous ID was cleaned up
- Treat this as a client bug: check exists-map before reporting start in wrappers
Example fix
// before
id := "my-fixed-query-id"
cleanup, apiErr := tracker.ReportQueryStarted(ctx, id)
// after
id := uuid.NewString() // unique per attempt
cleanup, apiErr := tracker.ReportQueryStarted(ctx, id)
if cleanup != nil { defer cleanup() } Defensive patterns
Strategy: validation
Validate before calling
// Always generate a unique id per attempt queryId := uuid.NewString()
Type guard
func isDuplicateQueryStart(apiErr *model.ApiError) bool {
return apiErr != nil && apiErr.Typ == model.ErrorBadRequest && strings.Contains(apiErr.Err.Error(), "already started")
} Try / catch
cleanup, apiErr := tracker.ReportQueryStarted(ctx, queryId)
if isDuplicateQueryStart(apiErr) {
queryId = uuid.NewString() // regenerate and retry once
cleanup, apiErr = tracker.ReportQueryStarted(ctx, queryId)
}
if cleanup != nil { defer cleanup() } Prevention
- Never reuse queryIds across retries
- Always defer the returned cleanup func
- In tests, reset tracker state between cases
When it happens
Trigger: Calling ReportQueryStarted (via ReportQueryStartForProgressTracking) twice with the same queryId before the query finishes — e.g. retrying a query initiation with a reused/generated-collision ID, or a code path that reports start more than once.
Common situations: Custom integrations that reuse request IDs for query progress, retries that don't regenerate the queryId, or races where a previous query's cleanup (postQueryCleanup) was never invoked.
Related errors
AI-assisted analysis of SigNoz/signoz@5069bf80b0 (2026-08-28).
Data as JSON: /api/errors/c4f89a0305d5e4f8.
Report an issue: GitHub.