SigNoz/signoz · info · model.ApiError
query %s already finished
Error message
query %s already finished
What it means
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.
Source
Thrown at pkg/query-service/app/clickhouseReader/query_progress/inmemory_tracker.go:143
// This is the first update
qt.progress = &model.QueryProgress{}
}
updateQueryProgress(qt.progress, p)
// broadcast latest state to all subscribers.
for _, sub := range maps.Values(qt.subscriptions) {
sub.send(*qt.progress)
}
}
func (qt *queryTracker) subscribe() (
<-chan model.QueryProgress, func(), *model.ApiError,
) {
qt.lock.Lock()
defer qt.lock.Unlock()
if qt.isFinished {
return nil, nil, model.NotFoundError(fmt.Errorf(
"query %s already finished", qt.queryId,
))
}
subscriberId := uuid.NewString()
subscription := newQueryProgressSubscription(qt.logger)
qt.subscriptions[subscriberId] = subscription
if qt.progress != nil {
subscription.send(*qt.progress)
}
return subscription.ch, func() {
qt.unsubscribe(subscriberId)
}, nil
}
func (qt *queryTracker) unsubscribe(subscriberId string) {View on GitHub (pinned to 5069bf80b0)
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
Example fix
// before
ch, cancel, apiErr := tracker.SubscribeToQueryProgress(ctx, queryId)
// after
ch, cancel, apiErr := tracker.SubscribeToQueryProgress(ctx, queryId)
if apiErr != nil && apiErr.Typ == model.ErrorNotFound {
// query already finished; fetch final result instead
return fetchFinalResult(ctx, queryId)
} Defensive patterns
Strategy: fallback
Validate before calling
// Subscribe before dispatching the query, not after cleanup, _ := tracker.ReportQueryStarted(ctx, queryId) defer cleanup() ch, _, apiErr := tracker.SubscribeToQueryProgress(ctx, queryId)
Type guard
func isAlreadyFinished(apiErr *model.ApiError) bool {
return apiErr != nil && apiErr.Typ == model.ErrorNotFound && strings.Contains(apiErr.Err.Error(), "already finished")
} Try / catch
ch, cancel, apiErr := tracker.SubscribeToQueryProgress(ctx, queryId)
if isAlreadyFinished(apiErr) {
return fetchFinalResult(ctx, queryId) // query done; get result
} Prevention
- Subscribe immediately after query start
- For sub-second queries skip progress streaming
- Handle NotFound as 'completed', not as failure
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
AI-assisted analysis of SigNoz/signoz@5069bf80b0 (2026-08-28).
Data as JSON: /api/errors/2b17322208dcaf4c.
Report an issue: GitHub.