{"record":{"id":"c4f89a0305d5e4f8","repo":"SigNoz/signoz","slug":"query-s-already-started","errorCode":null,"errorMessage":"query %s already started","messagePattern":"query (.+?) already started","errorType":"http","errorClass":"model.ApiError","httpStatus":400,"severity":"warning","filePath":"pkg/query-service/app/clickhouseReader/query_progress/inmemory_tracker.go","lineNumber":29,"sourceCode":"\t\"golang.org/x/exp/maps\"\n)\n\n// tracks progress and manages subscriptions for all queries\ntype inMemoryQueryProgressTracker struct {\n\tlogger  *slog.Logger\n\tqueries map[string]*queryTracker\n\tlock    sync.RWMutex\n}\n\nfunc (tracker *inMemoryQueryProgressTracker) ReportQueryStarted(\n\tqueryId string,\n) (postQueryCleanup func(), apiErr *model.ApiError) {\n\ttracker.lock.Lock()\n\tdefer tracker.lock.Unlock()\n\n\t_, exists := tracker.queries[queryId]\n\tif exists {\n\t\treturn nil, model.BadRequest(fmt.Errorf(\n\t\t\t\"query %s already started\", queryId,\n\t\t))\n\t}\n\n\ttracker.queries[queryId] = newQueryTracker(tracker.logger, queryId)\n\n\treturn func() {\n\t\ttracker.onQueryFinished(queryId)\n\t}, nil\n}\n\nfunc (tracker *inMemoryQueryProgressTracker) ReportQueryProgress(\n\tqueryId string, chProgress *clickhouse.Progress,\n) *model.ApiError {\n\tqueryTracker, err := tracker.getQueryTracker(queryId)\n\tif err != nil {\n\t\treturn err\n\t}","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/SigNoz/signoz/blob/5069bf80b08f1f00d7e014eccc09902f9871004f/pkg/query-service/app/clickhouseReader/query_progress/inmemory_tracker.go#L11-L47","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nid := \"my-fixed-query-id\"\ncleanup, apiErr := tracker.ReportQueryStarted(ctx, id)\n\n// after\nid := uuid.NewString() // unique per attempt\ncleanup, apiErr := tracker.ReportQueryStarted(ctx, id)\nif cleanup != nil { defer cleanup() }","handlingStrategy":"validation","validationCode":"// Always generate a unique id per attempt\nqueryId := uuid.NewString()","typeGuard":"func isDuplicateQueryStart(apiErr *model.ApiError) bool {\n    return apiErr != nil && apiErr.Typ == model.ErrorBadRequest && strings.Contains(apiErr.Err.Error(), \"already started\")\n}","tryCatchPattern":"cleanup, apiErr := tracker.ReportQueryStarted(ctx, queryId)\nif isDuplicateQueryStart(apiErr) {\n    queryId = uuid.NewString() // regenerate and retry once\n    cleanup, apiErr = tracker.ReportQueryStarted(ctx, queryId)\n}\nif cleanup != nil { defer cleanup() }","preventionTips":["Never reuse queryIds across retries","Always defer the returned cleanup func","In tests, reset tracker state between cases"],"tags":["query-progress","duplicate-id","concurrency","signoz"],"backgroundTag":"duplicate-resource-id","analyzedSha":"5069bf80b08f1f00d7e014eccc09902f9871004f","analyzedAt":"2026-08-28T06:22:12.824Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}