gastownhall/beads · error

close: actor and issue ID must not be empty

Error message

close: actor and issue ID must not be empty

What it means

validateCloseRequest requires a non-empty Actor and IssueID on every CloseRequest and wraps this rejection in publicops.ErrValidation. Closing an issue must record who closed it and which issue was closed.

Source

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

	return fmt.Errorf("%w: %w", publicops.ErrValidation, err)
}

func validateUpdateRequest(request publicops.UpdateRequest) error {
	if request.Actor == "" || request.IssueID == "" {
		return validationError(fmt.Errorf("update: actor and issue ID must not be empty"))
	}
	if err := storageissueops.ValidateUpdateRequest(request); err != nil {
		return validationError(err)
	}
	if err := validateMetadataPatch(request.Patch.Metadata); err != nil {
		return validationError(err)
	}
	return nil
}

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

func validateMetadataPatch(metadata publicops.MetadataPatch) error {
	if metadata.Replace.Set && (metadata.Merge.Set || len(metadata.Set) > 0 || len(metadata.Unset) > 0) {
		return fmt.Errorf("metadata replacement cannot combine with incremental edits")
	}
	if metadata.Replace.Set && len(metadata.Replace.Value) > 0 && !json.Valid(metadata.Replace.Value) {
		return fmt.Errorf("metadata replacement is not valid JSON")
	}
	if metadata.Merge.Set {
		var object map[string]json.RawMessage
		if len(metadata.Merge.Value) == 0 || json.Unmarshal(metadata.Merge.Value, &object) != nil || object == nil {
			return fmt.Errorf("metadata merge must be a JSON object")
		}
	}
	keys := make([]string, 0, len(metadata.Set))

View on GitHub (pinned to 71377f2769)

Solutions

  1. Populate request.Actor with the closing identity
  2. Populate request.IssueID with the target issue key
  3. Filter/validate the ID list before looping Close calls
  4. Check errors.Is(err, publicops.ErrValidation) to distinguish input bugs from storage failures

Example fix

// before
for _, id := range ids { uc.Close(ctx, publicops.CloseRequest{Actor: actor, IssueID: id}) }
// after
for _, id := range ids {
    if id == "" { continue }
    uc.Close(ctx, publicops.CloseRequest{Actor: actor, IssueID: id})
}
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

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

Try / catch

if err := uc.Close(ctx, req); err != nil {
    if errors.Is(err, publicops.ErrValidation) {
        // fix Actor/IssueID before retrying
        return err
    }
    return err
}

Prevention

When it happens

Trigger: Calling Close with a publicops.CloseRequest where request.Actor == "" or request.IssueID == "".

Common situations: Scripted bulk-close loops where an ID list contains an empty string, a close automation with no configured actor identity, or parsing issue IDs from input that produced empty fields.

Related errors


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