{"record":{"id":"2b17322208dcaf4c","repo":"SigNoz/signoz","slug":"query-s-already-finished","errorCode":null,"errorMessage":"query %s already finished","messagePattern":"query (.+?) already finished","errorType":"http","errorClass":"model.ApiError","httpStatus":404,"severity":"info","filePath":"pkg/query-service/app/clickhouseReader/query_progress/inmemory_tracker.go","lineNumber":143,"sourceCode":"\t\t// This is the first update\n\t\tqt.progress = &model.QueryProgress{}\n\t}\n\tupdateQueryProgress(qt.progress, p)\n\n\t// broadcast latest state to all subscribers.\n\tfor _, sub := range maps.Values(qt.subscriptions) {\n\t\tsub.send(*qt.progress)\n\t}\n}\n\nfunc (qt *queryTracker) subscribe() (\n\t<-chan model.QueryProgress, func(), *model.ApiError,\n) {\n\tqt.lock.Lock()\n\tdefer qt.lock.Unlock()\n\n\tif qt.isFinished {\n\t\treturn nil, nil, model.NotFoundError(fmt.Errorf(\n\t\t\t\"query %s already finished\", qt.queryId,\n\t\t))\n\t}\n\n\tsubscriberId := uuid.NewString()\n\tsubscription := newQueryProgressSubscription(qt.logger)\n\tqt.subscriptions[subscriberId] = subscription\n\n\tif qt.progress != nil {\n\t\tsubscription.send(*qt.progress)\n\t}\n\n\treturn subscription.ch, func() {\n\t\tqt.unsubscribe(subscriberId)\n\t}, nil\n}\n\nfunc (qt *queryTracker) unsubscribe(subscriberId string) {","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/SigNoz/signoz/blob/5069bf80b08f1f00d7e014eccc09902f9871004f/pkg/query-service/app/clickhouseReader/query_progress/inmemory_tracker.go#L125-L161","documentation":"NotFoundError from subscribe when the query tracker's isFinished flag is already set — SubscribeToQueryProgress is attempted after the query completed, so there is no future progress stream to subscribe to.","triggerScenarios":"Subscribing to progress for a query that already finished (cleanup raced or the client subscribed late). The subscribe path checks isFinished under the query lock and refuses to create a channel.","commonSituations":"Fast queries that finish before the client's subscribe request arrives; retries of subscription after network hiccups; clients that fetch the result first then subscribe.","solutions":["Handle NotFoundError by falling back to a final status fetch (the query is done — poll results instead)","Subscribe before triggering/early in the query lifecycle","For very short queries, skip progress subscription entirely"],"exampleFix":"// before\nch, cancel, apiErr := tracker.SubscribeToQueryProgress(ctx, queryId)\n\n// after\nch, cancel, apiErr := tracker.SubscribeToQueryProgress(ctx, queryId)\nif apiErr != nil && apiErr.Typ == model.ErrorNotFound {\n    // query already finished; fetch final result instead\n    return fetchFinalResult(ctx, queryId)\n}","handlingStrategy":"fallback","validationCode":"// Subscribe before dispatching the query, not after\ncleanup, _ := tracker.ReportQueryStarted(ctx, queryId)\ndefer cleanup()\nch, _, apiErr := tracker.SubscribeToQueryProgress(ctx, queryId)","typeGuard":"func isAlreadyFinished(apiErr *model.ApiError) bool {\n    return apiErr != nil && apiErr.Typ == model.ErrorNotFound && strings.Contains(apiErr.Err.Error(), \"already finished\")\n}","tryCatchPattern":"ch, cancel, apiErr := tracker.SubscribeToQueryProgress(ctx, queryId)\nif isAlreadyFinished(apiErr) {\n    return fetchFinalResult(ctx, queryId) // query done; get result\n}","preventionTips":["Subscribe immediately after query start","For sub-second queries skip progress streaming","Handle NotFound as 'completed', not as failure"],"tags":["query-progress","already-finished","subscription"],"backgroundTag":"resource-not-found","analyzedSha":"5069bf80b08f1f00d7e014eccc09902f9871004f","analyzedAt":"2026-08-28T06:22:12.824Z","schemaVersion":2},"datasetVersion":"2026-08-28T11:17:15.048Z"}