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
- Populate request.Actor with the closing identity
- Populate request.IssueID with the target issue key
- Filter/validate the ID list before looping Close calls
- 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
- Sanitize ID lists: drop empty strings before batch Close loops
- Configure a default actor identity for automations
- Validate every CloseRequest at construction time
- Handle ErrValidation distinctly from transient storage errors
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
- update: actor and issue ID must not be empty
- reopen: actor and issue ID must not be empty
- no store is open for this workspace
- not found
- no absolute native user directory is available
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/6b7295ed54313602.
Report an issue: GitHub.