{"record":{"id":"19a82316e1d7f6cb","repo":"gastownhall/beads","slug":"reopen-actor-must-not-be-empty","errorCode":null,"errorMessage":"reopen: actor must not be empty","messagePattern":"reopen: actor must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/domain/issue.go","lineNumber":1715,"sourceCode":"\t\tIssue:  issue,\n\t\tClosed: !row.AlreadyClosed,\n\t}, nil\n}\n\nfunc (u *issueUseCaseImpl) ReopenIssue(ctx context.Context, id string, params ReopenIssueParams, actor string) (ReopenIssueResult, error) {\n\treturn u.reopen(ctx, id, params, actor, false)\n}\n\nfunc (u *issueUseCaseImpl) ReopenWisp(ctx context.Context, id string, params ReopenIssueParams, actor string) (ReopenIssueResult, error) {\n\treturn u.reopen(ctx, id, params, actor, true)\n}\n\nfunc (u *issueUseCaseImpl) reopen(ctx context.Context, id string, params ReopenIssueParams, actor string, useWisp bool) (ReopenIssueResult, error) {\n\tif id == \"\" {\n\t\treturn ReopenIssueResult{}, fmt.Errorf(\"reopen: id must not be empty\")\n\t}\n\tif actor == \"\" {\n\t\treturn ReopenIssueResult{}, fmt.Errorf(\"reopen: actor must not be empty\")\n\t}\n\trow, err := u.issueRepo.Reopen(ctx, id, ReopenRowParams{Reason: params.Reason}, actor, IssueTableOpts{UseWispsTable: useWisp})\n\tif err != nil {\n\t\treturn ReopenIssueResult{}, fmt.Errorf(\"reopen %s: %w\", id, err)\n\t}\n\tissue, err := u.issueRepo.Get(ctx, id, IssueTableOpts{UseWispsTable: row.IsWisp})\n\tif err != nil {\n\t\treturn ReopenIssueResult{}, fmt.Errorf(\"reopen %s: reload: %w\", id, err)\n\t}\n\treturn ReopenIssueResult{\n\t\tIssue:    issue,\n\t\tReopened: row.Updated,\n\t}, nil\n}\n\nfunc (u *issueUseCaseImpl) ClaimIssueIfOpen(ctx context.Context, id, actor string) (ClaimResult, error) {\n\treturn u.claim(ctx, id, actor, false)\n}","sourceCodeStart":1697,"sourceCodeEnd":1733,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/domain/issue.go#L1697-L1733","documentation":"A guard error thrown by the reopen() use case when the actor argument is empty. Reopen events are attributed to an actor, so the library rejects empty identities to keep the audit trail meaningful.","triggerScenarios":"Calling ReopenIssue / ReopenWisp with actor=\"\" — e.g. missing identity in a headless environment, an integration that forgot to pass the actor through, or an empty default in a wrapper function.","commonSituations":"CI jobs without user identity env vars; service scripts calling reopen with a hardcoded empty actor; webhooks forwarding payloads that omit the acting user.","solutions":["Pass a non-empty actor (authenticated user, service account, or 'system' default).","Set the identity-providing environment variable in the runtime context.","Validate actor before the call and surface a clear configuration error."],"exampleFix":"// before\nusecase.ReopenIssue(ctx, id, params, \"\")\n// after\nif actor == \"\" { actor = \"automation@service\" }\nusecase.ReopenIssue(ctx, id, params, actor)","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(actor) == \"\" {\n    return fmt.Errorf(\"reopen: actor must not be empty\")\n}","typeGuard":"func validActor(a string) bool { return strings.TrimSpace(a) != \"\" }","tryCatchPattern":"if err := run(); err != nil {\n    if strings.Contains(err.Error(), \"actor must not be empty\") {\n        return fmt.Errorf(\"configure an actor identity before reopening\")\n    }\n    return err\n}","preventionTips":["Resolve actor once at startup with a sane default (e.g. service account).","Ensure identity env vars exist in headless environments.","Thread the actor explicitly through wrapper functions rather than defaulting to \"\".","Add a smoke test asserting mutations fail loudly without identity."],"tags":["validation","reopen-issue","empty-actor","guard"],"backgroundTag":"missing-actor-identity","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}