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

  1. Handle NotFoundError by falling back to a final status fetch (the query is done — poll results instead)
  2. Subscribe before triggering/early in the query lifecycle
  3. 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

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.