gastownhall/beads · error

reopen: actor and issue ID must not be empty

Error message

reopen: actor and issue ID must not be empty

What it means

validateReopenRequest requires a non-empty Actor and IssueID on every ReopenRequest, wrapping this rejection in publicops.ErrValidation. Reopening records who performed the action and on which issue.

Source

Thrown at internal/storage/uow/issue_operations.go:581

	for _, key := range keys {
		if err := storage.ValidateMetadataKey(key); err != nil {
			return err
		}
		if !json.Valid(metadata.Set[key]) {
			return fmt.Errorf("metadata value for key %q is not valid JSON", key)
		}
	}
	for _, key := range metadata.Unset {
		if err := storage.ValidateMetadataKey(key); err != nil {
			return err
		}
	}
	return nil
}

func validateReopenRequest(request publicops.ReopenRequest) error {
	if request.Actor == "" || request.IssueID == "" {
		return validationError(fmt.Errorf("reopen: actor and issue ID must not be empty"))
	}
	return nil
}

func semanticIssueEqual(left, right *types.Issue) bool {
	if left == nil || right == nil {
		return left == right
	}
	leftCopy := *left
	rightCopy := *right
	leftCopy.UpdatedAt, rightCopy.UpdatedAt = time.Time{}, time.Time{}
	leftCopy.RowVersion, rightCopy.RowVersion = 0, 0
	return reflect.DeepEqual(leftCopy, rightCopy)
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set request.Actor to the acting identity before calling Reopen
  2. Set request.IssueID to the target issue key
  3. Guard the call site: skip or fix requests with empty fields
  4. Trace where the empty value originates (config, env, parsing) and add a default or error

Example fix

// before
req := publicops.ReopenRequest{IssueID: id}
// after
req := publicops.ReopenRequest{Actor: actor, IssueID: id}
if req.Actor == "" || req.IssueID == "" { return fmt.Errorf("reopen aborted: missing actor/id") }
Defensive patterns

Strategy: validation

Validate before calling

func validReopen(req publicops.ReopenRequest) error {
    if req.Actor == "" { return fmt.Errorf("actor required") }
    if req.IssueID == "" { return fmt.Errorf("issue ID required") }
    return nil
}

Type guard

func reopenRequestComplete(req publicops.ReopenRequest) bool {
    return req.Actor != "" && req.IssueID != ""
}

Try / catch

if err := uc.Reopen(ctx, req); err != nil {
    if errors.Is(err, publicops.ErrValidation) {
        // input bug: populate Actor/IssueID, do not retry blindly
        return err
    }
    return err
}

Prevention

When it happens

Trigger: Calling Reopen with a publicops.ReopenRequest where request.Actor == "" or request.IssueID == "".

Common situations: A reopen automation with no actor identity configured, IDs parsed from input that yielded empty strings, or a request struct partially populated after copy-paste from another operation.

Related errors


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