gastownhall/beads · error

assignee must not be empty for an assignee-scoped summary

Error message

assignee must not be empty for an assignee-scoped summary

What it means

ValidateStatsAssignee refuses an empty or whitespace-only assignee when building an assignee-scoped summary. An assignee is an opaque identifier this layer has no vocabulary for, so it is returned UNCHANGED when accepted — trimming would silently answer for a different actor than the caller named. There is no default assignee to fall back to.

Source

Thrown at internal/workapi/stats.go:28

// The shared half of issueops.StatsReporter: the assignee-scoped question,
// which every implementation answers by asking storage two questions and
// folding the answers, rather than by running one aggregate.
//
// The workspace-wide question needs nothing here — it is a single seam call on
// every backend. This file exists because the assignee-scoped answer is
// ASSEMBLED, and assembling it twice is how the two front doors would come to
// disagree about what "your work" means.

// ValidateStatsAssignee resolves the actor an assignee-scoped summary answers
// for, refusing an empty or whitespace-only one with ErrValidation.
//
// It returns the value UNCHANGED when it accepts: an assignee is an opaque
// identifier this layer has no vocabulary for, so trimming it would silently
// answer for a different actor than the caller named.
func ValidateStatsAssignee(assignee string) (string, error) {
	if strings.TrimSpace(assignee) == "" {
		return "", fmt.Errorf("assignee must not be empty for an assignee-scoped summary%.0w", issueops.ErrValidation)
	}
	return assignee, nil
}

// BuildStatsAssigneeIssueFilter is the predicate that selects one actor's rows
// for the fold below.
//
// It carries the assignee and NOTHING else, which is a decision and not an
// omission: no status restriction (the fold tallies every status, including
// closed), no limit (a capped scan would silently under-report a busy actor's
// total), and no wisp suppression — the search seam merges the ephemeral tier
// unless told not to.
func BuildStatsAssigneeIssueFilter(assignee string) types.IssueFilter {
	return types.IssueFilter{Assignee: &assignee}
}

// BuildStatsAssigneeWorkFilter is the ready-work predicate for the same actor.
// It is a second filter type because ready work is a different question with

View on GitHub (pinned to 71377f2769)

Solutions

  1. Pass a non-empty assignee identifier, or skip the assignee-scoped summary entirely when no assignee was requested
  2. Guard at the call site: only build an assignee-scoped summary when an assignee was actually provided
  3. Compare against errors.Is(err, issueops.ErrValidation) and prompt the user for the assignee

Example fix

// before
assignee, err := workapi.ValidateStatsAssignee(flagAssignee) // flagAssignee == ""
// after
if flagAssignee != "" {
    assignee, err = workapi.ValidateStatsAssignee(flagAssignee)
} else {
    // build an unscoped summary instead
}
Defensive patterns

Strategy: validation

Validate before calling

if strings.TrimSpace(assignee) == "" {
    return fmt.Errorf("assignee-scoped summary requires a non-empty assignee")
}

Type guard

func isScopedSummary(assignee string) bool {
    return strings.TrimSpace(assignee) != ""
}

Try / catch

a, err := workapi.ValidateStatsAssignee(assignee)
if err != nil {
    if errors.Is(err, issueops.ErrValidation) {
        return fmt.Errorf("assignee required: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling workapi.ValidateStatsAssignee("") or ValidateStatsAssignee(" ") — directly or via a stats role that scopes a summary to one actor.

Common situations: A CLI handler passed an unset --assignee flag value straight through; a config/env lookup for the current user returned empty; a caller assumed an empty string meant 'all assignees' rather than omitting the scope.

Understand the failure class

Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/b616c2ea4ebae29f. Report an issue: GitHub.