temporalio/temporal · error

only one of WorkflowId or WorkflowType can be specified in a

Error message

only one of WorkflowId or WorkflowType can be specified in a query

What it means

The filestore/S3 visibility query parser allows exactly one primary key: either WorkflowId or WorkflowType, never both, since its archive lookup strategy can key off only one. Parse returns this error when the WHERE clause contains predicates on both columns.

Source

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

func NewQueryParser() QueryParser {
	return &queryParser{}
}

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")
	}

View on GitHub (pinned to bde624efd1)

Solutions

  1. Remove either the WorkflowId or the WorkflowType predicate, keeping the more selective one (WorkflowId).
  2. If both are needed for display, filter by WorkflowId and verify WorkflowType after fetching records.
  3. Validate query input at the UI/API layer to reject both fields being set before invoking Parse.

Example fix

// before
parser.Parse("WorkflowId = 'w1' AND WorkflowType = 'MyType'")
// after
parser.Parse("WorkflowId = 'w1'")
Defensive patterns

Strategy: validation

Validate before calling

var wfID, wfType bool
for _, f := range filters {
    if f.Name == "WorkflowId" { wfID = true }
    if f.Name == "WorkflowType" || f.Name == "WorkflowTypeName" { wfType = true }
}
if wfID && wfType {
    return errors.New("specify only one of WorkflowId or WorkflowType")
}

Try / catch

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

Prevention

When it happens

Trigger: Calling Parse with a WHERE clause like "WorkflowId = 'w1' AND WorkflowType = 'MyType'", including via AND-chained or parenthesized comparisons handled by convertAndExpr/convertParenExpr.

Common situations: Users porting queries from standard SQL visibility (which permits combined filters); query builders that append both user-supplied workflow ID and type unconditionally.

Related errors


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