temporalio/temporal · error

only one of StartTime or CloseTime can be specified in a que

Error message

only one of StartTime or CloseTime can be specified in a query

What it means

The s3store query parser supports time-range narrowing on exactly one of StartTime or CloseTime per query, because the archive time-window computation needs a single anchor. Parse rejects queries whose WHERE clause includes predicates on both time columns.

Source

Thrown at common/archiver/s3store/query_parser.go:72

func (p *queryParser) Parse(query string) (*parsedQuery, error) {
	stmt, err := sqlparser.Parse(fmt.Sprintf(sqlquery.QueryTemplate, query))
	if err != nil {
		return nil, err
	}
	whereExpr := stmt.(*sqlparser.Select).Where.Expr
	parsedQuery := &parsedQuery{}
	if err := p.convertWhereExpr(whereExpr, parsedQuery); err != nil {
		return nil, err
	}
	if parsedQuery.workflowID == nil && parsedQuery.workflowType == nil {
		return nil, errors.New("WorkflowId or WorkflowType is required in query")
	}
	if parsedQuery.workflowID != nil && parsedQuery.workflowType != nil {
		return nil, errors.New("only one of WorkflowId or WorkflowType can be specified in a query")
	}
	if parsedQuery.closeTime != nil && parsedQuery.startTime != nil {
		return nil, errors.New("only one of StartTime or CloseTime can be specified in a query")
	}
	if (parsedQuery.closeTime != nil || parsedQuery.startTime != nil) && parsedQuery.searchPrecision == nil {
		return nil, errors.New("SearchPrecision is required when searching for a StartTime or CloseTime")
	}

	if parsedQuery.closeTime == nil && parsedQuery.startTime == nil && parsedQuery.searchPrecision != nil {
		return nil, errors.New("SearchPrecision requires a StartTime or CloseTime")
	}
	return parsedQuery, nil
}

func (p *queryParser) convertWhereExpr(expr sqlparser.Expr, parsedQuery *parsedQuery) error {
	if expr == nil {
		return errors.New("where expression is nil")
	}

	switch expr := expr.(type) {
	case *sqlparser.ComparisonExpr:

View on GitHub (pinned to bde624efd1)

Solutions

  1. Keep only one time predicate (StartTime or CloseTime) and rely on SearchPrecision for the scan window.
  2. Apply the second time bound as post-filtering on returned records in application code.
  3. Combine the remaining time condition with AND of SearchPrecision (required for time filters).

Example fix

// before
parser.Parse("WorkflowId = 'w1' AND StartTime > '2024-01-01' AND CloseTime < '2024-02-01'")
// after
parser.Parse("WorkflowId = 'w1' AND StartTime > '2024-01-01' AND SearchPrecision = 'Day'")
Defensive patterns

Strategy: validation

Validate before calling

hasStart := strings.Contains(query, "StartTime")
hasClose := strings.Contains(query, "CloseTime")
if hasStart && hasClose {
    return errors.New("specify only one of StartTime or CloseTime")
}

Try / catch

pq, err := parser.Parse(query)
if err != nil {
    if strings.Contains(err.Error(), "only one of StartTime or CloseTime") {
        return nil, status.Error(codes.InvalidArgument, err.Error())
    }
    return nil, err
}

Prevention

When it happens

Trigger: Calling Parse with e.g. "WorkflowId = 'w1' AND StartTime > '...' AND CloseTime < '...'" — both parsedQuery.startTime and parsedQuery.closeTime are non-nil after convertWhereExpr.

Common situations: Users writing standard date-range queries (start AND end bounds) as they would against SQL databases; query builders that always add both start and close time bounds.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/82ff607c41fd3576. Report an issue: GitHub.